<h1>Android 筆記 -- RecyclerView (循環視圖)(基礎篇)</h1>
>前置閱讀: 本篇會有很多用到[DataBinding](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/android_dataBinding)、[VIewBinding](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/android_viewBinding)的地方
RecyclerView 是 Android 中用來顯示大量資料的高效元件,具備高性能滾動與視圖重用機制。透過 ViewHolder,離開螢幕的項目會被回收再利用,節省記憶體並提升效能。
<div style="border-left: 4px solid #43A047;background-color: #183524;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;">
<font size=4><u>主要特色包括:</u></font>
- 支援多種佈局(如線性、網格、瀑布流)
- 可自訂項目樣式與動畫效果
- 可顯示多種類型資料
</div>
---
▼ RecyclerView三大主要部分及兩附件
---
layoutManager、ViewHolder、Adapter,以及兩個Layout佈局文件
---
<h3><b>< <u>1. layoutManager</u> ></b></h3>
==layoutManager 負責設定 RecyclerView 的滾動、排列方式==
<div style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;">
<details><summary>RecyclerView 提供的三個版面配置管理工具</summary>
<div>
<div style="border-left: 4px solid #43A047;background-color: #183524;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;">
<details><summary>1. <code>LinearLayoutManager</code>: 會在一個維度清單中排列項目,用於實現線性佈局。</summary>
<div>
<br>
>Android Developers: [LinearLayoutManager](https://developer.android.com/reference/androidx/recyclerview/widget/LinearLayoutManager?hl=zh-tw)
>
---
- 支援水平或垂直方向的列表顯示。
- 適合顯示單列或單行的數據列表。
```kotlin=
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this) // 預設垂直
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false) // 設置為水平
```
</div>
</details>
<details><summary>2. <code>GridLayoutManager</code>: 會在二維格線中排列項目,用於實現網格佈局。</summary>
<div>
<br>
>Android Developers: [GridLayoutManager](https://developer.android.com/reference/androidx/recyclerview/widget/GridLayoutManager?hl=zh-tw)
>
---
- 每行或每列可以容納多個項目。
- 可以針對特定項目設置跨行或跨列的效果。
- 如果網格是垂直排列的,GridLayoutManager 會嘗試讓每個資料列中的所有元素具有相同的寬度和高度,但不同的資料列高度不同。
- 如果格線已水平排列,GridLayoutManager 會嘗試讓每個資料欄的所有元素具有相同的寬度和高度,但不同資料欄寬度不同。
- <code>setSpanCount(spanCount: Int)</code>: 設置行或列的數量。
- <code>setSpanSizeLookup(object: SpanSizeLookup)</code>: 針對某些項目自定義跨行或跨列數量。
```kotlin=
//範例
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager = GridLayoutManager(this, 3) // 每行 3 個項目
```
</div>
</details>
<details><summary>3. <code>StaggeredGridLayoutManager</code>: 類似於 <code>GridLayoutManager</code>,但允許每個項目的寬高可以不一致,形成不規則的外觀,用於在 RecyclerView 中實現交錯式網格佈局。</summary>
<div>
<br>
>Android Developers: [StaggeredGridLayoutManager](https://developer.android.com/reference/androidx/recyclerview/widget/StaggeredGridLayoutManager?hl=zh-tw)
>
---
- 元素大小不必相同,呈現「交錯」效果。
- 適合展示帶有圖片的內容
- <code>setSpanCount(spanCount: Int)</code>: 設置列數(垂直方向)或行數(水平方向)
- <code>setGapStrategy(gapStrategy: Int)</code>: 設置空隙填充策略。例如: <code>GAP_HANDLING_NONE</code> 或 <code>GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS</code>
```kotlin=
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL) // 2 列
```
</div>
</details>
</div>
</div>
</details>
<details><summary>一些 RecycleView 的其他可用設定</summary>
<div>
<br>
>參考: [Day 13:RecyclerView 基本資料列表顯示--RecyleView 更多設定](https://ithelp.ithome.com.tw/articles/10263176?sc=rss.iron)
---
- 添加項目分隔線(ItemDecoration):
```kotlin=
recyclerView.addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
```
- 設定點擊事件監聽器(ClickListener):
```kotlin=
adapter.setOnItemClickListener { position ->
// 處理點擊事件
}
```
- 設定滾動監聽器(ScrollListener):
```kotlin=
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
// 在此處理滾動事件
}
}
```
- 設定項目動畫(Item Animation):
```kotlin=
recyclerView.itemAnimator = DefaultItemAnimator()
// 或者其他自定义的項目動畫
```
- 設定項目拖動和滑動(ItemTouchHelper):
```kotlin=
val itemTouchHelper = ItemTouchHelper(callback)
itemTouchHelper.attachToRecyclerView(recyclerView)
```
</div>
</details>
<br>
<details><summary>layoutManager 的基本範例</summary>
<div>
<br>
- 方案1.

- 方案2. ==除了寫在Activity或Fragment外,也可以寫在xml中==

</div>
</details>
</div>
>[!Tip]更多項目可參考[LayoutManager](https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.LayoutManager?hl=zh-tw)
---
<h3><b>< <u>2. ViewHolder</u> ></b></h3>
>使用 <code>ViewHolder</code> 需繼承 <code>RecyclerView.ViewHolder(view)</code>
ViewHolder 負責將 [ItemLayout](#4.兩個Layout佈局文件) 和 RecyclerAdapter 中對應的參數綁定起來,並且儲存對 [ItemLayout](#4.兩個Layout佈局文件) 的引用。
<div style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;">
<details><summary><code>ViewHolder</code> 的運作原理</summary>
<div>
<br>
- 當 <code>RecyclerView</code> 需要顯示新項目時,<code>Adapter</code> 會在 <code>onCreateViewHolder()</code> 中建立 <code>ViewHolder</code>,並重複使用其中的資料與視圖參考。
- 我們可以粗略將 <code>Holder</code> 理解為一個儲存 <code>View 參照</code> 的地方,也可以把它當成一個儲存 View 的 class。
- 通常我們會將 <code>ViewHolder</code> 寫成 <code>inner Class</code>。
<font size=3><b>< <u>透過儲存引用,可以達到下列三點</u> ></b></font>
- 保存對 [ItemLayout](#4.兩個Layout佈局文件) 內子視圖的引用,避免反覆使用 findViewById() 查找,提升效能。
- 負責將數據綁定到 [ItemLayout](#4.兩個Layout佈局文件),讓每個項目正確顯示內容。
- 支援 RecyclerView 的複用機制,減少資源浪費。
</div>
</details>
<details id="Holder的基本架構"><summary>Holder 的基本架構</summary>
<div>
<br>
>
---
```kotlin=
inner class FruitRecyclerViewHolder(private val binding: FruitItemViewBinding): RecyclerView.ViewHolder(binding.root){
val fruitName = binding.tvNameValue
val fruitDescription = binding.tvDescriptionValue
val fruitPrice = binding.tvPriceValue
}
```
</div>
</details>
<br>
<details id="Holder的實際案例"><summary>Holder 的推薦做法</summary>
<div>
<br>
<font size=3><b>< <u>推薦使用這種的架構,原因如下</u> ></b></font>
- 封裝性:
- ViewHolder 應該負責管理自己的視圖和事件處理
- 所有和視圖互動的邏輯都集中在一個地方
- 更好維護和測試
- 清晰的職責劃分:
- <code>onBindViewHolder()</code> 主要負責協調數據和 ViewHolder 的關係
- <code>bind()</code> 負責具體的視圖更新和事件處理邏輯
---

```kotlin=
// 改進版的 RecyclerView Adapter - 展示良好的封裝設計
class FruitRecyclerViewAdapter_2(private val fruitList: List<FruitItemDataClass>): RecyclerView.Adapter<FruitRecyclerViewAdapter_2.FruitRecyclerViewHolder2>() {
inner class FruitRecyclerViewHolder2(private val binding: FruitItemViewBinding): RecyclerView.ViewHolder(binding.root){
// 將 View 元件改為 private,提升封裝性
// 原本:val fruitName = binding.tvNameValue (public,外部可直接存取)
// 現在:private val fruitName = binding.tvNameValue (private,只有內部可存取)
private val fruitName = binding.tvNameValue
private val fruitDescription = binding.tvDescriptionValue
private val fruitPrice = binding.tvPriceValue
// 新增 bind() 方法,實現職責分離
// 目的:讓 ViewHolder 負責自己的數據綁定邏輯
// 好處:Adapter 不需要知道 ViewHolder 內部如何處理數據
fun bind(item: FruitItemDataClass) {
fruitName.text = item.name
fruitDescription.text = item.description
fruitPrice.text = item.price.toString()
// 未來擴展點:可以在這裡加入更多邏輯而不影響 Adapter
// 例如:點擊事件、格式化、條件顯示等
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitRecyclerViewHolder2 {
val inflater = LayoutInflater.from(parent.context)
val binding = FruitItemViewBinding.inflate(inflater, parent, false)
return FruitRecyclerViewHolder2(binding)
}
override fun onBindViewHolder(holder: FruitRecyclerViewHolder2, position: Int) {
// 簡化 onBindViewHolder 邏輯
// 原本:直接操作 holder 的各個 View 元件 (違反封裝)
// 現在:只需調用 holder.bind() 方法 (符合封裝原則)
val fruitItem = fruitList[position]
holder.bind(fruitItem) // 委託給 ViewHolder 處理,職責更清晰
}
override fun getItemCount(): Int {
return fruitList.size
}
}
```
</div>
</details>
</div>
---
<h3><b>< <u>3.Adapter</u> ></b></h3>
>Adapter 必須覆寫 <code>onCreateViewHolder()</code>、<code>onBindViewHolder</code>、<code>getItemCount()</code>繼承 <code>RecyclerView.Adapter<yourViewHolder></code>
Adapter 負責管理數據邏輯,並將==資料與 <code>RecyclerView</code> 中的項目進行關聯,控制列表項目如何顯示==。
<div style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;">
<details><summary><code>onCreateViewHolder(parent:ViewGroup, viewType:Int): ViewHolder</code>: 負責創建及初始化 ViewHolder 及其相關聯的 View,最終須返回一個處理過的 ViewHolder</summary>
<div>
<br>
>實作要點:創建 View → 初始化 ViewHolder → 返回 ViewHolder
```kotlin=
//在這裡創建及初始化 ViewHolder,須返回一個自定義的 ViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitRecyclerViewHolder2 {
//使用 LayoutInflater 將項目佈局 XML 文件轉換為對應的 View。
val inflater = LayoutInflater.from(parent.context)
val binding = FruitItemViewBinding.inflate(inflater, parent, false)
return FruitRecyclerViewHolder2(binding)
}
```
- <code>parent: ViewGroup</code>: 傳入的 ViewGroup 是 RecyclerView 自己,用於提供上下文或佈局參數。
- <code>viewType: Int</code>:通過覆寫 <code>getItemViewType()</code> 返回的值,決定當前項目的佈局類型(用於多佈局)。
- <code>return ViewHolder</code>: 必須返回一個自訂的 ViewHolder,該 ViewHolder 包含了創建的視圖。
<div style="border-left: 4px solid #43A047;background-color: #183524;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;">
<details><summary>運作原理</summary>
<div>
<br>
- RecyclerView 需要新項目時調用此方法創建 ViewHolder。
- 創建的 ViewHolder 傳遞給 <code>onBindViewHolder()</code> 進行數據綁定
- 使用完的 ViewHolder 進入重複利用池,避免重複創建er](#ViewHolder)的建構子進行初始化,最後返回這個 ViewHolder。
</div>
</details>
</div>
</div>
</details>
<details id="getItemCount()"><summary><code>getItemCount():Int</code>: 負責回資料集的總數量,告知(返回) RecyclerView 需要顯示多少個項目。</summary>
<div>
<br>
>RecyclerView 根據 <code>getItemCount()</code> 返回的數值決定調用 <code>onBindViewHolder()</code> 的次數
```kotlin=
//在這裡將資料集的大小傳遞給RecyclerView
override fun getItemCount(): Int {
return fruitList.size
}
```
</div>
</details>
<details><summary><code>onBindViewHolder(viewHolder: ViewHolder, position: Int)</code>: 負責將資料集裡對應的資料匹配到 ViewHolder 上對應的位置,並更新視圖內容以反映數據的狀態。</summary>
<div>
<br>
>- 此方法會將數據集合中對應位置的數據綁定到 ViewHolder 中的視圖。
>- <code>onBindViewHolder()</code> 是每一個 [Item](#4.兩個Layout佈局文件) 在建立的時候會跑的方法,也就是你的資料有多少筆,他就會跑幾次。
```kotlin=
override fun onBindViewHolder(holder: FruitRecyclerViewHolder, position: Int) {
holder.fruitName.text = fruitList[position].name
holder.fruitDescription.text = fruitList[position].description
holder.fruitPrice.text = fruitList[position].price.toString()
}
```
- <code>viewHolder: ViewHolder</code>: 當前項目的 ViewHolder,包含視圖引用
- <code>position: Int</code>: 資料集中的索引位置,範圍為「0 到 <code>getItemCount() - 1</code>」
<details style="border-left: 4px solid #43A047;background-color: #183524;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;"><summary>其他 <code>onBindViewHolder()</code> 中常用的方法</summary>
<div>
<br>
- <code>notifyItemRemoved(position)</code>: 通知 <code>RecyclerView</code>,從指定位置 <code>position</code> 刪除了一個項目。這會觸發 <code>RecyclerView</code> 的動畫,將該項目移除,並將該位置之後的所有項目位置向前移動一位。
- <code>notifyItemRangeChanged(position, itemCount - position)</code>: 通知 <code>RecyclerView</code> 在 <code>positionStart</code> 和 <code>positionStart + itemCount - 1</code> 之間的項目已經改變。
- <code>notifyDataSetChanged()</code>: 通知 <code>RecyclerView</code> 要強制刷新。
</div>
</details>
</div>
</details>
</div>
---
<h3 id="4.兩個Layout佈局文件"><b>< <u>4.兩個Layout佈局文件</u> ></b></h3>
使用 RecycleView 會需要用到至少兩個 Layout 佈局文件來呈現。<code>Container Layout</code> 和 <code>ItemLayout</code>。
<div style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;">
<details><summary><code>Container Layout</code>: 定義 <code>RecyclerView</code> 在畫面中的位置和整體呈現,也就是放了 <code>RecyclerView 元件</code>的 Layout。</summary>
<div>
<br>

---
```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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_top"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.12" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.97" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.05" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.95" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/materialCardView"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardBackgroundColor="#1B00BCD4"
app:cardCornerRadius="8dp"
app:contentPadding="2dp"
app:layout_constraintBottom_toBottomOf="@id/guideline_bottom"
app:layout_constraintEnd_toEndOf="@id/guideline_end"
app:layout_constraintStart_toStartOf="@id/guideline_start"
app:layout_constraintTop_toTopOf="@id/guideline_top">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_fruit_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
/>
</com.google.android.material.card.MaterialCardView>
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoSizeTextType="uniform"
android:text="RecyclerView示範"
android:textSize="34sp"
app:layout_constraintBottom_toTopOf="@+id/guideline_top"
app:layout_constraintEnd_toStartOf="@+id/guideline_end"
app:layout_constraintStart_toStartOf="@+id/guideline_start"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
```
</div>
</details>
<details><summary><code>ItemLayout</code>: 負責表示單一一個 Item 的呈現方式。</summary>
<div>
<br>

---
```xml=
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="8dp"
app:contentPadding="4dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- 名稱 -->
<TextView
android:id="@+id/tv_name_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="12dp"
android:text="名稱:"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_name_value"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:text="@{item.name}"
app:layout_constraintBottom_toBottomOf="@+id/tv_name_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/tv_name_label"
app:layout_constraintTop_toTopOf="@id/tv_name_label" />
<!-- 描述 -->
<TextView
android:id="@+id/tv_description_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="8dp"
android:text="描述:"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_name_label" />
<TextView
android:id="@+id/tv_description_value"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:text="@{item.description}"
app:layout_constraintBottom_toBottomOf="@+id/tv_description_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/tv_description_label"
app:layout_constraintTop_toTopOf="@id/tv_description_label" />
<!-- 價格 -->
<TextView
android:id="@+id/tv_price_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="12dp"
android:text="價格:"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_description_label" />
<TextView
android:id="@+id/tv_price_value"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:text='@{String.valueOf(item.price) + " 元"}'
app:layout_constraintBottom_toBottomOf="@+id/tv_price_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/tv_price_label"
app:layout_constraintTop_toTopOf="@id/tv_price_label" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
```
</div>
</details>
</div>
---
▼ RecyclerView 製作步驟總結
---
<details><summary>1. 準備 Layout 文件</summary>
<div>
<br>
- Container Layout: 包含 RecyclerView 及其他 UI 元件的主要佈局
- Item Layout: 定義單個項目的視圖結構
</div>
</details>
<details><summary>2. 建立數據類別</summary>
<div>
<br>
創建用於存儲每個項目數據的 Data Class
</div>
</details>
<details><summary>3. 實作 Adapter</summary>
<div>
<br>
- 繼承 RecyclerView.Adapter<ViewHolder>,實作三個核心方法:
- <code>onCreateViewHolder()</code>: 創建及初始化 ViewHolder
- <code>onBindViewHolder()</code>: 將數據綁定到 ViewHolder
- <code>getItemCount()</code>: 返回數據集總數量
</div>
</details>
<details><summary>4. 設計 ViewHolder</summary>
<div>
<br>
- 繼承 <code>RecyclerView.ViewHolder</code>
- 包含 <code>bind()</code> 方法處理數據綁定(推薦做法)
- 將 View 元件設為 private 提升封裝性
</div>
</details>
<details><summary>5. Activity / Fragment 設置</summary>
<div>
<br>
- 初始化 RecyclerView
- 設置 LayoutManager(LinearLayoutManager、GridLayoutManager 等)
- 綁定 Adapter
- 準備數據並傳遞給 Adapter
</div>
</details>
<br>
<details><summary>舊資料</summary>
<div>
<br>
---
▼ RecyclerView 製作步驟總結:
1.決定清單或格線的外觀。一般來說,您可以使用 RecyclerView 程式庫的標準版面配置管理工具。
2.設計清單中的每個元素外觀和行為。根據這個設計,擴充 ViewHolder 類別。您的 ViewHolder 版本提供清單項目的所有功能。檢視容器是 View 的包裝函式,而該檢視畫面由 RecyclerView 管理。
3.定義將資料與 ViewHolder 檢視畫面相關聯的 Adapter。
<iframe width="560" height="315" src="https://www.youtube.com/embed/5eXDba32rkI?si=67wviZfwRWsu3yg0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
:::
## ▼一些RecyclerView常用的方法
:::success
- 1.`recyclerView.adapter.notifyDataSetChanged()`: 通知適配器 (adapter) 整個資料集已經發生變化,並且需要重新繪製所有的項目。
:::info
:::spoiler 其他類似的方法
- `notifyItemInserted(position)`:當插入了一個新項目時通知。
- `notifyItemRemoved(position)`:當移除了某個項目時通知。
- `notifyItemChanged(position)`:當某個特定項目變化時通知。
- `notifyItemRangeChanged(positionStart, itemCount)`:當一個範圍內的項目變化時通知。
:::
- 2.`recyclerView.smoothScrollToPosition(position: Int)`: 是一個用來將 RecyclerView平滑的捲動到指定項目位置的方法(有動畫)。position是你希望捲動到的項目索引,這個索引起點為0。
:::info
:::spoiler 其他類似的方法
- `scrollToPosition`: 立即捲動到指定項目,沒有動畫過渡效果。
:::
</div>
</details>
---
RecyclerView的介紹影片(CC字幕)
---
<font size=4><b>< <u>RecyclerView 的工作原理</u> ></b></font>
<iframe width="560" height="315" src="https://www.youtube.com/embed/xjfBzI9qlwk?si=OfpAboDxaZBc_089" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
<font size=4><b>< <u>RecyclerView 的工作流程</u> ></b></font>
<iframe width="560" height="315" src="https://www.youtube.com/embed/ns3WC8HFx90?si=xDRXC7ieok205NLy" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
---
下一篇: [Android筆記–RecyclerView(循環視圖)(進階篇)](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/android_RecyclerView_advance)
---
GitHub 專案
---
[MyPratice_RecyclerViewSample](https://github.com/PudCheetah/MyPratice_RecyclerViewSample): 乾淨的 RecyclerView 示範
[Task_13](https://github.com/PudCheetah/Task_13): RecyclerView 和 DataBinding 運用練習,示範標準的 RecyclerView
---
參考資料:
---
- [[Google Course] Android Basics in Kotlin(第6篇) — Display a scrollable list with RecyclerView](https://happyphoebe40090.medium.com/google-course-android-basics-in-kotlin-%E7%AC%AC6%E7%AF%87-display-a-scrollable-list-4e1fa4d3ef87)
- ==★==[AndroidDeveloper--使用 RecyclerView 建立動態清單](https://developer.android.com/develop/ui/views/layout/recyclerview?hl=zh-tw)
- ==★==[[Android 十全大補] RecyclerView](https://ithelp.ithome.com.tw/articles/10220196)
- [碼農日常-『Android studio』基本RecyclerView用法](https://thumbb13555.pixnet.net/blog/post/311803031)
- [Android -在ViewHolder中使用ViewBinding來優化建構子](https://medium.com/@Soda3752/android-%E5%9C%A8viewholder%E4%B8%AD%E4%BD%BF%E7%94%A8viewbinding%E4%BE%86%E5%84%AA%E5%8C%96%E5%BB%BA%E6%A7%8B%E5%AD%90-c16faa8cd0d2)
- ==★==[Day 13:RecyclerView 基本資料列表顯示--RecyleView 更多設定](https://ithelp.ithome.com.tw/articles/10263176?sc=rss.iron)
- ==★==[[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)
- ==★★★==[Android x Kotlin : RecyclerView(一)-嬰兒式基本用法速覽](https://ithelp.ithome.com.tw/articles/10238539)
舊資料
---
▼ 完整的MainActivity
:::danger
:::spoiler ※===完整的MainActivity1.0===※
```kotlin=
class MainActivity : AppCompatActivity() {
private lateinit var bindingActivityMain: ActivityMainBinding
private lateinit var bindingRowFunctionBinding: RowFunctionBinding
var functions = listOf<String>(
"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q",
"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q",
"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q",
"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q",
"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q",
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bindingActivityMain = ActivityMainBinding.inflate(layoutInflater)
setContentView(bindingActivityMain.root)
bindingActivityMain.recyclerView1.layoutManager = LinearLayoutManager(this)
// bindingActivityMain.recyclerViewMain1.setHasFixedSize(true)
bindingActivityMain.recyclerView1.adapter = FunctionAdapter()
}
inner class FunctionAdapter(): RecyclerView.Adapter<FunctionHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FunctionHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.row_function, parent, false)
// var bindingRowFunctionBinding = RowFunctionBinding.inflate(layoutInflater)
var bindingRowFunctionBinding = RowFunctionBinding.bind(view)
return FunctionHolder(bindingRowFunctionBinding)
}
override fun getItemCount(): Int {
return functions.size
}
override fun onBindViewHolder(holder: FunctionHolder, position: Int) {
holder.nameText.text = functions.get(position)
}
}
class FunctionHolder(var bindingRowFunctionBinding: RowFunctionBinding): RecyclerView.ViewHolder(bindingRowFunctionBinding.root){
var nameText: TextView = bindingRowFunctionBinding.name
}
}
```
:::
:::danger
:::spoiler ※===完整的MainActivity2.0===※
```kotlin=
class MainActivity : AppCompatActivity() {
private lateinit var bindingMainBinding: ActivityMainBinding
private lateinit var bindingRowFunctionBinding: RowFunctionBinding
var functions = listOf<String>(
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bindingMainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(bindingMainBinding.root)
bindingMainBinding.recyclerView1MainActivity.layoutManager = LinearLayoutManager(this)
bindingMainBinding.recyclerView1MainActivity.setHasFixedSize(true)
bindingMainBinding.recyclerView1MainActivity.adapter = FunctionAdapter(functions)
}
class FunctionAdapter(var recyclerViewList: List<String>): RecyclerView.Adapter<FunctionAdapter.FunctionHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FunctionHolder {
val bindingRowFunctionBinding = RowFunctionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
// val view = LayoutInflater.from(parent.context).inflate(R.layout.row_function, parent, false)
// var bindingRowFunctionBinding = RowFunctionBinding.bind(view)
return FunctionHolder(bindingRowFunctionBinding)
}
override fun getItemCount(): Int {
return recyclerViewList.size
}
override fun onBindViewHolder(holder: FunctionHolder, position: Int) {
holder.textView.text = recyclerViewList.get(position)
}
inner class FunctionHolder(bindingRowFunctionBinding: RowFunctionBinding): RecyclerView.ViewHolder(bindingRowFunctionBinding.root){
var textView: TextView = bindingRowFunctionBinding.textView1RowFunction
}
}
// class FunctionHolder(var bindingRowFunctionBinding: RowFunctionBinding): RecyclerView.ViewHolder(bindingRowFunctionBinding.root){
// var textView: TextView = bindingRowFunctionBinding.textView1RowFunction
// }
}
//具體變更:
//更改了onCreatViewHolder()中視圖綁定的方式
//將Holder()改為inner class放入Adapter中
//將Adapter由inner class改為普通class,並將串列作為參數傳入Adapter之中
```
:::