使用 RecyclerView 顯示可捲動的清單

1. 事前準備

回想一下您平常在手機上使用的應用程式,幾乎每個應用程式都至少有一個清單。通話記錄畫面、聯絡人應用程式以及您喜愛的社群媒體應用程式都會顯示資料清單。如以下螢幕截圖所示,其中部分應用程式會顯示簡單的字詞或詞組清單,而其他應用程式會顯示較複雜的項目,例如含有文字和圖片的資訊卡。無論內容為何,顯示資料清單都是 Android 中最常見的 UI 工作。

cf10a913f9db0ee4.png

為了協助您使用清單建構應用程式,Android 提供了 RecyclerView。RecyclerView 經過精心設計,即使是大型清單,都能透過重複使用或回收已捲動至螢幕外的檢視畫面以有效顯示清單內容。當清單項目捲動至螢幕外時,RecyclerView 會重複使用該檢視畫面來顯示下一個清單項目。這表示捲動至螢幕上的新內容會填滿該項目。這種 RecyclerView 行為不但可省下大量處理時間,還能讓清單更順暢的捲動。

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

dcf4599789b9c2a1.png

在這個程式碼研究室中,您將建構 Affirmations 應用程式。Affirmations 是一個簡單的應用程式,會在捲動清單中顯示 10 個正面肯定的文字。接著,在後續的程式碼研究室中,您會更進一步,為各個肯定文字新增具吸引力的圖片,並修飾應用程式 UI。

必要條件

  • 使用 Android Studio 中的範本建立專案。
  • 在應用程式中新增字串資源。
  • 在 XML 中定義版面配置。
  • 瞭解 Kotlin 中的類別和繼承機制 (包括抽象類別)。
  • 沿用現有類別並覆寫方法。
  • 請參閱 developer.android.com 的說明文件,瞭解 Android 架構提供的類別。

課程內容

  • 如何使用 RecyclerView 顯示資料清單。
  • 如何將程式碼編排到對應的套件中
  • 如何與 RecyclerView 搭配使用轉換介面來自訂個別清單項目的外觀。

建構項目

  • 使用 RecyclerView 顯示肯定字串清單的應用程式。

需求條件

  • 已安裝 Android Studio 4.1 以上版本的電腦。

2. 建立專案

建立空白活動專案

建立新專案之前,請確認你使用的是 Android Studio 4.1 以上版本。

  1. 使用「Empty Acitivity」(空白活動) 範本在 Android Studio 中建立新的 Kotlin 專案。
  2. 輸入「Affirmations」做為應用程式的名稱,「com.example.affirmations」做為套件名稱,然後選擇「API Level 19」做為 最低 SDK 版本。
  3. 按一下「Finish」建立專案。

3. 設定資料清單

建立 Affirmations 應用程式的下一步是新增資源。您必須在專案中新增以下內容。

  • 在應用程式中顯示為肯定字詞的字串資源。
  • 為您的應用程式提供肯定字詞清單的資料來源。

新增肯定字串

  1. 在「Project」(專案) 視窗中,開啟 「App」(應用程式) >「Res」(解析度) >「Values」(設定值) >「string.xml」。這個檔案目前只有一項資源,也就是應用程式的名稱。
  2. 在 strings.xml 中,新增以下肯定做為個別字串資源。將其命名為 affirmation1、affirmation2 等。

肯定文字

I am strong.
I believe in myself.
Each day is a new opportunity to grow and be a better version of myself.
Every challenge in my life is an opportunity to learn from.
I have so much to be grateful for.
Good things are always coming into my life.
New opportunities await me at every turn.
I have the courage to follow my heart.
Things will unfold at precisely the right time.
I will be present in all the moments that this day brings.

完成後,strings.xml 檔案應該會如下所示。

<resources>
    <string name="app_name">Affirmations</string>
    <string name="affirmation1">I am strong.</string>
    <string name="affirmation2">I believe in myself.</string>
    <string name="affirmation3">Each day is a new opportunity to grow and be a better version of myself.</string>
    <string name="affirmation4">Every challenge in my life is an opportunity to learn from.</string>
    <string name="affirmation5">I have so much to be grateful for.</string>
    <string name="affirmation6">Good things are always coming into my life.</string>
    <string name="affirmation7">New opportunities await me at every turn.</string>
    <string name="affirmation8">I have the courage to follow my heart.</string>
    <string name="affirmation9">Things will unfold at precisely the right time.</string>
    <string name="affirmation10">I will be present in all the moments that this day brings.</string>
</resources>

現在,您已新增了字串資源,可以在程式碼中將其參照為 R.string.affirmation1 或 R.string.affirmation2。

