Android Kotlin 基礎課程 07.1:RecyclerView 基本概念

本程式碼研究室是 Android Kotlin 基礎課程的一部分。使用程式碼研究室逐步完成程式碼課程後,您將能充分發揮本課程的潛能。所有課程程式碼研究室清單均列於 Android Kotlin 基礎程式碼研究室到達網頁

簡介

本程式碼研究室可教導如何使用 RecyclerView 顯示項目清單。使用前一代程式碼研究室的睡眠追蹤應用程式進行建構,您就能透過採用建議架構的 RecyclerView,以更靈活且更多種的方式顯示資料。

須知事項

您應該很熟悉:

  • 運用活動、片段和視圖來建構基本的使用者介面 (UI)。
  • 在片段之間瀏覽,並使用 safeArgs 在片段之間傳送資料。
  • 使用檢視模型、檢視模型工廠、轉換,以及 LiveData 及其觀測器。
  • 建立 Room 資料庫、建立 DAO 以及定義實體。
  • 使用協同程式處理資料庫工作和其他長時間執行的工作。

課程內容

  • 如何使用具有 AdapterViewHolderRecyclerView 來顯示項目清單。

執行步驟

  • 將「TrackMySleepquality」應用程式從上一個課程變更為使用 RecyclerView 顯示睡眠品質資料。

在這個程式碼研究室中,您將為追蹤睡眠品質的應用程式建立 RecyclerView 部分。這個應用程式會使用 Room 資料庫儲存一段時間內的睡眠資料。

啟動睡眠追蹤器應用程式有兩個畫面,以片段表示,如下圖所示。

第一個畫面 (如左側所示) 為開始和停止追蹤的按鈕。這個畫面也會顯示使用者的睡眠資料。[清除] 按鈕永久刪除應用程式已收集的所有資料。第二個螢幕則是根據選取睡眠品質評分的方式。

這個應用程式使用簡化的架構搭配 UI 控制器 ViewModelLiveData。應用程式也會使用 Room 資料庫,將睡眠資料永久保留下來。

第一個畫面中顯示的睡眠之夜功能可正常運作,但內容不完全正確。此應用程式使用複雜的格式化工具,為文字檢視建立文字字串,並為品質提供數字。此外,這項設計不會擴充透過這個程式碼研究室修正所有問題後,最終應用程式會提供相同的功能,且主畫面的外觀如下:

顯示清單或格線的資料是 Android 最常用的使用者介面工作之一。清單的內容十分簡單,文字檢視清單可能會顯示簡單資料,例如購物清單。複雜的清單 (例如以註解的度假目的地清單) 可能會在包含捲動標頭的捲動格狀空間中,向使用者顯示許多詳細資料。

為了支援上述所有用途,Android 提供了 RecyclerView 小工具。

RecyclerView」最大的優點在於,對於大型清單而言,效果極佳:

  • 根據預設,RecyclerView 只會處理或繪製目前顯示在畫面上的項目。舉例來說,如果清單中有 1,000 個元素,但只有 10 個元素可以顯示,RecyclerView 只會使用 10 個項目來在畫面上擷取 10 個項目。當使用者捲動畫面時,RecyclerView 會判斷螢幕上顯示的新項目,而只要花一些時間就能顯示這些項目。
  • 當項目捲動離開畫面時,會回收該項目的檢視。這表示該項目填入了可以捲動在畫面上的新內容。這種 RecyclerView 行為可大幅縮短處理時間,並有助於建立順暢的捲動清單。
  • 當項目變更時,RecyclerView 不會重整整份清單,而是直接更新該項目。顯示複雜項目清單時,能大幅提升效率!

在下方的序列中,您可以看到一個已填入資料 ABC 的檢視畫面。在該畫面捲動至螢幕外之後,RecyclerView 會讓新資料 (XYZ) 重複使用檢視畫面。

轉接器圖案

如果您經常在不同使用不同電源插座的國家/地區旅行,或許知道如何使用轉接器將裝置插入插座。此轉接器可讓您將一種類型的插座轉換為另一個,也就是將一個介面轉換為另一個介面。

軟體工程的調整模式可協助物件與其他 API 搭配運作。RecyclerView 使用轉接程式將應用程式資料轉換成 RecyclerView 顯示的內容,而不會變更應用程式儲存及處理資料的方式。針對睡眠追蹤器應用程式,您將建立轉接器,將 Room 資料庫中的資料調整為RecyclerView的顯示內容,而不變更 ViewModel

