---
# System prepended metadata

title: Android 筆記 -- RecyclerView (循環視圖)(基礎篇)
tags: [onCreatViewHolder, onBindViewHolder, android, recycle, adapter, Manager, layoutManager, ViewHolder, GridLayoutManager, holder, "StaggeredGridLayoutManager\_", LinearLayoutManager, Layout, RecyclerView, getItemCount, view, 基礎]

---

<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>&lt; <u>1. layoutManager</u> &gt;</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)
>![1735900515757](https://hackmd.io/_uploads/SkgjCEB8Je.gif =40%x)

---
- 支援水平或垂直方向的列表顯示。
- 適合顯示單列或單行的數據列表。
```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)
>![1735900690464](https://hackmd.io/_uploads/BksjAVr8yx.gif =40%x)

---
- 每行或每列可以容納多個項目。
- 可以針對特定項目設置跨行或跨列的效果。
- 如果網格是垂直排列的，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)
>![1735900761759](https://hackmd.io/_uploads/rkv204HIyl.gif =40%x)

---
- 元素大小不必相同，呈現「交錯」效果。
- 適合展示帶有圖片的內容
- <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.
  ![image](https://hackmd.io/_uploads/H1sBjjWdex.png)

- 方案2. ==除了寫在Activity或Fragment外，也可以寫在xml中==
  ![image](https://hackmd.io/_uploads/SkJfCsbOgg.png)
    
</div>
</details>
</div>

>[!Tip]更多項目可參考[LayoutManager](https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.LayoutManager?hl=zh-tw)

---
<h3><b>&lt; <u>2. ViewHolder</u> &gt;</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>&lt; <u>透過儲存引用，可以達到下列三點</u> &gt;</b></font>
- 保存對 [ItemLayout](#4.兩個Layout佈局文件) 內子視圖的引用，避免反覆使用 findViewById() 查找，提升效能。
- 負責將數據綁定到 [ItemLayout](#4.兩個Layout佈局文件)，讓每個項目正確顯示內容。
- 支援 RecyclerView 的複用機制，減少資源浪費。
    
</div>
</details>
<details id="Holder的基本架構"><summary>Holder 的基本架構</summary>
<div>
<br>

>![image](https://hackmd.io/_uploads/ByroVyX_xl.png)

---
```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>&lt; <u>推薦使用這種的架構，原因如下</u> &gt;</b></font>
- 封裝性：
  - ViewHolder 應該負責管理自己的視圖和事件處理
  - 所有和視圖互動的邏輯都集中在一個地方
  - 更好維護和測試
- 清晰的職責劃分：
  - <code>onBindViewHolder()</code> 主要負責協調數據和 ViewHolder 的關係
  - <code>bind()</code> 負責具體的視圖更新和事件處理邏輯

---

![image](https://hackmd.io/_uploads/H1XU_y7Oeg.png)
```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>&lt; <u>3.Adapter</u> &gt;</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>&lt; <u>4.兩個Layout佈局文件</u> &gt;</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>

![image](https://hackmd.io/_uploads/Hk9xkZ7uel.png)

---
```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>

![image](https://hackmd.io/_uploads/S1aoy-7_lg.png)

---
```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>&lt; <u>RecyclerView 的工作原理</u> &gt;</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>&lt; <u>RecyclerView 的工作流程</u> &gt;</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之中
```
:::