建立新的套件

以合理方式管理程式碼能幫助您和其他開發人員瞭解、維護及擴充程式碼。就像將文書工作整理到檔案和資料夾一樣,您可以將程式碼整理到檔案和套件中。

什麼是套件?

  1. 在 Android Studio 中的「Project」(專案)視窗 (Android) 中,在「App」(應用程式) >「Java」下查看 Affirmations 應用程式的新專案檔案。其外觀應與下方的螢幕截圖類似,其中有三個套件,其中一個是您的程式碼 (com.example.affirmations),另二個則是測試檔案 (com.example.affirmations (androidTest)) 和 (com.example.affirmations (test))。

809a0d77a0759dc5.png

  1. 請注意,套件名稱包含數個字詞,並以半形句號分隔。

使用套件的方法有兩種。

  • 為程式碼的不同部分建立不同的套件。例如,開發人員通常會將處理資料的類別,以及在不同套件中建立 UI 的類別分開。
  • 在程式碼中使用其他套件的程式碼。如要使用其他套件的類別,您必須在建構系統的依附元件中定義這些類別。在程式碼中 import 套件也是一種標準的實作方法,因此您可使用短名稱 (例如 TextView),而非使用完整名稱 (例如 android.widget.TextView)。例如,您已經為 sqrt (import kotlin.math.sqrt) 和 View (import android.view.View) 等類別使用 import 陳述式。

在 Affirmations 應用程式中,除了匯入 Android 和 Kotlin 類別之外,您也需要將應用程式整理為幾個套件。即使您的應用程式類別數量不多,使用套件將類別依功能分組也是一種不錯的實作方法。

為套件命名

可用任何名稱為套件命名,但不可重複使用。其他任何已發布的套件名稱都不能相同。由於套件的數量相當龐大,因此要隨機想出不重複名稱並不容易,所以程式設計師會利用慣例來更輕鬆建立和理解套件名稱。

  • 套件名稱通常從常見名稱排列到特定名稱,名稱中每個部分都會是小寫英文字母,並以半形句號分隔。重要事項:半型句號只是名稱的一部分。而這並不代表程式碼為階層結構,或對資料夾結構的強制要求!
  • 因為網際網路網域在全球皆不重複,因此慣例會使用網域 (通常是您或貴公司的網域) 做為名稱的第一個部分。
  • 您可以選擇套件名稱,指明套件內含的項目,以及檔案之間彼此的關聯性。
  • 對於像這樣的程式碼範例,經常會在 com.example 後方接上應用程式的名稱。

以下列舉幾種預先定義的套件名稱及其內容範例:

  • kotlin.math - 數學函式與常數。
  • android.widget - 檢視畫面,例如 TextView。

建立套件

  1. 在 Android Studio 的「Project」窗格中,以滑鼠右鍵按一下「App」>「Java」>「com.example.affirmations」,然後依序選取「New」>「Package」。

39f35a81a574b7ee.png

  1. 在「New Package」彈出式視窗中,查看建議的套件名稱前置字元。建議套件名稱的第一個部分為您按一下滑鼠右鍵時出現的套件名稱。套件名稱並不會建立套件階層,但名稱中重複使用的部分可用來表示內容之間的關係和編制!
  2. 在彈出式視窗中,將 model 附加到建議的套件名稱結尾處。開發人員通常會使用 model 做為建模 (或代表) 資料類別的套件名稱。

3d392f8da53adc6f.png

  1. 按下「Enter」(輸入) 鍵,系統隨即會在「com.example.affirmations」(根層級) 套件下建立新的套件。這個新的套件將包含應用程式中定義的任何資料相關類別。

建立 Affirmation 資料類別

在這項工作中,您會建立名為 Affirmation. 的類別。Affirmation 的物件執行個體代表一個肯定字詞,且包含使用該肯定字詞的字串資源 ID。

  1. 在「com.example.affirmations.model」套件上按一下滑鼠右鍵,然後依序選取「New」>「Kotlin File/Class」。

a6bc9eb5c382cf9.png

  1. 在彈出式視窗中選取「Class」,然後輸入 Affirmation 做為類別名稱。這麼做會在 model 套件中建立名為 Affirmation.kt 的新檔案。
  2. 在類別定義前新增 data 關鍵字,即可將 Affirmation 設為資料類別。這樣會發生錯誤,因為資料類別必須定義至少一個屬性。

Affirmation.kt

package com.example.affirmations.model

data class Affirmation {
}