實作 RecyclerView

如要在 RecyclerView 中顯示您的資料,您必須符合以下條件:

  • 要顯示的資料。
  • 在版面配置檔案中定義的 RecyclerView 執行個體,做為資料檢視的容器。
  • 單一資料項目的版面配置。
    如果所有清單項目看起來都一樣,則可對所有項目使用相同的版面配置,但這並非強制性的。項目版面配置必須與片段版面配置分開建立,因此可以一次建立一個項目檢視並填入資料。
  • 版面配置管理工具。
    版面配置管理員會在資料檢視中處理 UI 元件的機構 (版面配置)。
  • 視圖架。
    檢視架可延伸ViewHolder類別。內含顯示項目版面配置顯示項目的檢視資訊。資料檢視擁有者也會新增RecyclerView使用的資訊,可有效移動畫面瀏覽。
  • 轉接器。
    轉接器會將資料連接至 RecyclerView。可調整資料,以便在 ViewHolder 中顯示資料。RecyclerView 使用轉接頭來瞭解如何在螢幕上顯示資料。

在這項工作中,您將 RecyclerView 新增至版面配置檔案,並設定 Adapter 來將睡眠資料提供給 RecyclerView

步驟 1:使用 LayoutManager 新增 RecyclerView

在這個步驟中,您將 fragment_sleep_tracker.xml 檔案中的 ScrollView 替換為 RecyclerView

  1. 從 GitHub 下載 RecyclerViewFundamentals-Starter 應用程式。
  2. 建構並執行應用程式。請注意資料如何以純文字顯示。
  3. 開啟 Android Studio 中「設計」分頁中的 fragment_sleep_tracker.xml 版面配置檔案。
  4. 在「元件樹狀結構」窗格中刪除 ScrollView。這個動作會一併刪除 ScrollView 中的 TextView
  5. 在「Palette」窗格中,捲動左側的元件類型清單以尋找「Containers」(容器),然後選取該元件。
  6. RecyclerView 從「Pelette」窗格拖曳至「Component Tree」窗格。將 RecyclerView 置於 ConstraintLayout 內。

  1. 如果畫面上出現對話方塊,詢問您是否要新增依附元件,請按一下 [確定],讓 Android Studio 將 recyclerview 依附元件加入您的 Gradle 檔案。應用程式可能需要幾秒鐘的時間才能完成同步處理,

  1. 開啟模組 build.gradle 檔案,捲動至結尾,然後記下新的依附元件,如下所示:
implementation 'androidx.recyclerview:recyclerview:1.0.0'
  1. 切換回 fragment_sleep_tracker.xml
  2. 在「Text」分頁中,找出下列 RecyclerView 程式碼:
<androidx.recyclerview.widget.RecyclerView
   android:layout_width="match_parent"
   android:layout_height="match_parent" />
  1. 向「RecyclerView」提供 sleep_listid
android:id="@+id/sleep_list"
  1. RecyclerView 佔據 ConstraintLayout 內部畫面的剩餘部分。方法是將 RecyclerView 的頂部限制在 [開始] 按鈕、底部的 [清除] 按鈕,以及每一側的父項。在版面配置編輯器或 XML 中,使用以下程式碼將版面配置寬度和高度設為 0 dp:
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintBottom_toTopOf="@+id/clear_button"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/stop_button"
  1. RecyclerView XML 中加入版面配置管理工具。每個「RecyclerView」都需要版面配置管理員,說明如何將清單中的項目排序。Android 提供的是 LinearLayoutManager,根據預設,系統會按照完整寬度列的垂直清單顯示項目。
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
  1. 切換至「設計」分頁,並留意增加的限制會導致「RecyclerView」展開以填滿可用空間。

步驟 2:建立清單項目版面配置和文字檢視工具

RecyclerView 只是容器。在這個步驟中,您會建立要在 RecyclerView 內呈現項目的版面配置和基礎架構。

