今天來寫關於嵌(ㄑㄧㄢ)入式RecyclerView,又稱為巢狀RecyclerView
巢狀RecyclerView簡單來說就是在原先的RecyclerView列表元件中在套入一層RecyclerView的意思(・ω・)b
這個啊~我敢跟各位打包票
有拿智慧型手機的一定有看過其應用
就是Google Play啦!!
沒錯,GooglePlay他就是可以直向滑動,也可以橫向滑動對吧
那麼,就來對照一下今天的範例吧
以及Github
→https://github.com/thumbb13555/NestedRecyclerDemo/tree/master
補充一下,這個也是本站中的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混合介面
歡迎參考參考~
1. 專案結構
這次的內容有點小複雜~畢竟一口氣寫了四個檔案
如下
這次的內容,簡單來說兩個檔案MyAdapter跟NestedAdapter都是模組,都是RecyclerView的適配器
而MyAdapter是主要的那個RecyclerView,而NestedAdapter則是嵌套在主要的RecyclerView裡面的那個
再來,MyData則是他們要吃的格式,我下面兩個單元會再詳細說明內容以及製作資料的部分
2. 介面繪製
有三個介面,分別是主介面main、主RecyclerView的Item跟嵌套RecyclerVIew的item
分別如下
<?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" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
就一個RecyclerView而已,沒什麼特別的
<?xml version="1.0" encoding="utf-8"?> <androidx.cardview.widget.CardView 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" app:cardCornerRadius="10dp" app:cardElevation="7dp" android:layout_margin="5dp" > <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" android:padding="5dp" > <TextView android:id="@+id/textView_Title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="TextView" android:textAppearance="@style/TextAppearance.AppCompat.Large" android:textStyle="bold" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerview_Nested" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:orientation="horizontal" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView_Title" /> </androidx.constraintlayout.widget.ConstraintLayout> </androidx.cardview.widget.CardView>
這裡有點特別!!要注意我粉底白字的部分
以往我都習慣把LayoutManager交給程式碼去弄
不過這個地方要在一開始就先定義好LayoutManager
並且orientation(定向)要事先設定為horizontal(橫向),設定完後介面會即使顯示出來給你看
這部分要特別注意喔!
<?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="wrap_content" android:orientation="horizontal" android:layout_height="wrap_content" android:padding="10dp"> <ImageView android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@color/cardview_shadow_start_color" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:srcCompat="@drawable/ic_launcher_foreground" /> <TextView android:id="@+id/textView_nesTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:lineSpacingExtra="14sp" android:text="TextView" android:textSize="18sp" android:textStyle="bold" app:layout_constraintEnd_toEndOf="@+id/imageView" app:layout_constraintStart_toStartOf="@+id/imageView" app:layout_constraintTop_toBottomOf="@+id/imageView" /> </androidx.constraintlayout.widget.ConstraintLayout>
這邊也沒什麼要注意的,就直接複製就好囉
3. 建立實體類MyData.java以及製作測試資料
接下來是撰寫資料及建立實體類MyData.java
建立過程省略,我直接PO內容給你
class MyData { private String title; private List<NestedData> nesData; public MyData(String title, List<NestedData> nesData) { this.title = title; this.nesData = nesData; } public String getTitle() { return title; } public List<NestedData> getNesData() { return nesData; } static class NestedData{ private String nesTitle; public NestedData(String title) { this.nesTitle = title; } public String getNesTitle() { return nesTitle; } } }
再來是測試資料,下面有放playground可以執行起來玩看看
執行起來會像這個樣子喔
至於這段程式寫在MainActivity裡面,我先把那部分PO出來~
public class MainActivity extends AppCompatActivity { MyAdapter myAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); /**製作資料*/ List<MyData> data = makeData(); } private List<MyData> makeData(){ List<MyData> data = new ArrayList<>(); String[] title = new String[]{"週一","週二","週三","週四","週五"}; String[] nesTitle = new String[]{"清燉牛肉","番茄炒蛋","炸雞腿","糖醋魚","焗烤燉飯","爆炒蝦仁","紅燒鮭魚"}; for (int i = 0; i < title.length; i++) { int r = (int)(Math.random()*7); List<MyData.NestedData> nesArray = new ArrayList<>(); for (int j = 0; j < r+1; j++) { nesArray.add(new MyData.NestedData(nesTitle[(int)(Math.random()*7)])); } data.add(new MyData(title[i],nesArray)); } return data; } }
4. 撰寫嵌套的Adapter(NestedAdapter.java)
接下來是重點了,再來要來寫RecylerView的Adapter
順序上我是從最裡面的那個一層一層寫
所以我們先寫被嵌入的那一層~
class NestedAdapter extends RecyclerView.Adapter<NestedAdapter.NesHolder> { private List<MyData.NestedData> nestedData; private OnChildClick childClick; private int parentPosition; public NestedAdapter(List<MyData.NestedData> nestedData,int parentPosition, OnChildClick childClick) { this.nestedData = nestedData; this.childClick = childClick; this.parentPosition = parentPosition; } public class NesHolder extends RecyclerView.ViewHolder { TextView tvTitle; public NesHolder(@NonNull View itemView) { super(itemView); tvTitle = itemView.findViewById(R.id.textView_nesTitle); } } @NonNull @Override public NesHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new NesHolder(LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_nested, parent, false)); } @Override public void onBindViewHolder(@NonNull NesHolder holder, int position) { MyData.NestedData item = nestedData.get(position); holder.tvTitle.setText(item.getNesTitle()); holder.itemView.setOnClickListener(v -> { //取得點擊到的item,並使用interface回傳 childClick.onChildClick(item,parentPosition); }); } @Override public int getItemCount() { return nestedData.size(); } interface OnChildClick { void onChildClick(MyData.NestedData data,int parentPosition); } }
這層比較沒什麼特別的,基本上就是一個普通的RecyclerVIew寫法
而由於這次的內容還包含點擊事件,因此我加入了Interface以便後續撰寫
關於這方面不太了解的,可以慘考我這篇文章喔
→碼農日常-『Android studio』NumberPicker 配合 Interface (接口)完成一個時間選擇器
4. 撰寫主Adapter(MyAdapter.java)
這裏就是比較需要注意的地方了
不過一樣先貼
class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> implements NestedAdapter.OnChildClick { private RecyclerView.RecycledViewPool viewPool = new RecyclerView.RecycledViewPool(); private List<MyData> myData; private OnItemClick onItemClick; public MyAdapter(List<MyData> myData,OnItemClick onItemClick) { this.myData = myData; this.onItemClick = onItemClick; } public class MyViewHolder extends RecyclerView.ViewHolder { private TextView tvTitle; private RecyclerView recyclerView; public MyViewHolder(@NonNull View itemView) { super(itemView); tvTitle = itemView.findViewById(R.id.textView_Title); recyclerView = itemView.findViewById(R.id.recyclerview_Nested); } } @NonNull @Override public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new MyViewHolder(LayoutInflater.from(parent.getContext()) .inflate(R.layout.item,parent,false)); } @Override public void onBindViewHolder(@NonNull MyViewHolder holder, int position) { holder.tvTitle.setText(myData.get(position).getTitle()); //設置巢狀RecyclerView holder.recyclerView.setAdapter(new NestedAdapter(myData.get(position).getNesData() ,position,this)); holder.recyclerView.setRecycledViewPool(viewPool); } @Override public int getItemCount() { return myData.size(); } /**此處為點選Child-Item後從 * @see NestedAdapter 的回傳*/ @Override public void onChildClick(MyData.NestedData data, int parentPosition) { onItemClick.onItemClick(data,myData.get(parentPosition)); } interface OnItemClick{ void onItemClick(MyData.NestedData data,MyData myData); } }
首先是藍底白字的部分
這是RecyclerView底下的一個類別叫RecyclerViewPool
以往只要命名上出現Pool的方法大多跟緩加載有關聯
沒錯,這個方法的確就是優化執行緒用的
而綠底白字的部分則一樣是將recyclerView綁定Adapter用的
最後一樣是Interface的設置,我就不再囉唆了
5. 撰寫主程式(MainActivity.java)
以上都設置好模組了,接下來就是只剩使用它們!(-‿◦)
總之廢話不多說,填入以下內容即完成(;´д`)ゞ
public class MainActivity extends AppCompatActivity { MyAdapter myAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); /**製作資料*/ List<MyData> data = makeData(); /**設置RecyclerView*/ RecyclerView recyclerView = findViewById(R.id.recyclerView); recyclerView.setLayoutManager(new LinearLayoutManager(this)); //設置Adapter以及點擊回饋 myAdapter = new MyAdapter(data, new MyAdapter.OnItemClick() { @Override public void onItemClick(MyData.NestedData data, MyData myData) { Toast.makeText(MainActivity.this , "選擇了"+myData.getTitle()+"的 "+data.getNesTitle() , Toast.LENGTH_SHORT).show(); } }); recyclerView.setAdapter(myAdapter); } private List<MyData> makeData(){ List<MyData> data = new ArrayList<>(); String[] title = new String[]{"週一","週二","週三","週四","週五"}; String[] nesTitle = new String[]{"清燉牛肉","番茄炒蛋","炸雞腿","糖醋魚","焗烤燉飯","爆炒蝦仁","紅燒鮭魚"}; for (int i = 0; i < title.length; i++) { int r = (int)(Math.random()*7); List<MyData.NestedData> nesArray = new ArrayList<>(); for (int j = 0; j < r+1; j++) { nesArray.add(new MyData.NestedData(nesTitle[(int)(Math.random()*7)])); } data.add(new MyData(title[i],nesArray)); } return data; } }
我寫的文章還有附帶點擊事件喔~很有誠意吧XD
這篇我有一點點隨便寫(先承認
因為其實今天在Coding遇到了一點小問題,基本上就是顯示結果有些不如預期這樣
不過當你看到這篇文章時,表示我當幫你克服囉XD
後來又東摸西摸..最後搞得這篇半夜才寫完(╯=▃=)╯︵┻━┻
而且寫完Code後要寫文章時,莫名其妙又跑去弄網誌的簽名檔,一整個不知道自己在衝三毀( ´_ゝ`)
不過簽名檔也弄好了,有點像終於把魚骨頭挑出來一樣舒爽(・ωー)~☆
這是新的簽名檔喔
↓
圖是自己畫的XD
不過做出來效果比想像得好..畢竟我不是什麼設計系的囧
好啦,那本文就到這邊,最後..