在建立 Affirmation 的執行個體時,您必須傳遞肯定字串的資源 ID。資源 ID 是整數。

  1. 將 val 整數參數 stringResourceId 新增至 Affirmation 類別的建構函式。這麼做可以排除錯誤。
package com.example.affirmations.model

data class Affirmation(val stringResourceId: Int)

建立一個資料來源的類別

應用程式中顯示的資料可能來自不同來源 (例如您的應用程式專案中或來自需要連上網際網路才能下載資料的外部資源)。因此,資料不一定與您所需格式完全相同。應用程式的其餘部分不應與資料來自何處或是資料原本是以何種格式提供有所關聯。您可以並應該將此資料準備隱藏在為此應用程式預備資料的單獨 Datasource 類別中。

準備資料是另一個問題,因此請將 Datasource 類別放在獨立的「資料」套件中。

  1. 在 Android Studio 的「Project」視窗中,以滑鼠右鍵按一下「App」>「Java」>「com.example.affirmations」,然後依序選擇「New」>「Package」。
  2. 輸入 data 做為套件名稱的最後一個部分,
  3. 在 data 套件上按一下滑鼠右鍵,然後選取「New Kotlin File/Class」。
  4. 輸入 Datasource 做為類別名稱。
  5. 在 Datasource 類別中,建立名為 loadAffirmations() 的函式。

loadAffirmations() 函式必須傳回 Affirmations 的清單。方法是建立清單,並在每個資源字串中填入 Affirmation 執行個體。

  1. 將 List<Affirmation> 宣告為 loadAffirmations() 方法的傳回類型。
  2. 在 loadAffirmations() 內文中,新增 return 陳述式。
  3. 在 return 關鍵字之後,呼叫 listOf<>() 以建立 List。
  4. 在角括號 <> 中,將清單項目的類型指定為 Affirmation。視需要匯入 com.example.affirmations.model.Affirmation。
  5. 在括號中建立一個 Affirmation,並傳入 R.string.affirmation1 做為資源 ID,如下所示。
Affirmation(R.string.affirmation1)
  1. 請將剩餘的 Affirmation 物件新增至所有肯定字詞的清單中 (以半形逗號分隔)。完成的程式碼應如下所示。

Datasource.kt

package com.example.affirmations.data

import com.example.affirmations.R
import com.example.affirmations.model.Affirmation

class Datasource {

    fun loadAffirmations(): List<Affirmation> {
        return listOf<Affirmation>(
            Affirmation(R.string.affirmation1),
            Affirmation(R.string.affirmation2),
            Affirmation(R.string.affirmation3),
            Affirmation(R.string.affirmation4),
            Affirmation(R.string.affirmation5),
            Affirmation(R.string.affirmation6),
            Affirmation(R.string.affirmation7),
            Affirmation(R.string.affirmation8),
            Affirmation(R.string.affirmation9),
            Affirmation(R.string.affirmation10)
        )
    }
}

[選用] 在 TextView 中顯示肯定字詞清單的大小

如要確認您可以建立肯定字詞清單,您可以呼叫 loadAffirmations(),來顯示「Empty Acitivity」應用程式範本隨附的 TextView 中肯定字詞回傳清單大小。

  1. 在 layouts/activity_main.xml 中,將範本隨附的 TextView 賦予 textview 的 id。
  2. 在現有程式碼後、onCreate() 方法的 MainActivity 中,取得 textview 的參照。
val textView: TextView = findViewById(R.id.textview)
  1. 然後新增程式碼來建立和顯示肯定字詞清單的大小。建立 Datasource、呼叫 loadAffirmations()、取得回傳清單的大小、將其轉換為字串並指派其做為 textView 的 text。
textView.text = Datasource().loadAffirmations().size.toString()
  1. 執行應用程式。畫面應如下所示。

b4005973d4a0efc8.png

  1. 刪除您剛才在 MainActivity 中新增的程式碼。

4. 在應用程式中新增 RecyclerView

在這項工作中,您將設定 RecyclerView 以顯示 Affirmations 清單。

建立及使用 RecyclerView 涉及幾個部分。您可以把這幾個部分想成是勞力的分配。下圖提供總覽說明,方便您瞭解導入作業的每個部分。

  • item - 要顯示的清單資料項目。代表應用程式中的一個 Affirmation 物件。
  • Adapter - 擷取資料並準備顯示 RecyclerView。
  • ViewHolders - 可供 RecyclerView 使用且重複使用以顯示肯定字詞的檢視區塊池。
  • RecyclerView - 螢幕上的檢視區塊

4e9c18b463f00bf7.png

在版面配置中新增 RecyclerView