如要盡快前往 RecyclerView,你使用的只是簡易清單項目,而且只能以數字形式顯示睡眠品質。您必須擁有 TextItemViewHolder 的檢視者才能執行這項操作。您也需要資料檢視 TextView,才能檢視資料。(在後續的步驟中,您可以進一步瞭解觀看擁有人,以及如何制定所有睡眠資料)。

  1. 建立名為「text_item_view.xml」的版面配置檔案。不論使用哪個元素,都不會影響根元素,因為您將取代範本程式碼。
  2. text_item_view.xml 中,刪除所有指定的程式碼。
  3. 新增在開頭和結尾加上 16dpTextView,並將文字大小設為 24sp。讓寬度與父項保持一致,而高度會納入內容。由於這個資料檢視位於 RecyclerView 內,因此您不需要將檢視放在 ViewGroup 中。
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:textSize="24sp"
    android:paddingStart="16dp"
    android:paddingEnd="16dp"
    android:layout_width="match_parent"       
    android:layout_height="wrap_content" />
  1. 開啟 Util.kt。捲動至結尾,並新增下方所述的定義,建立 TextItemViewHolder 類別。將程式碼放在檔案最後的大括號之後。這個存取碼為 Util.kt,因為這個檢視畫面保留程式目前只是暫時的,因此之後即可取代。
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
  1. 如果出現系統提示,請匯入 android.widget.TextViewandroidx.recyclerview.widget.RecyclerView

步驟 3:建立 SleepNightAdapter

執行 RecyclerView 的核心工作是建立轉接程式。您有一個項目檢視的簡易檢視區塊,以及每個項目的版面配置。您現在可以建立轉接程式。轉接程式會建立檢視器並填入資料以顯示 RecyclerView

  1. sleeptracker 套件中,新建名為 SleepNightAdapter 的 Kotlin 類別。
  2. SleepNightAdapter 類別延長 RecyclerView.Adapter。這個類別稱為 SleepNightAdapter,因為它會將 SleepNight 物件調整為 RecyclerView 可使用的物件。轉接器需要知道要使用的檢視器,請傳入 TextItemViewHolder。按照系統提示匯入必要元件後,您會看到錯誤訊息,因為必須實作必要方法。
class SleepNightAdapter: RecyclerView.Adapter<TextItemViewHolder>() {}
  1. SleepNightAdapter 的頂層建立 listOf SleepNight 變數,以存放資料。
var data =  listOf<SleepNight>()
  1. SleepNightAdapter 中覆寫 getItemCount(),以傳回 data 中的睡眠夜晚清單大小。「RecyclerView」需要瞭解轉接器的數量,且需呼叫 getItemCount()
override fun getItemCount() = data.size
  1. SleepNightAdapter 中覆寫 onBindViewHolder() 函式,如下所示。

    RecyclerView 會呼叫 onBindViewHolder() 函式,以顯示單一清單項目在指定位置的資料。因此 onBindViewHolder() 方法會使用兩個引數:檢視區塊,以及要繫結的資料位置。在這個應用程式中,持有人是 TextItemViewHolder,而排名是清單中的排名。
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}
  1. onBindViewHolder() 中,為資料中的某個位置建立變數。
 val item = data[position]
  1. 您建立的ViewHolder具有名為 textView 的屬性。在 onBindViewHolder() 內,將 textViewtext 設為睡眠品質數字。這個代碼只會顯示數字清單,不過這個簡單的範例可讓您查看轉接程式如何將資料匯入資料檢視持有者,並在螢幕上顯示。
holder.textView.text = item.sleepQuality.toString()
  1. SleepNightAdapter 中,覆寫並實作 onCreateViewHolder(),當 RecyclerView 需要檢視區塊來代表項目時,就會呼叫此。

    此函式會使用兩個參數,並傳回 ViewHolderparent 參數是擁有資料檢視資料檢視的資料檢視群組,一律為 RecyclerView。當同一個 RecyclerView 中有多個檢視表時,系統會使用 viewType 參數。舉例來說,如果您將文字檢視、圖片和影片等全部放在同一個 RecyclerView 中,onCreateViewHolder() 函式就必須知道要使用的檢視類型。
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}
  1. onCreateViewHolder() 中,建立 LayoutInflater 的執行個體。

    版面配置選項可幫助你瞭解如何透過 XML 版面配置建立檢視畫面。context 包含關於如何正確增加視圖的資訊。在回收再生視圖的轉接頭中,您一律可以進入 parent 檢視群組,也就是 RecyclerView
