今天要來寫RecyclerView系列的第8彈!(沃草,第八篇關於RecyclerView?)
帶指標的橫向RecyclerView實作!
其他的
碼農日常-『Android studio』基本RecyclerView用法
碼農日常-『Android studio』基本RecyclerView 用法-2 基本版下拉更新以及點擊事件
碼農日常-『Android studio』基本RecyclerView 用法-3 RecyclerView上下滑動排序與側滑刪除(RecyclerView Swipe)
碼農日常-『Android studio』基本RecyclerView 用法-4 左滑顯示Button Menu
碼農日常-『Android studio』進階RecyclerView 用法-5 RecyclerView item混合介面
碼農日常-『Android studio』嵌套式RecyclerView(Nested RecyclerView)實現
碼農日常-『Android studio』RecyclerView自動捲動與控制捲動速度
完了,這系列真的會寫到不知道哪裡去...
好啦回歸正題,今天要寫的東西跟ViewPager有點接近,不過就我個人來說我覺得使用起來應該會比ViewPager稍微更簡單一些
用寫的有點抽象,我們來看一下Gif吧!
簡單來說吧,今天的實作主要就是下面的那一排「點點」
因為在UI的設計上來說,感覺橫向的物件沒有一個標記物還挺奇怪的...(不知道為什麼...
而有的時候再做開發時,感覺這種橫向的物件就使用ViewPager感覺又蠻殺雞焉用牛刀的
於是後來想著想著,不然就來做一個物件綁定RecyclerView的position就好啦...是這麼說啦
不過實做起來意外地燒腦了下
總之,既然看到了我寫的文章,那就便是希望能夠解決你的問題囉~
總之先上Github吧!
->點我
1. 撰寫「標記」的介面
整體來說,這次的實作有兩大步驟
次序上大致就是先完成「標記」的介面,再來完成關於RecyclerView的整體介面
因此,在文章的首先,我要先來完成標記的介面
首先,看一下這次的資料夾結構
在第一段落中,首要目標是完成紅色&綠色部分
總之先從紅色的部分下手吧!
首先,先來畫一下dot_root.xml這隻檔案
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/root" android:layout_width="match_parent" android:layout_height="match_parent" > </FrameLayout>
這裡的畫面基本上就是
完全一片空白(笑)
不是我少PO,是因為這不份的重點是為之後要畫的介面製造一個載體,讓他可以被主界面給使用
好啦,再來就是重點了,如下
public class DotIndicator extends View { private int thumbColor = Color.parseColor("#00A0AF"); private int trackColor = Color.parseColor("#ADD0EB"); private Paint paint; private int pageNum; private int currentPosition; private int gapSize; private float radius; private int colorOn; private int colorOff; public DotIndicator(Context context) { super(context); init(); } public DotIndicator(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } public DotIndicator(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } public void setThumbColor(int thumbColor) { this.thumbColor = thumbColor; } public void setTrackColor(int trackColor) { this.trackColor = trackColor; } private void init() { radius = dp2px(10f); paint = new Paint(Paint.ANTI_ALIAS_FLAG); colorOn = thumbColor; colorOff = trackColor; gapSize = dp2px(30f); } //獲取現在的位置 public void onPageScrolled(int position) { currentPosition = position; invalidate(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (pageNum <= 0) { return; } //計算標籤的位置 float left = (getWidth() - (pageNum - 1) * gapSize) * 0.5f; float height = getHeight() * 0.5f; paint.setColor(colorOff); //根據數量來畫出點點的數量 for (int i = 0; i < pageNum; i++) { canvas.drawCircle(left + i * gapSize, height, radius, paint); } paint.setColor(colorOn); //設置被選中的點點將之設為深色 canvas.drawCircle(left + currentPosition * gapSize, height, radius, paint); } //取得item的數量 public void setPageNum(int num) { pageNum = num; } private int dp2px(float dp) { final float scale = getContext().getResources().getDisplayMetrics().density; return (int) (dp * scale * 0.5f); } }
這裡說複雜也不複雜,其實有自己畫過介面的人應該多多少少都知道
這裡就是一個自己畫介面的組合
而我們在這裡的製作邏輯上,基本上就是用setPageNum(int num)取得RecyclerView所乘載的item的數量
再來在onDraw的綠色部分去依照item的數量去繪製相同數量的點點
再來使用onPageScrolled(int position)取得即時頁次的接口,而該頁次繪製成深色的部分就是在onDraw內使用橘色部分的程式碼去繪製
...大概就是這樣的一個流程
好的,寫到這裡就算是寫好「點點」的介面了
完成這部分的繪圖後,接下來就是要來撰寫控制器的部分了!
2. 撰寫控制器
接下來要撰寫的是讓ReyclerView的狀態綁定剛才的繪圖介面的部分
在RecyclerView的控制裡面,有個方法叫做addOnScrollListener,基本上可以理解為這個方法就是在RecyclerView被滑動時的反饋
而我們基本上就是運用這個功能下去完成實作的,如下
public class RecyclerViewIndicator extends FrameLayout { private final FrameLayout rootView; private DotIndicator dotIndicator; public RecyclerViewIndicator(@NonNull Context context) { this(context,null); } public RecyclerViewIndicator(@NonNull Context context, @Nullable AttributeSet attrs) { this(context, attrs,0); } public RecyclerViewIndicator(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); View root = inflate(context, R.layout.dot_root, this); rootView = root.findViewById(R.id.root); } public DotIndicator getDotIndicator() { return dotIndicator; } public void setWithRecyclerView(RecyclerView view) { dotIndicator = new DotIndicator(view.getContext()); rootView.post(() -> { int count = view.getAdapter().getItemCount(); dotIndicator.setPageNum(count); rootView.addView(dotIndicator); }); view.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); //獲取現在的位置,並更新給「點」 LinearLayoutManager manager = ((LinearLayoutManager) recyclerView.getLayoutManager()); assert manager != null; int current = manager.findLastVisibleItemPosition(); dotIndicator.onPageScrolled(current); } }); } }
基本上重點都在setWithRecyclerView(RecyclerView view)裡面
而由於我們要讓畫介面的處在非同步下去畫,因此將介面的初始化部分寫在了綠色的裡面
最後,調用addOnScrollListener去對標籤做更新,初始化以及翻頁的事件就完成囉!
另外,在這支檔案最上面的三個建構子的部分也要注意好,我當時在寫的時候就因為沒注意好這裡一直閃退,查了很久才查出來...
3. 畫主介面UI
前面終於把基底寫好了(累
接下來就是來把其他部分給完成起來囉!
首先是RecyclerView的item
iteml.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content"> <androidx.cardview.widget.CardView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="10dp" app:cardCornerRadius="6dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="10dp"> <TextView android:id="@+id/textView_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="TextView" android:textAppearance="@style/TextAppearance.AppCompat.Display1" android:textColor="#000000" android:textStyle="bold" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/textView_depiction" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="24dp" android:text="TextView" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView_title" /> <ImageView android:id="@+id/imageView_tank" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="16dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView_depiction" app:srcCompat="@drawable/cromwell" /> </androidx.constraintlayout.widget.ConstraintLayout> </androidx.cardview.widget.CardView> </androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView_display" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toTopOf="@+id/indicator" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <com.noahliu.horizontalrecyclerviewwithindicatorexample.RecyclerViewIndicator android:id="@+id/indicator" android:layout_width="match_parent" android:layout_height="10dp" app:layout_constraintBottom_toTopOf="@+id/guideline" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.8" /> </androidx.constraintlayout.widget.ConstraintLayout>
4. 撰寫主要功能
最後就是主要功能的部分了,這部分的話因為基本上都是RecyclerView的設置而已,我也不再多說廢話
首先是關於坦克的實體類
package com.noahliu.horizontalrecyclerviewwithindicatorexample; public class TanksData { private String title; private int image; private String depiction; public TanksData(String title, int image, String depiction) { this.title = title; this.image = image; this.depiction = depiction; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public int getImage() { return image; } public void setImage(int image) { this.image = image; } public String getDepiction() { return depiction; } public void setDepiction(String depiction) { this.depiction = depiction; } }
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> { public ArrayList<TanksData> data; public MyAdapter(ArrayList<TanksData> data) { this.data = data; } public static class ViewHolder extends RecyclerView.ViewHolder { TextView tvTitle,tvDepiction; ImageView igTank; public ViewHolder(View itemView) { super(itemView); tvTitle = itemView.findViewById(R.id.textView_title); tvDepiction = itemView.findViewById(R.id.textView_depiction); igTank = itemView.findViewById(R.id.imageView_tank); } } //綁定標籤點點列 public void setIndicator(RecyclerViewIndicator indicator,RecyclerView recyclerView){ indicator.setWithRecyclerView(recyclerView); } @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new ViewHolder(LayoutInflater.from(parent.getContext()) .inflate(R.layout.item, parent, false)); } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { TanksData tanksData = data.get(position); holder.tvTitle.setText(tanksData.getTitle()); holder.tvDepiction.setText(tanksData.getDepiction()); holder.igTank.setImageResource(tanksData.getImage()); } @Override public int getItemCount() { return data.size(); } }
在藍底白字的部分就是將標記綁定的地方優,千萬別忘了
最後,就是MainActivity的部分了
MainActivity.java
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); RecyclerViewIndicator indicator = findViewById(R.id.indicator); RecyclerView recyclerView = findViewById(R.id.recyclerView_display); recyclerView.setLayoutManager(new LinearLayoutManager(this , LinearLayoutManager.HORIZONTAL, false)); //令RecyclerView的Item在滑動時可自動置中的方法 LinearSnapHelper snapHelper = new LinearSnapHelper(); snapHelper.attachToRecyclerView(recyclerView); ArrayList<TanksData> data = new ArrayList<>(); data.add(new TanksData("Mark I坦克",R.drawable.mark,"這是第一種被正式命名為“坦克”的裝甲戰鬥車輛,於1916年首次出現在戰場上。")); data.add(new TanksData("Churchill坦克",R.drawable.churchill,"在第二次世界大戰期間,Churchill坦克是英國陸軍的主要戰車之一,擁有強大的裝甲和火力,是一款可靠的坦克。")); data.add(new TanksData("Cromwell坦克",R.drawable.cromwell,"在第二次世界大戰期間,Cromwell坦克是英國陸軍的主要戰車之一,擁有高速度和卓越的機動性。")); data.add(new TanksData("Centurion坦克",R.drawable.centurion,"在冷戰期間,Centurion坦克是英國陸軍主力坦克之一,經過多次改進後,成為了一款強大的主戰坦克。")); data.add(new TanksData("Challenger 2坦克",R.drawable.challenger,"是當代英國陸軍主力坦克之一,具有強大的裝甲和火力,並且具有良好的防禦能力。")); data.add(new TanksData("Warrior步兵戰車",R.drawable.warrior,"是一款由英國製造的輪式裝甲車,是英國陸軍中的主要裝甲車之一。")); MyAdapter adapter = new MyAdapter(data); recyclerView.setAdapter(adapter); //設置「點」進入綁定RecyclerView adapter.setIndicator(indicator,recyclerView); } }
兩個重點,首先是綠底的部分,這邊的意思是讓RecyclerView在滑動時會自動讓item跑到中間的方法
而粉底白字的就是我們本篇的重點,也就是讓RecyclerView與標記綁定的部分囉
我從開始寫Android到現在,也真的是遇到過了不少像這樣要自行畫介面的需求
而每次畫介面都是考驗邏輯跟考驗數學的時候(嘆
不過這次的也算簡單的了啦,就是剛開始有點不知道從何下手
好啦,無論如何,也是希望這篇文章對你有幫助,如果覺得我寫得不錯的話...