Affirmations 應用程式是由名為 MainActivity 的單一活動與名為 activity_main.xml 版面配置檔案所組成。首先,您需要將 RecyclerView 新增至 MainActivity 的版面配置中。

  1. 開啟 activity_main.xml (「App」(應用程式) >「Res」(解析度) >「Layout」(版面配置) >「activity_main.xml」)
  2. 如果您尚未使用這個程式,請切換為「Split」檢視畫面。

7e7c3e8429267dac.png

  1. 刪除 TextView。

目前的版面配置使用 ConstraintLayout。ConstraintLayout 適用於在版面配置中放置多個子畫面且十分靈活。由於您的版面配置只有單一子畫面 RecyclerView,因此可以切換成名為 FrameLayout 的簡易 ViewGroup,以用來存放單一子畫面。

9e6f235be5fa31a8.png

  1. 在 XML 中,將 ConstraintLayout 替換成 FrameLayout。已完成的版面配置應如下所示。

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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="match_parent"
    tools:context=".MainActivity">
</FrameLayout>
  1. 切換至「Design」(設計) 檢視畫面。
  2. 在「Palette」中選取「Containers」,然後找出「RecyclerView」。
  3. 將「RecyclerView」拖曳至版面配置中。
  4. 如果畫面顯示「Add Project Dependency」彈出式視窗,請查看視窗內容,並按一下「OK」(如果沒有看到彈出式視窗,則不必採取任何行動)。
  5. 請等待 Android Studio 完成以將 RecyclerView 顯示在版面配置中。
  6. 如有需要,請將 RecyclerView 的 layout_width 和 layout_height 屬性變更為 match_parent,以讓 RecyclerView 填滿整個螢幕。
  7. 將 RecyclerView 的資源 ID 設為 recycler_view。

RecyclerView 支援以不同方式 (如線性清單或格狀排列) 顯示項目。排列項目是由 LayoutManager 處理。Android 架構提供基本項目版面配置的版面配置管理工具。Affirmations 應用程式會將項目顯示為直向清單,讓您可以使用 LinearLayoutManager。

  1. 切換回「Code」檢視畫面。在 XML 程式碼中,將 RecyclerView 元素內的 LinearLayoutManager 新增為 RecyclerView 的版面配置管理工具屬性,如下所示。
app:layoutManager="LinearLayoutManager"

如要捲動超過螢幕範圍的項目直向清單,您必須新增直向捲軸。

  1. 在 RecyclerView 中,新增設為 vertical 的 android:scrollbars 屬性。
android:scrollbars="vertical"

最終的 XML 版面配置應如下所示:

activity_main.xml

<FrameLayout 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="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical"
        app:layoutManager="LinearLayoutManager" />

</FrameLayout>
  1. 執行應用程式

專案應可正常編譯並順利執行。不過,因為缺少程式碼的關鍵部分,因此只會在應用程式中顯示白色背景畫面。目前,您已將資料來源和 RecyclerView 加入版面配置,但 RecyclerView 沒有關於如何顯示 Affirmation 物件的資訊。

實作 RecyclerView 的 Adapter

您的應用程式需要一種能從 Datasource 取出資料並設定其格式的方法,讓每個 Affirmation 都可以在 RecyclerView 中顯示為一個項目。

Adapter 是一種設計模式,能將資料調整為 RecyclerView 可以使用的內容。在這個情況下,您必須要有從 loadAffirmations() 回傳清單中取得 Affirmation 例項的轉接介面,並將其轉換成清單項目檢視畫面,以便在 RecyclerView. 中顯示

執行應用程式時,RecyclerView 會使用轉接介面來判斷如何在螢幕上顯示資料。RecyclerView 要求轉接介面為清單中的第一個資料項目建立新的清單項目檢視畫面。在顯示檢視畫面後,系統會要求轉接介面提供資料以繪製項目。這個流程會重複直到 RecyclerView 不需要再透過任何其他檢視畫面填滿螢幕為止。如果一次只有 3 個清單項目顯示在螢幕上,則 RecyclerView 只會要求轉接介面準備這 3 個清單項目的檢視畫面 (而非所有 10 個清單項目的檢視畫面)。

在這個步驟中,您必須建構轉接介面來調整 Affirmation 物件例項,使其可在 RecyclerView 中顯示。

建立 Adapter

轉接介面包含許多部分,因此您將撰寫許多與到目前為止課程中所完成過的程式碼相比更複雜的程式碼。如果您一開始無法完全瞭解其中的詳細內容也沒關係。在您透過 RecyclerView 完成這整個應用程式後,您就能進一步瞭解所有部分如何搭配一同運作。您還可以重複使用本程式碼做為未來透過 RecyclerView 建立應用程式的基礎。

建立項目版面配置

RecyclerView 中的每個項目都有各自的版面配置,您可以在獨立的版面配置檔案中定義這些版面配置。由於系統只會顯示字串,因此您可以使用 TextView 來設定項目版面配置。

  1. 在「Res」(解析度) >「Layout」(版面配置) 中,建立一個名為 list_item.xml 的全新空白檔案。
  2. 在「Code」(程式碼) 檢視畫面中開啟 list_item.xml。
  3. 新增包含 id item_title 的 TextView。
  4. 為 layout_width 和 layout_height 新增 wrap_content,如以下程式碼所示。

請注意,您不需要在版面配置周圍使用 ViewGroup,因為這個清單項目版面配置稍後會加載並新增以做為 RecyclerView 父項的子項。

list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/item_title"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

您也可以依序點選「File」>「New」>「Layout Resource File」,然後將「File name」設為 list_item.xml,並將「Root element」設為 TextView。接著更新產生的程式碼,使其與上方的程式碼相符。

dbb34ca516c804c6.png

建立 ItemAdapter 類別

  1. 在 Android Studio 的「Project」窗格中,以滑鼠右鍵依序點選「App」>「Java」>「com.example.affirmations」,然後依序選取「New」>「Package」。
  2. 輸入 adapter 做為套件名稱的最後一個部分,
  3. 在 adapter 套件上按一下滑鼠右鍵,然後依序選取「New」>「Kotlin File/Class」。
  4. 輸入 ItemAdapter 做為類別名稱,完成後 ItemAdapter.kt 檔案隨即開啟。

您必須將參數新增至 ItemAdapter 的建構函式,以便將肯定字詞清單傳遞給轉接介面。

  1. 將參數新增至 ItemAdapter 建構函式,一個屬於 List<Affirmation> 類型,名為 dataset 的 val。視需要匯入 Affirmation。
  2. 由於 dataset 只會用於本類別,因此請將其設為 private。

ItemAdapter.kt

import com.example.affirmations.model.Affirmation

class ItemAdapter(private val dataset: List<Affirmation>) {

}

ItemAdapter 需要有關如何解決字串資源的資訊。系統會將上述資訊以及其他與應用程式的相關資訊儲存在 Context 物件執行個體中,而您可以將這些執行個體傳遞至 ItemAdapter 執行個體中。

  1. 將參數新增至 ItemAdapter 建構函式,一個屬於 Context 類型,名為 context 的 val。將這個值做為建構函式中的第一個參數。
class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>) {

}

建立 ViewHolder

RecyclerView 不會直接與項目檢視畫面互動,而是與 ViewHolders 互動。ViewHolder 代表 RecyclerView 中的單一清單項目檢視畫面,而且在可能時可以重複使用。ViewHolder 執行個體會保留清單項目版面配置中個別檢視畫面的參照 (因此稱其為「View Holder」(檢視畫面保留項))。如此一來,在使用新資料更新清單項目檢視中畫面時就能更加輕鬆。檢視畫面保留項也會新增 RecyclerView 用來有效在螢幕上移動檢視畫面的資訊。

  1. 在 ItemAdapter 類別中,在關閉 ItemAdapter 的大括號前,先建立 ItemViewHolder 類別。
class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>) {

    class ItemViewHolder()
}
  • 在另一個類別中定義類別的動作稱為建立巢狀類別。
  • 由於 ItemViewHolder 僅由 ItemAdapter 使用,因此在 ItemAdapter 中建立元素會顯示這種關係。這並非強制要求,但可協助其他開發人員瞭解您程式的結構。
  1. 將 View 類型的 private val view 做為參數新增至 ItemViewHolder 類別建構函式。
  2. 將 ItemViewHolder 設為 RecyclerView.ViewHolder 的子類別,並將 view 參數傳遞給父類別建構函式。
  3. 在 ItemViewHolder 內部定義 TextView 類型的 val 屬性 textView。請使用您在 list_item xml 中定義的 ID item_title 來指派其檢視畫面。
class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>) {

    class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView = view.findViewById(R.id.item_title)
    }
}

覆寫轉接介面方法

  1. 新增程式碼以從抽象類別 RecyclerView.Adapter 擴充 ItemAdapter。使用角括號指定 ItemAdapter.ItemViewHolder 做為檢視畫面保留項類型。
class ItemAdapter(
    private val context: Context,
    private val dataset: List<Affirmation>
) : RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {

    class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView = view.findViewById(R.id.item_title)
    }
}