val layoutInflater = LayoutInflater.from(parent.context)
  1. onCreateViewHolder() 中,要求 layoutinflater 建立膨脹,然後建立view

    可傳入資料檢視的 XML 版面配置,以及資料檢視的 parent 資料檢視群組。第三個布林引數是 attachToRoot。這個引數必須是 false,因為 RecyclerView 會在其時間將您加入檢視階層。
val view = layoutInflater
       .inflate(R.layout.text_item_view, parent, false) as TextView
  1. onCreateViewHolder() 中,傳回由「view」製作的TextItemViewHolder
return TextItemViewHolder(view)
  1. 適配器需要通知RecyclerView dataRecyclerView它只會知道轉接程式提供的檢視區塊。

    如要讓RecyclerView在其顯示資料變更時通知RecyclerView,請在 SleepNightAdapter 類別頂端的 data 變數中加入一個自訂設定器。在設定集中,為 data 提供一個新的值,然後呼叫 notifyDataSetChanged() 以使用新資料重新觸發清單。
var data =  listOf<SleepNight>()
   set(value) {
       field = value
       notifyDataSetChanged()
   }

步驟 4:告知 RecyclerView 關於轉接器

RecyclerView」需要知道轉接程式,才能取得檢視者。

  1. 開啟 SleepTrackerFragment.kt
  2. onCreateview() 建立轉接器。請在建立 ViewModel 模型後 (在 return 陳述式之前) 加入這段程式碼。
val adapter = SleepNightAdapter()
  1. adapterRecyclerView 建立關聯。
binding.sleepList.adapter = adapter
  1. 清理並重建專案以更新 binding 物件。

    如果 binding.sleepListbinding.FragmentSleepTrackerBinding 左右仍持續發生錯誤,請撤銷快取並重新啟動。(選取 [檔案 > 撤銷快取/重新啟動])。

    如果現在執行應用程式,就表示沒有任何錯誤,但如果您依序輕觸 [開始] 和 [停止],系統並不會顯示任何資料。

步驟 5:將資料新增至轉接頭

您目前已有一個轉接程式,以及將資料從轉接器插入 RecyclerView 的方法。現在您必須從「ViewModel」中將資料新增至轉接頭。

  1. 開啟 SleepTrackerViewModel
  2. 找出 nights 變數,該變數會儲存所有睡眠晚數,這是顯示的資料。nights 變數是透過在資料庫中呼叫 getAllNights() 而設定。
  3. 將「private」從「nights」中移除,因為您要建立需要存取這個變數的觀測器。您的宣告看起來應該像這樣:
val nights = database.getAllNights()
  1. database 套件中開啟 SleepDatabaseDao
  2. 找出 getAllNights() 函式。請注意,此函式會傳回 SleepNight 值清單做為 LiveData。這表示 nights 變數包含 LiveData。這個變數會由 Room 持續更新。您可以透過 nights 瞭解資料何時有所變更。
  3. 開啟 SleepTrackerFragment
  4. onCreateView()adapter 建立下方,針對 nights 變數建立觀測器。

    請提供片段的 viewLifecycleOwner 做為生命週期擁有者,可確保只有當 RecyclerView 出現在螢幕上時,這個觀測器才會啟用。
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   })
  1. 在觀察器內,每當你收到非空值的值 (nights) 時,請將這個值指派給轉接程式的data。這是觀測器完成設定和設定資料的程式碼:
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   it?.let {
       adapter.data = it
   }
})
  1. 建構並執行程式碼。

    如果變壓器可以使用,你會看到睡眠品質數字清單。系統會在您輕觸 [開始] 後顯示左側的螢幕截圖 -1。在您輕觸 [停止] 並選取品質評分後,右側螢幕截圖會顯示更新後的睡眠品質數據。

步驟 6:探索檢視模式的回收業者

RecyclerView「回收」檢視持有人,這表示它會重複使用這些憑證。當畫面捲動離開畫面時,RecyclerView 會重複使用一個檢視畫面,讓畫面顯示在畫面上。

由於這些檢視區塊已回收,請確認 onBindViewHolder() 已設定或重設先前在檢視項目上設定的所有項目。

