PU
  • NEW!
    NEW!  Connect Ideas Across Notes
    Save time and share insights. With Paragraph Citation, you can quote others’ work with source info built in. If someone cites your note, you’ll see a card showing where it’s used—bringing notes closer together.
    Got it
      • Create new note
      • Create a note from template
        • Sharing URL Link copied
        • /edit
        • View mode
          • Edit mode
          • View mode
          • Book mode
          • Slide mode
          Edit mode View mode Book mode Slide mode
        • Customize slides
        • Note Permission
        • Read
          • Only me
          • Signed-in users
          • Everyone
          Only me Signed-in users Everyone
        • Write
          • Only me
          • Signed-in users
          • Everyone
          Only me Signed-in users Everyone
        • Engagement control Commenting, Suggest edit, Emoji Reply
      • Invite by email
        Invitee

        This note has no invitees

      • Publish Note

        Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

        Your note will be visible on your profile and discoverable by anyone.
        Your note is now live.
        This note is visible on your profile and discoverable online.
        Everyone on the web can find and read all notes of this public team.

        Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

        Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

        Explore these features while you wait
        Complete general settings
        Bookmark and like published notes
        Write a few more notes
        Complete general settings
        Write a few more notes
        See published notes
        Unpublish note
        Please check the box to agree to the Community Guidelines.
        View profile
      • Commenting
        Permission
        Disabled Forbidden Owners Signed-in users Everyone
      • Enable
      • Permission
        • Forbidden
        • Owners
        • Signed-in users
        • Everyone
      • Suggest edit
        Permission
        Disabled Forbidden Owners Signed-in users Everyone
      • Enable
      • Permission
        • Forbidden
        • Owners
        • Signed-in users
      • Emoji Reply
      • Enable
      • Versions and GitHub Sync
      • Note settings
      • Note Insights New
      • Engagement control
      • Make a copy
      • Transfer ownership
      • Delete this note
      • Save as template
      • Insert from template
      • Import from
        • Dropbox
        • Google Drive
        • Gist
        • Clipboard
      • Export to
        • Dropbox
        • Google Drive
        • Gist
      • Download
        • Markdown
        • HTML
        • Raw HTML
    Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
    Create Create new note Create a note from template
    Menu
    Options
    Engagement control Make a copy Transfer ownership Delete this note
    Import from
    Dropbox Google Drive Gist Clipboard
    Export to
    Dropbox Google Drive Gist
    Download
    Markdown HTML Raw HTML
    Back
    Sharing URL Link copied
    /edit
    View mode
    • Edit mode
    • View mode
    • Book mode
    • Slide mode
    Edit mode View mode Book mode Slide mode
    Customize slides
    Note Permission
    Read
    Only me
    • Only me
    • Signed-in users
    • Everyone
    Only me Signed-in users Everyone
    Write
    Only me
    • Only me
    • Signed-in users
    • Everyone
    Only me Signed-in users Everyone
    Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    <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之中 ``` :::

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password
    or
    Sign in via Google Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully