# Android筆記--RecyclerView(循環視圖)(進階篇)
---
- 接續 [Android筆記–RecyclerView(循環視圖)(基礎篇)](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/Android_RecyclerView)
再來是關於RecyclerView在各種地方的實務。
----
▼1.簡單的加上按鈕
---
>[!Note]參考:[[Day 9] Android in Kotlin: 簡單的 Recycler View](https://ithelp.ithome.com.tw/articles/10241713?sc=rss.iron)
**效果**: 
:::success
:::spoiler ※===詳細製作法===※
- 1.首先我們到Holder綁定的Layout增加按鈕

- 2.在Holder中將新增的Button綁定
- 3.改採用apply的標準函數,然後給按鈕設置監聽器(要注意==context參數要用`holder.itemView.context`來取得==)
>[!Note]補充:[apply是甚麼?](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/kotlin_ScopeFunction)

:::
---
▼2.使用addItemDecoration()添加裝飾
---
RecyclerView 中的 addItemDecoration 用於在 RecyclerView 的項目之間添加裝飾,例如分割線、邊框、陰影等。要使用 addItemDecoration 方法,需要建立一個 ItemDecoration 類別。
:::info
:::spoiler
```kotlin=
//基本使用法: 呼叫RecyclerView之後再呼叫addItemDecoration(),通常配合apply使用。
bindingMainbinding.rv1ActivityMain.apply {
addItemDecoration(
...(內部填入ItemDecoration物件)
)
}
//範例:
bindingMainbinding.rv1ActivityMain.apply {
layoutManager = LinearLayoutManager(this@MainActivity)
setHasFixedSize(true)
addItemDecoration(DividerItemDecoration(this@MainActivity, DividerItemDecoration.HORIZONTAL))
adapter = MyAdapter(MyList())
}
```
### **添加分隔橫線**: "DividerItemDecoration.VERTICAL"

```kotlin=
addItemDecoration(DividerItemDecoration(this@MainActivity, DividerItemDecoration.VERTICAL))
```
### **添加分隔直線**: "DividerItemDecoration.HORIZONTAL"

```kotlin=
addItemDecoration(DividerItemDecoration(this@MainActivity, DividerItemDecoration.HORIZONTAL))
```
-
:::
---
▼3.添加波紋效果
---
效果:
:::success
:::spoiler ※===詳細製作法===※
### 方法1:

```kotlin=
android:background="?attr/selectableItemBackground"
```
### 方法2:

```kotlin=
android:foreground="?attr/selectableItemBackground"
```
- ==使用foreground和background的差別在於使用foreground可以變更該View的背景顏色== (因為foreground不會占用background屬性),而background不行。
- ==必須加上`android:clickable="true"`和`android:focusable="true"`屬性,否則波紋效果的設定有時會不起作用。==
- 有`selectableItemBackground`(有邊界)和`selectableItemBackgroundBorderless`(無邊界)兩種版本
:::
:::success
:::spoiler ※===參考===※
- ==★==[Android 波紋效果 Ripple Effect](https://medium.com/@waynechen323/android-%E6%B3%A2%E7%B4%8B%E6%95%88%E6%9E%9C-ripple-effect-c025940bf14c)
- [android使用selectableItemBackground的一些坑](https://haldir65.github.io/2016/09/23/2016-09-23-selectableItemBackground-foreground/)
- [android 按钮水波纹效果【背景色】](https://blog.csdn.net/fengyeNom1/article/details/105950836)
- [[使用 ?attr/selectableItemBackground 作为背景时如何修改波纹颜色?]](https://segmentfault.com/q/1010000043160224)
:::
---
▼4.卡片式佈局
---
將RecyclerView變成卡片式的,可以提升質感。

:::info
:::spoiler ※===詳細製作法===※
- 基本上就只是對View右鍵Convert View -> 轉換成CardView->再重放一個ConstraintLayout
- ==可以在CardView的地方加上`app:cardCornerRadius="xxdp"`,來調整弧度,上面的示範圖為20dp==



:::
---
▼5.載入圖片
---

:::success
:::spoiler ※===詳細製作法===※
### 1.將圖片放入resource裡

### 2.將圖片位置的參照放到我們製作的串列裡,方便調用

### 3.在ViewHolder中加上綁定、在onViewBinding中使用`setImageResource()`設置圖片,`setImageResource`內要填入位置參照。

:::
▼6.使用Glide載入圖片
---
>[!Note]參考:[Android筆記–Glide](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/android_Glide)
將Glide需求的參數填入即可,`with(holder.itemView.context)`,`load(圖片來源)`,`into(要放置的地方)`,onBindViewHolder()外的其他部分直接使用["▼5.載入圖片"](https://hackmd.io/ZxHCAtWdSYS_ZeFBMLF2iA?both#%E2%96%BC5%E8%BC%89%E5%85%A5%E5%9C%96%E7%89%87)的就可以了

▼7.強制禁止itemView隨大小自動調整
---
只要在Adapter中的`onBindViewHolder`加上
```kotlin=
holder.myItemView.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
```
就可以了

使用前:

使用後:

▼8.根據不同的狀況採用不同的ItemView
---

:::spoiler 詳細製作法
當 RecyclerView 的列表中需要顯示不同的佈局時,我們可以使用RecyclerView中的`getItemViewType()`來達到此效果(或是也可以直接手動改變)
- `getItemViewType()`: 使用傳入的 position,返回一個整數類型的值 (viewType),用來區分不同的項目類型,回傳的值會在Adapter的[onCreateViewHolder()](https://hackmd.io/ErN8nQN6SsaSHYh7LejGHg?view=&stext=6427%3A7%3A0%3A1737285342%3AzTiINF)中被使用,也就是說,==我們必須在`getItemViewType()`實現一個判斷邏輯來判斷資料集的第position筆資料是屬於哪一類型==。
<本篇使用的範例中有使用DataBinding>
:::spoiler 步驟1.製作多個ItemView

:::
:::spoiler 步驟2.宣告要用在getItemViewType()中的類型的代號
```kotlin=
//宣告要用在getItemViewType()中的類型的代號
companion object{
const val type_1 = 1
const val type_2 = 2
}
```
:::
:::spoiler 步驟3.製作對應的ViewHolder
```kotlin=
//建構ItemView裡元件的綁定以及運作邏輯,有幾個類型就要幾個
inner class TaskRecyclerViewHolder_1(private val rvItem1Binding: RvItem1Binding): RecyclerView.ViewHolder(rvItem1Binding.root){
fun bind(task: TaskDataClass){
rvItem1Binding.tasks = task
rvItem1Binding.checkBox1.setOnCheckedChangeListener(null)
rvItem1Binding.checkBox1.isChecked = task.taskDone
rvItem1Binding.checkBox1.setOnCheckedChangeListener { buttonView, isChecked ->
if(task != null){
viewModel.updateTaskDone(task.taskID, isChecked)
}
}
rvItem1Binding.executePendingBindings()
}
}
inner class TaskRecyclerViewHolder_2(private val rvItem2Binding: RvItem2Binding): RecyclerView.ViewHolder(rvItem2Binding.root){
fun bind(task: TaskDataClass){
rvItem2Binding.tasks = task
rvItem2Binding.checkBox2.setOnCheckedChangeListener(null)
rvItem2Binding.checkBox2.isChecked = task.taskDone
rvItem2Binding.checkBox2.setOnCheckedChangeListener { buttonView, isChecked ->
if(task != null){
viewModel.updateTaskDone(task.taskID, isChecked)
}
}
}
}
```
:::
:::spoiler 步驟4.覆寫getItemViewType()方法,並在其中實現分類邏輯
```kotlin=
//在這裡實現分類邏輯,當taskDone為true(被勾選時),就會被分類到type_1
override fun getItemViewType(position: Int): Int {
return when{
currentTaskList[position].taskDone == true -> type_1
else -> type_2
}
}
```
:::
:::spoiler 步驟5.根據ViewType的種類數量,充填對應的ItemViewBinding
```kotlin=
//getItemViewType()中回傳的類型代號(viewType)會傳入到onCreateViewHolder(),用來判斷需要創建哪
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
// 根據傳入的 viewType,選擇對應的佈局並初始化對應的 ViewHolder
return when(viewType){
// 為 type_1 的項目充填對應的 RvItem1Binding,並回傳 TaskRecyclerViewHolder_1
type_1 -> {
val rvItem1Binding = RvItem1Binding.inflate(LayoutInflater.from(parent.context), parent, false)
TaskRecyclerViewHolder_1(rvItem1Binding)
}
// 為其他類型的項目充填對應的 RvItem2Binding,並回傳 TaskRecyclerViewHolder_2
else -> {
val rvItem2Binding = RvItem2Binding.inflate(LayoutInflater.from(parent.context), parent, false)
TaskRecyclerViewHolder_2(rvItem2Binding)
}
}
}
```
:::
:::spoiler 步驟6.根據使用的Holder呼叫我們設計的Bind()方法
```kotlin=
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when(holder){
is TaskRecyclerViewHolder_1 -> { holder.bind_1(currentTaskList[position]) }
is TaskRecyclerViewHolder_2 -> { holder.bind_2(currentTaskList[position]) }
}
}
```
>[!Caution]GitHub專案: [Task_12](https://github.com/PudCheetah/Task_12)
:::
▼9.使用DataBinding讓資料自動綁定
---
>[!Note]參考:[Android筆記–DataBinding](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/android_dataBinding)
如果要在RecyclerView中使用DataBinding,在佈局文件的`variable`中的`type`,要填入`DataClass`
:::spoiler 範例
```kotlin=
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="tasks"
type="com.example.task_12.TaskDataClass" />
</data>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#2196F3"
app:cardCornerRadius="4dp"
app:cardElevation="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#5103A9F4"
android:orientation="vertical">
<TextView
android:id="@+id/task_name_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{tasks.taskName}" />
<CheckBox
android:id="@+id/checkBox_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="@{tasks.taskDone}"
android:text="CheckBox" />
<TextView
android:id="@+id/textView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Type_1" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</layout>
```
```kotlin=
//DataClass
data class TaskDataClass(
val taskID: Long = 0,
var taskName: String = "",
var taskDone: Boolean = false
)
```
```kotlin=
//RecyclerView
class TaskRecyclerViewAdapter(private val viewModel: TaskViewModel): ListAdapter<TaskDataClass, RecyclerView.ViewHolder>(TaskDiffCallback()){
private var currentTaskList = listOf<TaskDataClass>()
...
inner class TaskRecyclerViewHolder_1(private val rvItem1Binding: RvItem1Binding): RecyclerView.ViewHolder(rvItem1Binding.root){
fun bind_1(task: TaskDataClass){
//在這裡將傳入的task和xml中的tasks連結
rvItem1Binding.tasks = task
rvItem1Binding.checkBox1.setOnCheckedChangeListener(null)
rvItem1Binding.checkBox1.isChecked = task.taskDone
rvItem1Binding.checkBox1.setOnCheckedChangeListener { buttonView, isChecked ->
if(task != null){
viewModel.updateTaskDone(task.taskID, isChecked)
}
}
rvItem1Binding.executePendingBindings()
}
}
...
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val task = getItem(position)
when(holder){
is TaskRecyclerViewHolder_1 -> { holder.bind_1(currentTaskList[position]) }
...
}
}
```
:::
>[!Caution]GitHub專案: [Task_12](https://github.com/PudCheetah/Task_12)
▼10.使用DiffUtil高效更新 RecyclerView 的內容
---
DiffUtil 是 Android 提供的一個實用工具類別,用於高效更新 RecyclerView 的內容。它能夠比較兩個列表,計算出新增、刪除、修改等差異,並將這些變化高效地應用到 RecyclerView 中,而不需要完全重新刷新整個列表。
:::spoiler 詳細作法
- `areItemsTheSame()`:檢查收到的oldItem和newItem是否參考同一個項目(通常根據 ID)
- `areContentsTheSame()`:檢查兩個物件有沒有相同的內容,當`areItemsTheSame()`是true時,才會檢查`areContentsTheSame()`
- `ListAdapter`: 用於簡化和優化 RecyclerView 的數據處理過程。內部整合了 DiffUtil 的功能
- `getItem(position)`: 是一個 ListAdapter 提供的內建方法,用於從內部管理的數據集中,透過索引值獲取對應的資料項目。
- `submitList()`: 是 ListAdapter 提供的一個方法,用於將新的資料列表提交給 ListAdapter,並觸發 RecyclerView 的更新。
:::spoiler <u>步驟1.設計一個DataClass</u>
```kotlin=
data class TaskDataClass(
val taskID: Long = 0,
var taskName: String = "",
var taskDone: Boolean = false
)
```
:::
:::spoiler <u>步驟2.設計一個`DiffCallback`Class</u>
要繼承`DiffUtil.ItemCallback<xxxDataClass>()`,`<>`內填入要用的dataClass,並複寫`areItemsTheSame()`、`areContentsTheSame()`。</br>`areItemsTheSame()`通常是檢查ID是否相同,`areContentsTheSame()`則是檢查內容是否相同。[參考](https://ithelp.ithome.com.tw/articles/10261005#:~:text=ItemCallback%E7%9A%84class-,DiffUtil%E5%AF%A6%E4%BD%9C,-%E6%96%B0%E5%A2%9EDiffCallbackclass)
```kotlin=
class TaskDiffCallback: DiffUtil.ItemCallback<TaskDataClass>() {
override fun areItemsTheSame(oldItem: TaskDataClass, newItem: TaskDataClass): Boolean {
return oldItem.taskID == newItem.taskID
}
override fun areContentsTheSame(oldItem: TaskDataClass, newItem: TaskDataClass): Boolean {
return oldItem == newItem
}
}
```
:::
:::spoiler <u>步驟3.將原本的RecyclerViewAdapter改成ListAdapter,並變更內容</u>
RecyclerViewAdapter中有四處需要變更
- 1.ListAdapter的泛型標記中填入要使用的DataClass和ViewHolder類型。[參考](https://ithelp.ithome.com.tw/articles/10261005#:~:text=DataClass%E4%BD%9C%E7%AF%84%E4%BE%8B)</br>例如: </br>
- 原本: `RecyclerView.Adapter<RecyclerView.ViewHolder>()`
- 改成:`ListAdapter<TaskDataClass, RecyclerView.ViewHolder>(TaskDiffCallback())`
- 2.將原本使用資料集`currentTaskList = listOf<TaskDataClass>()`的地方改成直接使用`getItem(position)`。[參考](https://ithelp.ithome.com.tw/articles/10261005#:~:text=%E6%98%AF%E5%90%A6%E5%AE%8C%E5%85%A8%E7%9B%B8%E7%AD%89%E3%80%82-,%E6%9B%B4%E6%96%B0ListAdapter%E4%B8%AD%E7%9A%84data,-%E4%BB%A5%E5%BE%80%E5%8F%96%E5%80%BC)
- 例如,原本是`holder.bind_1(currentTaskList[position])`改成`holder.bind_1(getItem(position))`
- 3.將`getItemCount()`註解掉,ListAdapter 已內建實作</u>
- 4.將原本用來更新RecyclerView的方法也註解掉,ListAdapter已內建能夠替代的`submitList()`
```kotlin=
//原本用來更新RecyclerView的方法,將這段註解掉
fun submitNewTask(newTaskList: List<TaskDataClass>){
currentTaskList = newTaskList
notifyDataSetChanged()
}
```



:::
:::spoiler <u>步驟4.更改在Activity/Fragment中,用來更新RecyclerView的方法</u>
原本我們呼叫的是RecyclerViewAdapter中我們自己設計的`submitNewTask()`,現在改成使用ListAdapter中內建的`submitList()`。[參考](https://ithelp.ithome.com.tw/articles/10261005#:~:text=name%20%3D%20getItem(position)%0A%20%20%20%20%7D-,Data%E5%9C%A8ListAdapter%E7%9A%84%E4%BD%BF%E7%94%A8,-%E5%9C%A8ListAdapter%E4%B8%AD)
```kotlin=
//Fragment中原本的
viewModel.tasks.observe(viewLifecycleOwner){ tasks ->
recyclerViewadapter.submitNewTask(tasks)
}
//改用ListAdapter中內建的`submitList()`
viewModel.tasks.observe(viewLifecycleOwner){ tasks ->
recyclerViewadapter.submitList(tasks)
}
```
:::
>[!Note]參考: [[Day4] Android - Kotlin筆記:RecyclerView Adapter - ListAdapter + DiffUtil](https://ithelp.ithome.com.tw/articles/10261005)
:::
---
Q&A.<span id="portalA">為甚麼要在`onBindViewHolder`中加入`holder is xxxViewholder`的檢查?</span>
---
Ans: 這是為了方便未來如果要同時使用不同的`itemView`時,能夠更方便的擴充。[參考: ▼8.根據不同的狀況採用不同的ItemView](https://hackmd.io/ZxHCAtWdSYS_ZeFBMLF2iA?view=&stext=4645%3A23%3A0%3A1737197109%3ARwj1yj)。
如果確定只使用一個itemView的話,可以直接將Adapter中所有用到`RecyclerView.Holder`的地方都改成要使用的類別名
:::spoiler 圖解

:::
---
上一篇:[Android筆記–RecyclerView(循環視圖)(基礎篇)](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/Android_RecyclerView)
---
參考資料:
---
- [AndroidDeveloper--使用資訊卡顯示圖像清單
](https://developer.android.com/codelabs/basic-android-kotlin-training-display-list-cards?hl=zh-tw#0)
- [AndroidDeveloper--載入並顯示網際網路上的圖片]([https://](https://developer.android.com/codelabs/basic-android-kotlin-training-internet-images?hl=zh-tw#0))
- [[Google Course] Android Basics in Kotlin(第7篇) — Display the image and text with MaterialCardView](https://happyphoebe40090.medium.com/google-course-android-basics-in-kotlin-%E7%AC%AC7%E7%AF%87-display-the-image-and-text-with-materialcardview-317496863e1e)
- [AndroidDeveloper-- 自訂動態清單](https://developer.android.com/develop/ui/views/layout/recyclerview-custom?hl=zh-tw)
- [[Android 十全大補] RecyclerView as a Pro](https://ithelp.ithome.com.tw/articles/10220712)
- [[Day 9] Android in Kotlin: 簡單的 Recycler View](https://ithelp.ithome.com.tw/articles/10241713?sc=rss.iron)
- [Android Kotlin 實作 Day 6 : ImageList(RecyclerView + LayoutInflater)](https://ithelp.ithome.com.tw/articles/10203735)
- :::spoiler Android Kotlin開發 -小嫩雞的30篇精選筆記系列
- [Android x Kotlin : RecyclerView(二)-項目的拖曳換位及左右滑動刪除](https://ithelp.ithome.com.tw/articles/10239304)
- [Android x Kotlin : Recyclerview(三)-能上下滑又能左右滑的巢狀玩法](https://ithelp.ithome.com.tw/articles/10240046)
- :::spoiler HTK線上教室--RecyclerView實務系列
- [Day 13:RecyclerView 基本資料列表顯示](https://ithelp.ithome.com.tw/articles/10263176?sc=rss.iron)
- [Day 14:RecyclerView 進階項目佈局](https://ithelp.ithome.com.tw/articles/10263656)
- [Day 15:RecyclerView 卡片式項目佈局](https://ithelp.ithome.com.tw/articles/10264141)
- [Day 16:RecyclerView 跳頁&資料傳遞(1)](https://ithelp.ithome.com.tw/articles/10264909)
- [Day 17:RecyclerView 跳頁&資料傳遞(2)](https://ithelp.ithome.com.tw/articles/10265804)
- :::spoiler 碼農日常-進階RecyclerView 系列<JAVA>
- [碼農日常-『Android studio』基本RecyclerView用法](https://thumbb13555.pixnet.net/blog/post/311803031)
- [碼農日常-『Android studio』基本RecyclerView 用法-2 基本版下拉更新以及點擊事件](https://thumbb13555.pixnet.net/blog/post/312844960-android-studio-%e4%b9%8b%e5%9f%ba%e6%9c%acre)
- [碼農日常-『Android studio』基本RecyclerView 用法-3 RecyclerView上下滑動排序與側滑刪除(RecyclerView Swipe)](https://thumbb13555.pixnet.net/blog/post/316420566-recyclerview-swipe)
- [碼農日常-『Android studio』基本RecyclerView 用法-4 左滑顯示Button Menu](https://thumbb13555.pixnet.net/blog/post/322876604-swipereveallayout)
- [碼農日常-『Android studio』進階RecyclerView 用法-5 RecyclerView item混合介面](https://thumbb13555.pixnet.net/blog/post/324929799-recyclerview_multipl)
:::