舉例來說,如果某位通話品質等級低於 1 或代表睡眠品質不佳,此時就可以將文字顏色設為紅色。

  1. SleepNightAdapter 類別中,將下列程式碼加進 onBindViewHolder() 的結尾。
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
}
  1. 執行應用程式。
  2. 新增一些睡眠品質低落的資料,且數字為紅色。
  3. 為睡眠品質加上高評分,直到畫面上出現紅色的高數字為止。

    由於 RecyclerView 會重複使用觀看人數,最終只會讓其中一個紅色觀看者擁有高品質的評分。高分等級會以紅色標示。

  1. 如要修正這個問題,請新增「else」陳述式,以便將顏色設為黑色或等於不等於一個顏色。

    將上述兩種條件都設為「清晰」時,檢視者會針對每個項目使用正確的文字顏色。
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
} else {
   // reset
   holder.textView.setTextColor(Color.BLACK) // black
}
  1. 執行應用程式,且所有號碼應使用正確的顏色。

恭喜!現在,您的基本功能 RecyclerView 可正常運作。

在這項工作中,您需使用簡單的檢視畫面來取代顯示睡眠資料的其他資料。

您添加到 Util.kt 的簡單 ViewHolder 只是將 TextView 包覆在 TextItemViewHolder

class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)

為什麼 RecyclerView 不是直接使用 TextView?這行程式碼可以提供許多功能。ViewHolder 說明項目在 RecyclerView 中的檢視項目及其中繼資料。「RecyclerView」必須使用這項功能才能隨著清單捲動而正確定位檢視區塊,以及在 Adapter 中新增或移除項目時產生一些有趣的內容,例如動畫檢視。

如果 RecyclerView 需要存取儲存在 ViewHolder 中的資料檢視,可使用資料檢視擁有者的 itemView 屬性。RecyclerView將項目繫結至螢幕上以便顯示項目、在邊框周圍繪製裝飾,以及實作協助工具時,會使用 itemView

步驟 1:建立項目版面配置

在這個步驟中,您會建立一個商品項目的版面配置檔案。版面配置包含 ConstraintLayout 和睡眠品質 ImageView,以及 TextView 的睡眠長度,以及 TextView 的文字內容。您先前曾完成版面配置,因此請複製及貼上系統提供的 XML 程式碼。

  1. 建立新的版面配置資源檔案,並命名為 list_item_sleep_night
  2. 請將檔案中的所有程式碼替換成以下程式碼。接著,請熟悉您剛建立的版面配置。
<?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:layout_width="match_parent"
   android:layout_height="wrap_content">

   <ImageView
       android:id="@+id/quality_image"
       android:layout_width="@dimen/icon_size"
       android:layout_height="60dp"
       android:layout_marginStart="16dp"
       android:layout_marginTop="8dp"
       android:layout_marginBottom="8dp"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent"
       tools:srcCompat="@drawable/ic_sleep_5" />

   <TextView
       android:id="@+id/sleep_length"
       android:layout_width="0dp"
       android:layout_height="20dp"
       android:layout_marginStart="8dp"
       android:layout_marginTop="8dp"
       android:layout_marginEnd="16dp"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toEndOf="@+id/quality_image"
       app:layout_constraintTop_toTopOf="@+id/quality_image"
       tools:text="Wednesday" />

   <TextView
       android:id="@+id/quality_string"
       android:layout_width="0dp"
       android:layout_height="20dp"
       android:layout_marginTop="8dp"
       app:layout_constraintEnd_toEndOf="@+id/sleep_length"
       app:layout_constraintHorizontal_bias="0.0"
       app:layout_constraintStart_toStartOf="@+id/sleep_length"
       app:layout_constraintTop_toBottomOf="@+id/sleep_length"
       tools:text="Excellent!!!" />
</androidx.constraintlayout.widget.ConstraintLayout>
  1. 切換至 Android Studio 中的「設計」分頁。在設計檢視模式中,您的版面配置如下所示。在藍圖檢視中,看似右側螢幕截圖。

步驟 2:建立 ViewHolder

  1. 開啟 SleepNightAdapter.kt
  2. SleepNightAdapter 中建立名為 ViewHolder 的類別,讓該類別延伸 RecyclerView.ViewHolder
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}
  1. ViewHolder內取得資料檢視參照。您需要參照此 ViewHolder 將會更新的檢視。每次繫結此「ViewHolder」時,都需要存取圖片和兩種文字檢視。(您之後會將此程式碼轉換為使用資料繫結)。