您將會看到錯誤訊息,因為您必須從 RecyclerView.Adapter 實作一些摘要方法。

  1. 將游標移至 ItemAdapter,然後按下 Command + I (Windows 上為 Ctrl + I)。然後會顯示需要導入的方法清單:getItemCount()、onCreateViewHolder() 和 onBindViewHolder()。

7a8a383a8633094b.png

  1. 按住 Shift 並點選,即可全選三個函式,然後按一下「OK」。

這會為三個方法建立附正確參數的虛設常式,如下所示。

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
    TODO("Not yet implemented")
}

override fun getItemCount(): Int {
    TODO("Not yet implemented")
}

override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
    TODO("Not yet implemented")
}

您應該不會再次看到錯誤。接下來,您必須導入這些方法,確保其能在您的應用程式上發揮正常的功能。

導入 getItemCount()

getItemCount() 方法必須傳回您的資料集大小。應用程式資料位於您傳遞至 ItemAdapter 建構函式的 dataset 屬性中,您可以透過 size 取得其大小。

  1. 將 getItemCount() 替換成以下內容:
override fun getItemCount() = dataset.size

以下是更簡介的寫法:

override fun getItemCount(): Int {
    return dataset.size
}

導入 onCreateViewHolder()

版面配置管理員會呼叫 onCreateViewHolder() 方法,為 RecyclerView 建立新的檢視畫面保留項 (在沒有可以重複使用的現有檢視畫面保留項的情況下)。請注意,檢視畫面保留項代表單一清單項目檢視畫面。

onCreateViewHolder() 方法會採用兩個參數並傳回新的 ViewHolder。

  • parent 參數是將新清單項目資料檢視畫面做為子項目附加的資料檢視區塊群組。父項為 RecyclerView。
  • viewType 參數在同一個 RecyclerView 包含多個項目檢視區塊類型時會變得十分重要。如果在 RecyclerView 內顯示不同的清單項目版面配置,則會有不同的項目檢視畫面類型。您只能使用相同的項目檢視畫面類型回收檢視畫面。在這個範例中,只有一個清單項目版面配置和一個項目檢視畫面類型,因此不需要擔心這個參數。
  1. 在 onCreateViewHolder() 方法中,從提供的結構定義 (parent 的 context) 中取得 LayoutInflater 的例項。版面配置加載程式知道如何將 XML 版面配置加載到檢視畫面物件階層中。
val adapterLayout = LayoutInflater.from(parent.context)
  1. 建立 LayoutInflater 物件執行個體後,請加上一個半型句號,然後呼叫另一個方法,以加載實際的清單項目檢視畫面。傳入 XML 版面配置資源 ID R.layout.list_item 和 parent 檢視區塊群組。第三個布林值引數是 attachToRoot。這個引數必須是 false,因為 RecyclerView 在適當時會為您新增此項目到檢視區塊階層。
val adapterLayout = LayoutInflater.from(parent.context)
       .inflate(R.layout.list_item, parent, false)

現在 adapterLayout 持有清單項目檢視畫面的參照 (稍後可以從中

找到子項檢視畫面,例如 TextView)。

  1. 在 onCreateViewHolder() 中,傳回根檢視畫面為 adapterLayout 的新 ItemViewHolder 執行個體。
return ItemViewHolder(adapterLayout)

以下是到目前為止的 onCreateViewHolder() 程式碼。

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
    // create a new view
    val adapterLayout = LayoutInflater.from(parent.context)
        .inflate(R.layout.list_item, parent, false)

    return ItemViewHolder(adapterLayout)
}

導入 onBindViewHolder()

最後需要覆寫的方法為 onBindViewHolder()。版面配置管理員會呼叫此方法,以取代清單項目檢視畫面的內容。

onBindViewHolder() 方法有兩個參數,一個是先前透過 onCreateViewHolder() 方法建立的 ItemViewHolder,另一個則是代表清單中目前項目 position 的 int。使用這個方法時,您可以依據位置找到資料集中的 Affirmation 物件。

  1. 在 onBindViewHolder() 中,建立 val item 並在 dataset 中的指定 position 取得項目。
val item = dataset[position]

最後,您必須更新檢視畫面保留項參照的所有資料檢視,以反映這個項目的正確資料。在這個範例中,只有一個檢視畫面:ItemViewHolder 中的 TextView。設定 TextView 的文字以顯示這個項目的 Affirmation 字串。

  1. 使用 Affirmation 物件執行個體時,您可以呼叫 item.stringResourceId 來尋找對應的字串資源 ID。然而此數值為整數,因此您必須找出實際字串值的對應值。

