# Android筆記--RecyclerView(循環視圖)(基礎篇) >[!Note]前置閱讀: >本篇會有很多用到[DataBinding](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/android_dataBinding)、[VIewBinding](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/android_viewBinding)的地方 RecyclerView 是 Android 中用來顯示大量資料的高效元件,具備高性能滾動與視圖重用機制。透過 ViewHolder,離開螢幕的項目會被回收再利用,節省記憶體並提升效能。 <font size=4><u>主要特色包括:</u><font> - 支援多種佈局(如線性、網格、瀑布流) - 可自訂項目樣式與動畫效果 - 可顯示多種類型資料 --- # ▼ RecyclerView三大主要部分及兩附件 layoutManager、ViewHolder、Adapter,以及兩個Layout佈局文件 <u>**1.layoutManager**</u>: --- ==layoutManager負責設定RecyclerView的滾動、排列方式== :::spoiler <font size=4>**RecyclerView提供的三個版面配置管理工具**:</font> <font size=4>1.[LinearLayoutManager](https://developer.android.com/reference/androidx/recyclerview/widget/LinearLayoutManager?hl=zh-tw) 會在一個維度清單中排列項目,用於實現線性佈局。</font> --- ![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) // 設置為水平 ``` <font size=4>2.[GridLayoutManager](https://developer.android.com/reference/androidx/recyclerview/widget/GridLayoutManager?hl=zh-tw) 會在二維格線中排列項目,用於實現網格佈局。</font> --- ![1735900690464](https://hackmd.io/_uploads/BksjAVr8yx.gif =40%x) - 每行或每列可以容納多個項目。 - 可以針對特定項目設置跨行或跨列的效果。 - 如果網格是垂直排列的,GridLayoutManager 會嘗試讓每個資料列中的所有元素具有相同的寬度和高度,但不同的資料列高度不同。 - 如果格線已水平排列,GridLayoutManager 會嘗試讓每個資料欄的所有元素具有相同的寬度和高度,但不同資料欄寬度不同。 - `setSpanCount(spanCount: Int)`: 設置行或列的數量。 - `setSpanSizeLookup(object: SpanSizeLookup)`: 針對某些項目自定義跨行或跨列數量。 ```kotlin= //範例 val recyclerView = findViewById<RecyclerView>(R.id.recyclerView) recyclerView.layoutManager = GridLayoutManager(this, 3) // 每行 3 個項目 ``` <font size=4>3.[StaggeredGridLayoutManager](https://developer.android.com/reference/androidx/recyclerview/widget/StaggeredGridLayoutManager?hl=zh-tw)類似於`GridLayoutManager`,但允許每個項目的寬高可以不一致,形成不規則的外觀,用於在 RecyclerView 中實現交錯式網格佈局。</font> --- ![1735900761759](https://hackmd.io/_uploads/rkv204HIyl.gif =40%x) - 元素大小不必相同,呈現「交錯」效果。 - 適合展示帶有圖片的內容 - `setSpanCount(spanCount: Int)`: 設置列數(垂直方向)或行數(水平方向) - `setGapStrategy(gapStrategy: Int)`: 設置空隙填充策略。例如: `GAP_HANDLING_NONE`或`GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS` ```kotlin= val recyclerView = findViewById<RecyclerView>(R.id.recyclerView) recyclerView.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL) // 2 列 ``` ::: :::spoiler ※===layoutManager的基本範例===※ - 方案1. ![image](https://hackmd.io/_uploads/H1sBjjWdex.png) - 方案2.==除了寫在Activity或Fragment外,也可以寫在xml中== ![image](https://hackmd.io/_uploads/SkJfCsbOgg.png) ::: :::spoiler ※===一些RecycleView的其他可用設定===※ 參考: ==★==[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) ``` ::: >[!Tip]更多項目可參考[LayoutManager](https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.LayoutManager?hl=zh-tw) <span id="ViewHolder"><u>**2.ViewHolder**</u></span>: --- >[!Important]使用ViewHolder需繼承RecyclerView.ViewHolder(view) ViewHolder==負責將==[ItemLayout](#4.兩個Layout佈局文件)==和RecyclerAdapter中對應的參數綁定起來==,並且儲存對[ItemLayout](#4.兩個Layout佈局文件)的引用。 當RecyclerView 需要顯示新項目時,Adapter 會在 onCreateViewHolder() 中建立 ViewHolder,並重複使用其中的資料與視圖參考。 也就是說,我們可以粗略將Holder理解為一個儲存View參照(View reference)的地方,也可以把它當成一個儲存 View 的 class。 ==通常我們會將`ViewHolder`寫成`inner Class`==。 :::spoiler <font size=4><u>透過儲存引用,可以達到下列三點:</u></font> - 保存對 [ItemLayout](#4.兩個Layout佈局文件) 內子視圖的引用,避免反覆使用 findViewById() 查找,提升效能。 - 負責將數據綁定到 [ItemLayout](#4.兩個Layout佈局文件),讓每個項目正確顯示內容。 - 支援 RecyclerView 的複用機制,減少資源浪費。 ::: :::spoiler ※===<span id="Holder的基本架構">Holder的基本架構</span>===※ ![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 } ``` ::: :::spoiler ※===<span id="Holder的實際案例">Holder的推薦做法</span>===※ 推薦使用這種的架構,原因如下: - 封裝性: - ViewHolder 應該負責管理自己的視圖和事件處理 - 所有和視圖互動的邏輯都集中在一個地方 - 更好維護和測試 - 清晰的職責劃分: - onBindViewHolder() 主要負責協調數據和 ViewHolder 的關係 - bind() 負責具體的視圖更新和事件處理邏輯 ![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 } } ``` ::: <font size=5><u>**3.Adapter:**</u></font> --- Adapter負責管理數據邏輯,並將==資料與RecyclerView中的項目進行關聯,控制列表項目如何顯示==。 >[!Important]Adapter 必須覆寫`onCreateViewHolder()`、`onBindViewHolder`、`getItemCount()` 並繼承`RecyclerView.Adapter<yourViewHolder>` - :::spoiler <u>**`onCreateViewHolder(parent:ViewGroup, viewType:Int): ViewHolder`**</u>: 負責創建及初始化 ViewHolder 及其相關聯的 View,最終須返回一個處理過的[ViewHolder](#ViewHolder)。 <u>**運作原理**</u>: - RecyclerView 需要新項目時調用此方法創建 ViewHolder。 - 創建的 ViewHolder 傳遞給 onBindViewHolder() 進行數據綁定 - 使用完的 ViewHolder 進入重複利用池,避免重複創建er](#ViewHolder)的建構子進行初始化,最後返回這個 ViewHolder**。 - <u>**實作要點:創建 View → 初始化 ViewHolder → 返回 ViewHolder**</u> ```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) } ``` - parent:ViewGroup: 傳入的 ViewGroup 是 RecyclerView 自己,用於提供上下文或佈局參數。 - viewType:Int:通過覆寫 getItemViewType() 返回的值,決定當前項目的佈局類型(用於多佈局)。 - 返回值ViewHolder: 必須返回一個自訂的 ViewHolder,該 ViewHolder 包含了創建的視圖。 ::: <span id="getItemCount()"></span> - :::spoiler <u>**`getItemCount():Int`**</u>:負責回資料集的總數量,告知(返回) RecyclerView 需要顯示多少個項目。 RecyclerView 根據getItemCount()返回的數值決定調用 onBindViewHolder() 的次數 ```kotlin= //在這裡將資料集的大小傳遞給RecyclerView override fun getItemCount(): Int { return fruitList.size } ``` ::: - :::spoiler <u>**`onBindViewHolder(viewHolder: ViewHolder, position: Int)`**</u>: 負責將資料集裡對應的資料匹配到ViewHolder上對應的位置,並更新視圖內容以反映數據的狀態。 - 此方法會將數據集合中對應位置的數據綁定到 ViewHolder 中的視圖。 - `onBindViewHolder()`是每一個 [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() } ``` <u>**參數說明**</u>: - `viewHolder: ViewHolder`: 當前項目的 ViewHolder,包含視圖引用 - `position: Int`: 資料集中的索引位置,範圍為 0 到 [getItemCount()-1](#getItemCount()) <u>**※===onBindViewHolder()中常用的方法===※**</u> - `notifyItemRemoved(position)`: 通知 RecyclerView,從指定位置 position 刪除了一個項目。這會觸發 RecyclerView 的動畫,將該項目移除,並將該位置之後的所有項目位置向前移動一位。。 - `notifyItemRangeChanged(position, itemCount - position)`: 通知 RecyclerView 在 positionStart 和 positionStart + itemCount - 1 之間的項目已經改變。 - `notifyDataSetChanged()`: 通知RecyclerView要強制刷新。 ::: <font size=5><u><span id="4.兩個Layout佈局文件">**4.兩個Layout佈局文件:**</span></u></font> --- 使用RecycleView會需要用到至少兩個Layout佈局文件來呈現。Container Layout和 ItemLayout。 - :::spoiler **Container Layout**:定義 RecyclerView 在畫面中的位置和整體呈現,也就是放了RecyclerView元件的Layout。 ![image](https://hackmd.io/_uploads/Hk9xkZ7uel.png) :::success :::spoiler 佈局文件範例 ```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> ``` ::: - :::spoiler **ItemLayou:** 負責表示單一一個Item的呈現方式。 ![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> ``` --- # ▼ RecyclerView 製作步驟總結: 1. <u>準備 Layout 文件</u> - Container Layout:包含 RecyclerView 及其他 UI 元件的主要佈局 - Item Layout:定義單個項目的視圖結構 2. <u>建立數據類別</u> - 創建用於存儲每個項目數據的 Data Class 3. <u>實作 Adapter</u> - 繼承 RecyclerView.Adapter<ViewHolder>,實作三個核心方法: - onCreateViewHolder():創建及初始化 ViewHolder - onBindViewHolder():將數據綁定到 ViewHolder - getItemCount():返回數據集總數量 4. <u>設計 ViewHolder</u> - 繼承 RecyclerView.ViewHolder - 包含 bind() 方法處理數據綁定(推薦做法) - 將 View 元件設為 private 提升封裝性 5. <u>Activity/Fragment 設置</u> - 初始化 RecyclerView - 設置 LayoutManager(LinearLayoutManager、GridLayoutManager 等) - 綁定 Adapter - 準備數據並傳遞給 Adapter :::spoiler 舊資料 ▼ 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`: 立即捲動到指定項目,沒有動畫過渡效果。 ::: ::: --- ## RecyclerView的介紹影片(CC字幕): ### RecyclerView的工作原理: <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> ### RecyclerView的工作流程: <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之中 ``` :::