val sleepLength: TextView = itemView.findViewById(R.id.sleep_length)
val quality: TextView = itemView.findViewById(R.id.quality_string)
val qualityImage: ImageView = itemView.findViewById(R.id.quality_image)

步驟 3:在 SleepNightAdapter 中使用 ViewHolder

  1. SleepNightAdapter 定義中,請使用 TextItemViewHolder,不要使用您剛剛建立的 SleepNightAdapter.ViewHolder
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {

更新 onCreateViewHolder()

  1. 變更 onCreateViewHolder() 的簽名以傳回 ViewHolder
  2. 將版面配置充氣器變更為使用正確的版面配置資源 list_item_sleep_night
  3. 將投放內容移除至 TextView
  4. 傳回 ViewHolder 檔案,而不是傳回 TextItemViewHolder

    以下是完成的 onCreateViewHolder() 函式:
    override fun onCreateViewHolder(
            parent: ViewGroup, viewType: Int): ViewHolder {
        val layoutInflater = 
            LayoutInflater.from(parent.context)
        val view = layoutInflater
                .inflate(R.layout.list_item_sleep_night, 
                         parent, false)
        return ViewHolder(view)
    }

更新 onBindViewHolder()

  1. 變更 onBindViewHolder() 的簽名,使 holder 參數是 ViewHolder 而非 TextItemViewHolder
  2. onBindViewHolder() 內,除了 item 的定義以外,請刪除所有程式碼。
  3. 定義一個 val res,用來存放此資料檢視的 resources
val res = holder.itemView.context.resources
  1. sleepLength 文字檢視的文字設為持續時間。複製以下程式碼,該程式碼會呼叫 和範例程式碼所提供的格式設定函式。
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
  1. 由於需要定義 convertDurationToFormatted(),因此發生錯誤。開啟「Util.kt」並取消註解該程式碼與相關的匯入項目。(選取 [程式碼 &註解 (含註解)])。
  2. 返回onBindViewHolder(),使用 convertNumericQualityToString() 設定畫質。
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
  1. 您可能需要手動匯入這些函式。
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString
  1. 為品質設定正確的圖示。範例程式碼中會顯示新的 ic_sleep_active 圖示。
holder.qualityImage.setImageResource(when (item.sleepQuality) {
   0 -> R.drawable.ic_sleep_0
   1 -> R.drawable.ic_sleep_1
   2 -> R.drawable.ic_sleep_2
   3 -> R.drawable.ic_sleep_3
   4 -> R.drawable.ic_sleep_4
   5 -> R.drawable.ic_sleep_5
   else -> R.drawable.ic_sleep_active
})
  1. 以下是已完成的 onBindViewHolder() 函式,用於設定 ViewHolder 的所有資料:
   override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = data[position]
        val res = holder.itemView.context.resources
        holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
        holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
        holder.qualityImage.setImageResource(when (item.sleepQuality) {
            0 -> R.drawable.ic_sleep_0
            1 -> R.drawable.ic_sleep_1
            2 -> R.drawable.ic_sleep_2
            3 -> R.drawable.ic_sleep_3
            4 -> R.drawable.ic_sleep_4
            5 -> R.drawable.ic_sleep_5
            else -> R.drawable.ic_sleep_active
        })
    }
  1. 執行您的應用程式。顯示畫面應如下圖所示,顯示睡眠品質圖示,以及睡眠時長與睡眠品質的文字。

RecyclerView」現已完成!您已經瞭解如何實作 AdapterViewHolder,並且成功整理出包含 RecyclerView Adapter 的清單。

目前您的程式碼顯示建立轉接程式和檢視持有者的程序。不過,您可以改善這個程式碼。要顯示的程式碼和管理資料檢視持有者的程式碼會混合,onBindViewHolder() 也知道如何更新 ViewHolder

在正式版應用程式中,您可能有多個檢視點、更複雜的轉接程式,以及多位開發人員進行變更。您應該妥善設計程式碼,讓所有與資料檢視擁有者相關的項目都只屬於資料檢視持有者。

步驟 1:重構 onBindViewHolder()