在 Android 架構中,您可以使用字串資源 ID 呼叫 getString(),以傳回其相關聯的字串值。getString() 是 Resources 類別中的方法,您可以透過 context 取得 Resources 類別執行個體。

也就是說,您可以呼叫 context.resources.getString() 並傳入字串資源 ID。產生的字串可以設為 holder ItemViewHolder 中 textView 的 text。簡而言之,這一行程式碼會更新檢視畫面保留項,以顯示肯定字串。

holder.textView.text = context.resources.getString(item.stringResourceId)

已完成的 onBindViewHolder() 方法應如下所示。

override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
    val item = dataset[position]
    holder.textView.text =  context.resources.getString(item.stringResourceId)
}

以下是已完成的轉接介面程式碼。

ItemAdapter.kt

package com.example.affirmations.adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.R
import com.example.affirmations.model.Affirmation

/**
 * Adapter for the [RecyclerView] in [MainActivity]. Displays [Affirmation] data object.
 */
class ItemAdapter(
    private val context: Context,
    private val dataset: List<Affirmation>
) : RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {

    // Provide a reference to the views for each data item
    // Complex data items may need more than one view per item, and
    // you provide access to all the views for a data item in a view holder.
    // Each data item is just an Affirmation object.
    class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView = view.findViewById(R.id.item_title)
    }

    /**
     * Create new views (invoked by the layout manager)
     */
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
        // create a new view
        val adapterLayout = LayoutInflater.from(parent.context)
            .inflate(R.layout.list_item, parent, false)

        return ItemViewHolder(adapterLayout)
    }

    /**
     * Replace the contents of a view (invoked by the layout manager)
     */
    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
        val item = dataset[position]
        holder.textView.text = context.resources.getString(item.stringResourceId)
    }

    /**
     * Return the size of your dataset (invoked by the layout manager)
     */
    override fun getItemCount() = dataset.size
}

現在您已經導入 ItemAdapter,所以需要告知 RecyclerView 使用這個轉接介面。

修改 MainActivity 以使用 RecyclerView

如要完成上述步驟,您必須使用 Datasource 和 ItemAdapter 類別建立和顯示 RecyclerView 中的項目。請在 MainActivity 中進行這項操作。

  1. 開啟 MainActivity.kt。
  2. 在 MainActivity, 中前往 onCreate() 方法。呼叫 setContentView(R.layout.activity_main). 後插入下列步驟中說明的新程式碼
  3. 建立 Datasource 的執行個體,並呼叫該執行個體上的 loadAffirmations() 方法。將傳回的肯定字詞清單儲存在名為 myDataset 的 val 中。
val myDataset = Datasource().loadAffirmations()
  1. 建立名為 recyclerView 的變數,並使用 findViewById() 在版面配置中尋找對 RecyclerView 的參照。
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
  1. 如果要告知 recyclerView 使用您建立的 ItemAdapter 類別,則請建立新的 ItemAdapter 執行個體。ItemAdapter 要求使用兩個參數:此活動的結構定義 (this) 以及 myDataset 中的肯定字詞。
  2. 將 ItemAdapter 物件指派給 recyclerView 的 adapter 屬性。
recyclerView.adapter = ItemAdapter(this, myDataset)
  1. 由於活動版面配置中的 RecyclerView 版面配置大小是固定的,因此您可以將 RecyclerView 的 setHasFixedSize 參數設為 true。這項設定僅用於改善效能。如果您知道內容變更不會改變 RecyclerView 的版面配置大小,則請使用這項設定。
recyclerView.setHasFixedSize(true)
  1. 完成後,MainActivity 的程式碼應該會與下列程式碼類似。

MainActivity.kt

package com.example.affirmations

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.adapter.ItemAdapter
import com.example.affirmations.data.Datasource

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Initialize data.
        val myDataset = Datasource().loadAffirmations()

        val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
        recyclerView.adapter = ItemAdapter(this, myDataset)

        // Use this setting to improve performance if you know that changes
        // in content do not change the layout size of the RecyclerView
        recyclerView.setHasFixedSize(true)
    }
}
  1. 執行您的應用程式。您應該會看到在螢幕上顯示的肯定字串清單。

427c10d4f29d769d.png

恭喜!你剛建立了一個以 RecyclerView 和自訂轉接介面來顯示資料清單的應用程式。請花點時間檢查您建立的程式碼,並瞭解不同的部分如何搭配運作。

這個應用程式包含所有顯示您肯定字詞的必要部分,但還不可用於正式發布階段。UI 還有些可加強的地方。在接下來的程式碼研究室中,您將改善程式碼、瞭解如何在應用程式中新增圖片以及簡化 UI。

