<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之中 ``` :::