在這個步驟中,您會重構程式碼,並將所有檢視器功能移至 ViewHolder。這項重構的目的在於改變應用程式的呈現方式,不讓開發人員以更輕鬆、安全的方式處理程式碼。幸好,Android Studio 提供了多項實用工具,

  1. SleepNightAdapter 中,選取 onBindViewHolder() 以外的陳述式以宣告變數 item
  2. 按一下滑鼠右鍵,然後選取 [重新計算] > [擷取 & 函式]
  3. 將函式命名為 bind 並接受建議的參數。按一下 [確定]。

    bind() 函式位於 onBindViewHolder() 下方。
    private fun bind(holder: ViewHolder, item: SleepNight) {
        val res = holder.itemView.context.resources
        holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
        holder.quality.text = convertNumericQualityToString(item.sleepQuality, res)
        holder.qualityImage.setImageResource(when (item.sleepQuality) {
            0 -> R.drawable.ic_sleep_0
            1 -> R.drawable.ic_sleep_1
            2 -> R.drawable.ic_sleep_2
            3 -> R.drawable.ic_sleep_3
            4 -> R.drawable.ic_sleep_4
            5 -> R.drawable.ic_sleep_5
            else -> R.drawable.ic_sleep_active
        })
    }
  1. 將遊標移到 bind()holder 參數文字 holder 上。按下 Alt+Enter (在 Mac 上為 Option+Enter) 開啟意圖選單。選取 [轉換參數到接收器],轉換成具備下列簽名的擴充功能函式:
private fun ViewHolder.bind(item: SleepNight) {...}
  1. 剪下 bind() 函式並貼到 ViewHolder
  2. bind() 設為公開。
  3. 視需要將 bind() 匯入轉接程式。
  4. 因為它為了在ViewHolder中,所以您可以移除簽章的ViewHolder部分。以下是 ViewHolder 類別中 bind() 函式的最終程式碼。
fun bind(item: SleepNight) {
   val res = itemView.context.resources
   sleepLength.text = convertDurationToFormatted(
           item.startTimeMilli, item.endTimeMilli, res)
   quality.text = convertNumericQualityToString(
           item.sleepQuality, res)
   qualityImage.setImageResource(when (item.sleepQuality) {
       0 -> R.drawable.ic_sleep_0
       1 -> R.drawable.ic_sleep_1
       2 -> R.drawable.ic_sleep_2
       3 -> R.drawable.ic_sleep_3
       4 -> R.drawable.ic_sleep_4
       5 -> R.drawable.ic_sleep_5
       else -> R.drawable.ic_sleep_active
   })
}

步驟 2:重構 onCreateViewHolder

轉接器中的 onCreateViewHolder() 方法目前會從 ViewHolder 的版面配置資源開放視圖。但是,通貨膨脹不適用於適配器,以及如何使用 ViewHolder。通貨膨脹將於 ViewHolder發生。

  1. onCreateViewHolder() 中,選取函式主體中的所有程式碼。
  2. 按一下滑鼠右鍵,然後選取 [重新計算] > [擷取 & 函式]
  3. 將函式命名為 from 並接受建議的參數。按一下 [確定]。
  4. 將遊標移到函式名稱 from 上。按下 Alt+Enter (在 Mac 上為 Option+Enter) 開啟意圖選單。
  5. 選取 [移至隨附物件]from() 函式必須在隨附物件中,以便可在 ViewHolder 類別中呼叫,而非在 ViewHolder 執行個體上呼叫。
  6. companion 物件移到 ViewHolder 類別中。
  7. from() 設為公開。
  8. onCreateViewHolder() 中,變更 return 陳述式以傳回 ViewHolder 類別中呼叫 from() 的結果。

    您已完成的 onCreateViewHolder()from() 方法應如下方所示,且您的程式碼應順利執行並執行。
    override fun onCreateViewHolder(parent: ViewGroup, viewType: 
Int): ViewHolder {
        return ViewHolder.from(parent)
    }
companion object {
   fun from(parent: ViewGroup): ViewHolder {
       val layoutInflater = LayoutInflater.from(parent.context)
       val view = layoutInflater
               .inflate(R.layout.list_item_sleep_night, parent, false)
       return ViewHolder(view)
   }
}
  1. 變更 ViewHolder 類別的簽名,使其建構函式設為不公開。由於 from() 現在是傳回新 ViewHolder 執行個體的方法,因此任何人都不想再呼叫 ViewHolder 建構函式。
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
  1. 執行應用程式。您的應用程式必須如往常一樣建立及執行,也就是重組後產生的結果。