5. 解決方案程式碼

本程式碼研究室的解決方案程式碼位於下方顯示的專案和模組中。請注意,部分 Kotlin 檔案位於不同的套件內,如本檔案開頭的 package 陳述式所示。

res/values/strings.xml

<resources>
    <string name="app_name">Affirmations</string>
    <string name="affirmation1">I am strong.</string>
    <string name="affirmation2">I believe in myself.</string>
    <string name="affirmation3">Each day is a new opportunity to grow and be a better version of myself.</string>
    <string name="affirmation4">Every challenge in my life is an opportunity to learn from.</string>
    <string name="affirmation5">I have so much to be grateful for.</string>
    <string name="affirmation6">Good things are always coming into my life.</string>
    <string name="affirmation7">New opportunities await me at every turn.</string>
    <string name="affirmation8">I have the courage to follow my heart.</string>
    <string name="affirmation9">Things will unfold at precisely the right time.</string>
    <string name="affirmation10">I will be present in all the moments that this day brings.</string>
</resources>

affirmations/data/Datasource.kt

package com.example.affirmations.data

import com.example.affirmations.R
import com.example.affirmations.model.Affirmation

class Datasource {

    fun loadAffirmations(): List<Affirmation> {
        return listOf<Affirmation>(
            Affirmation(R.string.affirmation1),
            Affirmation(R.string.affirmation2),
            Affirmation(R.string.affirmation3),
            Affirmation(R.string.affirmation4),
            Affirmation(R.string.affirmation5),
            Affirmation(R.string.affirmation6),
            Affirmation(R.string.affirmation7),
            Affirmation(R.string.affirmation8),
            Affirmation(R.string.affirmation9),
            Affirmation(R.string.affirmation10)
        )
    }
}

affirmations/model/Affirmation.kt

package com.example.affirmations.model

data class Affirmation(val stringResourceId: Int)

affirmations/MainActivty.kt

package com.example.affirmations

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.adapter.ItemAdapter
import com.example.affirmations.data.Datasource

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Initialize data.
        val myDataset = Datasource().loadAffirmations()

        val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
        recyclerView.adapter = ItemAdapter(this, myDataset)

        // Use this setting to improve performance if you know that changes
        // in content do not change the layout size of the RecyclerView
        recyclerView.setHasFixedSize(true)
    }
}

affirmations/adapter/ItemAdapter.kt

package com.example.affirmations.adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.R
import com.example.affirmations.model.Affirmation

/**
 * Adapter for the [RecyclerView] in [MainActivity]. Displays [Affirmation] data object.
 */
class ItemAdapter(
    private val context: Context,
    private val dataset: List<Affirmation>
) : RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {

    // Provide a reference to the views for each data item
    // Complex data items may need more than one view per item, and
    // you provide access to all the views for a data item in a view holder.
    // Each data item is just an Affirmation object.
    class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView = view.findViewById(R.id.item_title)
    }

    /**
     * Create new views (invoked by the layout manager)
     */
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
        // create a new view
        val adapterLayout = LayoutInflater.from(parent.context)
            .inflate(R.layout.list_item, parent, false)

        return ItemViewHolder(adapterLayout)
    }

    /**
     * Replace the contents of a view (invoked by the layout manager)
     */
    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
        val item = dataset[position]
        holder.textView.text = context.resources.getString(item.stringResourceId)
    }

    /**
     * Return the size of your dataset (invoked by the layout manager)
     */
    override fun getItemCount() = dataset.size
}

src/main/res/layout/activty_main.xml

<FrameLayout 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="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical"
        app:layoutManager="LinearLayoutManager" />

</FrameLayout>

src/main/res/layout/list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/item_title"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

6. 摘要

  • RecyclerView 小工具有助於顯示資料清單,
  • RecyclerView 會使用轉接介面模式來調整及顯示資料。
  • ViewHolder 會建立並保留 RecyclerView 的檢視畫面。
  • RecyclerView 內建 LayoutManagers。RecyclerView 會委派項目版面配置 LayoutManagers。

如何導入轉接介面:

  • 為轉接介面建立新類別,例如 ItemAdapter。
  • 建立代表單一清單項目檢視畫面的自訂 ViewHolder 類別。自 RecyclerView.ViewHolder 類別擴充而來。
  • 修改 ItemAdapter 類別,使其從 RecyclerView 擴充。Adapter 類別搭配自訂 ViewHolder 類別。
  • 在轉接介面中導入下列方法:getItemsCount()、onCreateViewHolder() 和 onBindViewHolder()。

7. 瞭解詳情