Android Studio 專案:RecyclerViewFundamentals

  • 顯示清單或格線的資料是 Android 最常用的使用者介面工作之一。RecyclerView即使設計極大清單,也可以提高效率。
  • RecyclerView 只會處理處理或繪製目前顯示在畫面上的項目。
  • 當項目捲動離開畫面時,其檢視畫面的檢視畫面會再度使用。這表示該項目填入了可以捲動在畫面上的新內容。
  • 軟體工程的調整模式可協助物件與其他 API 搭配運作。RecyclerView 使用轉接程式將應用程式資料轉換為其顯示內容,而不需變更應用程式儲存及處理資料的方式。

如要在 RecyclerView 中顯示您的資料,您必須符合以下條件:

  • RecyclerView
    如要建立 RecyclerView 例項,請在版面配置檔案中定義 <RecyclerView> 元素。
  • LayoutManager
    RecyclerView 使用 LayoutManager 來整理 RecyclerView 中項目的版面配置,例如排列在網格或線性清單中。

    在版面配置檔案的 <RecyclerView> 中,將 app:layoutManager 屬性設為版面配置管理工具 (例如 LinearLayoutManagerGridLayoutManager)。

    您也可以透過程式為 RecyclerView 設定 LayoutManager。(我們會在後續的程式碼研究室中說明此技術)。
  • 每個項目的版面配置
    為 XML 版面配置檔案中的單一項目建立版面配置。
  • 轉接器
    建立轉接程式,準備資料以顯示在 ViewHolder 中。將轉接程式與 RecyclerView 建立關聯。

    RecyclerView 執行時,系統會使用轉接程式來辨識如何在螢幕上顯示資料。

    轉接程式要求你採用以下方法:
    getItemCount() 可傳回項目數量。
    onCreateViewHolder()為清單中的項目傳回 ViewHolder
    onBindViewHolder() 可配合清單中的項目檢視畫面調整資料。

  • ViewHolder
    ViewHolder 包含查看資訊,用於顯示項目版面配置中的某個項目。
  • 轉接器中的 onBindViewHolder() 方法會根據資料自動調整視圖。您可以隨時覆寫這個方法。一般來說,onBindViewHolder() 會增加項目的版面配置,並將資料放入版面配置的檢視畫面中。
  • 由於 RecyclerView 不知道這項資料,因此 Adapter 會在資料變更時通知 RecyclerView。使用 notifyDataSetChanged() 通知 Adapter 資料已變更。

Udacity 課程:

Android 開發人員說明文件:

這個部分會列出在代碼研究室中,受老師主導的課程作業的可能學生作業。由老師自行決定要執行下列動作:

  • 視需要指派家庭作業。
  • 告知學生如何提交家庭作業。
  • 批改家庭作業。

老師可視需要使用這些建議,並視情況指派其他合適的家庭作業。

如果您是自行操作本程式碼研究室,歡迎透過這些家庭作業來測試自己的知識。

回答這些問題

第 1 題

RecyclerView如何顯示項目?請選取所有適用選項。

▢ 以清單或格線顯示項目。

▢ 垂直或水平捲動。

▢ 使用較大型的裝置 (例如平板電腦) 沿著對角線捲動。

▢ 清單或格狀檢視功能的設計不足,導致內容無法滿足這項需求。

第 2 題

使用 RecyclerView 有哪些好處?請選取所有適用選項。

▢ 有效顯示大型清單。

▢ 自動更新資料。

▢ 更新項目時,可盡量減少更新、刪除或新增到項目時重新整理的機會。

▢ 重複使用會捲動在畫面上的視圖,以顯示下一個項目在畫面上。

第 3 題

使用轉接程式的幾個好處是什麼?請選取所有適用選項。

▢ 區分問題有助於更輕鬆地變更及測試程式碼。

RecyclerView 與顯示的資料無關。

▢ 資料處理層不需要擔心資料的顯示方式。

▢ 應用程式執行速度更快。

第 4 題

以下關於「ViewHolder」的敘述何者正確?請選取所有適用選項。

ViewHolder 版面配置是在 XML 版面配置檔案中定義。

▢ 資料集中的每個資料單位都有一個 ViewHolder

▢ 您在 RecyclerView 中可以有多個 ViewHolder

Adapter 將資料繫結至 ViewHolder

開始下一個課程:7.2:DiffUtil 和資料與 RecyclerView 繫結