Android Kotlin 基礎課程 07.4:與 RecyclerView 項目互動

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

簡介

大多數應用程式都會使用清單及格線顯示項目,讓使用者與這些項目互動。輕觸清單中的項目並查看項目的詳細資料,是這類互動的常見用途。為此,您可以新增點擊詳細資料檢視畫面,藉此回應使用者輕觸項目的點擊事件監聽器。

在這個程式碼研究室中,您從前一版的程式碼研究室中,將睡眠追蹤應用程式的擴充版本新增到您的 RecyclerView

須知事項

  • 運用活動、片段和檢視來建構基本的使用者介面。
  • 在片段之間瀏覽,並使用 safeArgs 在片段之間傳送資料。
  • 查看模型、查看模型工廠、轉換,以及 LiveData 及其觀測器。
  • 如何建立 Room 資料庫、建立資料存取物件 (DAO) 以及定義實體。
  • 如何使用協同程式處理資料庫和其他長時間執行的工作。
  • 如何實作具有 AdapterViewHolder 和項目版面配置的基本 RecyclerView
  • 如何實作 RecyclerView 的資料繫結。
  • 如何建立及使用繫結轉接程式來轉換資料。
  • 如何使用 GridLayoutManager

課程內容

  • 如何讓 RecyclerView 中的項目可供點擊。導入點擊接聽器,即可在有人點擊商品時前往詳細檢視。

執行步驟

  • 根據本系列中先前程式碼研究室的 TrackMySleepquality 應用程式,進行擴充。
  • 在清單中加入點擊接聽器,開始聽取使用者互動。當使用者輕觸清單項目時,就會觸發特定片段的瀏覽作業,其中會詳細列出使用者點擊的項目。範例程式碼提供詳細片段的程式碼和導覽程式碼。

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

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

這個應用程式使用簡化的架構搭配 UI 控制器、檢視模型、LiveData,以及可保存睡眠資料的 Room 資料庫。

在這個程式碼研究室中,您可以新增使用者輕觸網格中的項目時進行回應的功能 (如下所示)。此畫面的程式碼 (片段、檢視模型和導覽) 提供入門應用程式,您將執行點擊處理機制。

步驟 1:取得入門應用程式

  1. 從 GitHub 下載 RecyclerViewClickHandler-Starter 程式碼,並在 Android Studio 中開啟專案。
  2. 建構並執行啟動的睡眠追蹤器應用程式。

[選用] 如要使用先前的程式碼研究室中的應用程式,請更新您的應用程式

如要透過這個程式碼研究室使用 GitHub 提供的入門應用程式,請跳至下一個步驟。

如果您想繼續使用先前在程式碼研究室中建立的睡眠追蹤應用程式,請按照下列指示更新現有的應用程式,讓該應用程式取得詳細資料畫面片段的程式碼。

  1. 即使您要繼續使用現有的應用程式,也可以從 GitHub 取得 RecyclerViewClickHandler-Starter 程式碼,以便複製檔案。
  2. 複製 sleepdetail 套件中的所有檔案。
  3. 複製 layout 資料夾中的 fragment_sleep_detail.xml 檔案。
  4. 複製 navigation.xml 的更新內容,以新增 sleep_detail_fragment 的導覽。
  5. database 套件的 SleepDatabaseDao 中新增 getNightWithId() 方法:
/**
 * Selects and returns the night with given nightId.
*/
@Query("SELECT * from daily_sleep_quality_table WHERE nightId = :key")
fun getNightWithId(key: Long): LiveData<SleepNight>
  1. res/values/strings 中新增下列字串資源:
<string name="close">Close</string>
  1. 請清理並重建您的應用程式以更新資料繫結。

步驟 2:檢查睡眠詳細資料畫面的程式碼

在這個程式碼研究室中,您需實作一個點擊處理常式,該片段會導向一個區段,以顯示關於獲點擊睡眠之夜的詳細資訊。您的起始程式碼已包含此 SleepDetailFragment 的片段和導覽圖表,因為該程式碼包含很多程式碼,而片段和導覽並不是此程式碼研究室的一部分。熟悉下列程式碼:

  1. 在應用程式中找出 sleepdetail 套件。這個套件包含片段、檢視模型,以及可檢視一個夜晚詳細資料的詳細資料的片段模型工廠。

  2. sleepdetail 套件中,開啟並檢查 SleepDetailViewModel 的程式碼。此檢視表模型會取得建構函式中 SleepNight 和 DAO 的鍵。

    類別的主體會取得取得指定鍵的 SleepNight 以及 navigateToSleepTracker 變數,以便控制按下 [關閉] 按鈕時回到 SleepTrackerFragment

    getNightWithId() 函式會傳回 LiveData<SleepNight>,並定義於 SleepDatabaseDao (在 database 套件中) 中。

  3. sleepdetail 套件中,開啟並檢查 SleepDetailFragment 的程式碼。請注意資料繫結的設定方式、檢視模型和導覽觀察器。

  4. sleepdetail 套件中,開啟並檢查 SleepDetailViewModelFactory 的程式碼。

  5. 在版面配置資料夾中檢查 fragment_sleep_detail.xml。請注意,在 <data> 標記中定義出的 sleepDetailViewModel 變數後,就能從資料檢視模型來顯示各資料。

    版面配置包含 ConstraintLayout,其中包含睡眠品質的 ImageViewTextView 為睡眠評分的 TextViewButton 用於關閉詳細資料片段的 Button

  6. 開啟 navigation.xml 檔案。針對「sleep_tracker_fragment」,請留意 sleep_detail_fragment 的新動作。

    action_sleep_tracker_fragment_to_sleepDetailFragment

在這項工作中,你可以更新RecyclerView,以便顯示輕觸項目的詳細資料畫面。

「點擊」和「處理」動作是分為兩個部分的工作:首先,您必須監聽並接收點擊,並判斷哪些項目獲得點擊。然後,您必須以動作回應點擊。

那麼,為這個應用程式新增點擊事件監聽器的最佳方式是什麼?

  • SleepTrackerFragment 會代管許多資料檢視,因此監聽片段層級的點擊事件並不會指出獲得點擊的項目。甚至會告訴使用者這是點擊項目或其他 UI 元素。
  • 而在 RecyclerView 層級中,使用者很難確實瞭解使用者點擊的清單中項目為何。
  • 取得一個點擊項目相關資訊的最佳速度是在 ViewHolder 物件中,因為該物件代表一個清單項目。

雖然ViewHolder是聆聽點擊的絕佳選擇,但通常不適合用來處理點擊。處理點擊的好方法是什麼?

  • Adapter 會在資料檢視中顯示資料項目,因此您可以處理轉接程式的點擊。不過,轉接器的工作是調整資料以便顯示,而不是處理應用程式邏輯。
  • 您通常應該處理 ViewModel 中的點擊,因為 ViewModel 可存取資料和邏輯,以判斷要如何處理點擊。

步驟 1:建立點擊事件監聽器,並從項目版面配置觸發這個事件監聽器

  1. 在「sleeptracker」資料夾中,開啟 SleepNightAdapter.kt
  2. 在檔案最後,在頂層建立新的事件監聽器類別 SleepNightListener
class SleepNightListener() {
    
}
  1. SleepNightListener 類別中新增 onClick() 函式。點擊顯示清單項目的檢視時,檢視會呼叫此 onClick() 函式。(您稍後會將這個函式的 android:onClick 屬性設為這個函式)。
class SleepNightListener() {
    fun onClick() = 
}
  1. onClick() 添加 SleepNight 類型的函數引數 night。資料檢視會顯示所顯示項目,因此必須傳送這項資訊來處理點擊。
class SleepNightListener() {
    fun onClick(night: SleepNight) = 
}
  1. 如要定義 onClick() 的功能,請在 SleepNightListener 建構函式中提供 clickListener 回呼,並將其指派給 onClick()

    將處理點擊的 lambda 命名為 clickListener,這樣有助於追蹤各類別之間的點擊。clickListener 回呼只需有 night.nightId 就能存取資料庫中的資料。你完成的「SleepNightListener」課程應如下方所示。
class SleepNightListener(val clickListener: (sleepId: Long) -> Unit) {
   fun onClick(night: SleepNight) = clickListener(night.nightId)
}
  1. 開啟 list_item_sleep_night.xml
  2. data 區塊中新增變數,讓 SleepNightListener 類別可透過資料繫結取得。為新的 <variable> 提供 namename。將 type 設為 com.example.android.trackmysleepquality.sleeptracker.SleepNightListener 類別的完整名稱,如下所示。您現在可以透過這個版面配置存取「SleepNightListener」中的「onClick()」函式。
<variable
            name="clickListener"
            type="com.example.android.trackmysleepquality.sleeptracker.SleepNightListener" />
  1. 如要監聽此清單項目中任何部分的點擊,請在 ConstraintLayout 中加入 android:onClick 屬性。

    使用資料繫結 lambda 將屬性設為 clickListener:onClick(sleep),如下所示:
android:onClick="@{() -> clickListener.onClick(sleep)}"

步驟 2:將點擊接聽器傳送至檢視區塊與繫結物件

  1. 開啟 SleepNightAdapter.kt.
  2. 修改 SleepNightAdapter 類別的建構函式以接收 val clickListener: SleepNightListener。當轉接程式繫結 ViewHolder 時,必須將該轉接程式提供給此點擊接聽器。
class SleepNightAdapter(val clickListener: SleepNightListener):
       ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {
  1. onBindViewHolder() 中,將呼叫更新為 holder.bind(),以便將點擊接聽器傳送至 ViewHolder。您將收到編譯器錯誤,因為您已在函式呼叫中加入參數。
holder.bind(getItem(position)!!, clickListener)
  1. clickListener 參數加到 bind()。方法是將遊標放到錯誤上,然後在錯誤上按下 Alt+Enter (Windows) 或 Option+Enter (Mac),如下圖所示:

  1. ViewHolder 類別中的 bind() 函式內,將點擊事件監聽器指派給 binding 物件。系統顯示錯誤,因為您需要更新繫結物件。
binding.clickListener = clickListener
  1. 如要更新資料繫結,請清除重新建立專案。(您可能也需要撤銷快取)。因此,您已經從轉接程式建構函式取得點擊事件監聽器,並把該函式傳送至檢視區塊,再進入繫結物件。

步驟 3:輕觸項目時顯示吐司

現在,您導入程式碼是為了擷取點擊,但您尚未導入輕觸項目清單會有什麼影響。最簡單的回應方式是在使用者點擊項目時顯示nightId。這樣可以確認點選清單項目後,即可擷取並傳送正確的 nightId

  1. 開啟 SleepTrackerFragment.kt.
  2. onCreateView() 中,找出 adapter 變數。請注意,由於系統現在會顯示點擊事件監聽器參數,因此會顯示錯誤。
  3. 透過將 lambda 傳遞至 SleepNightAdapter 來定義點擊接聽器。這個簡單的 lambda 只是顯示 nightId 的吐司,如下所示。您必須匯入 Toast。以下是完整的更新定義。
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
   Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
})
  1. 執行應用程式、輕觸項目,並確認其顯示正確的nightId。由於項目增加了 nightId 的值,而應用程式優先顯示最近的昨晚,因此 nightId 的最低項目會顯示在清單底部。

在這項工作中,當您變更 RecyclerView 中的項目時,會改變機制,改為顯示詳細點擊片段,而不是顯示吐司。

步驟 1:點擊瀏覽

在這個步驟中,您不僅要改成顯示吐司,而是在 SleepTrackerFragmentonCreateView() 中更改點擊接聽器 lambda,將 nightId 傳遞給 SleepTrackerViewModel,並觸發導航至 SleepDetailFragment

定義點擊處理常式函式:

  1. 開啟 SleepTrackerViewModel.kt
  2. SleepTrackerViewModel 中,定義結尾的 onSleepNightClicked()click 處理常式函式。
fun onSleepNightClicked(id: Long) {

}
  1. 在「onSleepNightClicked()」中,將「_navigateToSleepDetail」設為已點選的夜間夜晚,於 _navigateToSleepDetail 觸發「導航」。
fun onSleepNightClicked(id: Long) {
   _navigateToSleepDetail.value = id
}
  1. 導入 _navigateToSleepDetail。和先前一樣,為導航狀態定義 private MutableLiveData。同時,公開發布的 val 也能使用。
private val _navigateToSleepDetail = MutableLiveData<Long>()
val navigateToSleepDetail
   get() = _navigateToSleepDetail
  1. 定義應用程式瀏覽完成後要呼叫的方法。請呼叫 onSleepDetailNavigated(),並將其值設為 null
fun onSleepDetailNavigated() {
    _navigateToSleepDetail.value = null
}

加入可呼叫點擊處理常式的程式碼:

  1. 開啟 SleepTrackerFragment.kt,然後向下捲動到建立轉接程式的程式碼,並定義 SleepNightListener 以顯示吐司。
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
   Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
})
  1. 只要在程式碼下方加入下列程式碼,即可在sleepTrackerViewModel輕觸項目時呼叫點擊處理常式 onSleepNighClicked()。進入 nightId,以便檢視模型判斷要睡覺的是哪一個夜晚。這會導致錯誤發生,因為您尚未定義onSleepNightClicked()。您可以依個人需求保留、留言或刪除吐司。
sleepTrackerViewModel.onSleepNightClicked(nightId)

加入程式碼以觀察點擊次數:

  1. 開啟 SleepTrackerFragment.kt
  2. onCreateView()manager 宣告上方,加入程式碼來觀察新的navigateToSleepDetail LiveData。當 navigateToSleepDetail 變更時,請前往 SleepDetailFragment,然後傳入 night,然後再呼叫 onSleepDetailNavigated()。由於您已經在先前的程式碼研究室中完成程式碼,因此程式碼如下:
sleepTrackerViewModel.navigateToSleepDetail.observe(this, Observer { night ->
            night?.let {
              this.findNavController().navigate(
                        SleepTrackerFragmentDirections
                                .actionSleepTrackerFragmentToSleepDetailFragment(night))
               sleepTrackerViewModel.onSleepDetailNavigated()
            }
        })
  1. 執行程式碼、按一下任一項目,應用程式隨即會當機。

處理繫結轉接頭中的空值:

  1. 以偵錯模式再次執行應用程式。輕觸任一項目,然後篩選記錄以顯示錯誤。這裡會顯示堆疊追蹤,其中包含類似下方的內容。
Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter item

不過,堆疊追蹤並未明確指出觸發錯誤的位置。資料繫結的其中一個缺點,就是讓您的程式碼偵錯更加困難。應用程式會在您按一下項目時當機,而唯一的程式碼是處理點擊。

然而,由此表明,使用新的備用設計機制,現在可以對 itemnull值。尤其是當應用程式啟動時,LiveData 會以 null 開頭,因此您需要為每個轉接程式加入空值檢查。

  1. BindingUtils.kt 中,針對每個繫結轉接器,將 item 引數的類型變更為空值,然後將內文納入 item?.let{...}。例如,sleepQualityString 的轉接器看起來會像這樣。請同樣變更其他轉接器。
@BindingAdapter("sleepQualityString")
fun TextView.setSleepQualityString(item: SleepNight?) {
   item?.let {
       text = convertNumericQualityToString(item.sleepQuality, context.resources)
   }
}
  1. 執行您的應用程式。輕觸任一項目,即可開啟詳細檢視畫面。

Android Studio 專案:RecyclerViewClickHandler

如要讓 RecyclerView 中的項目回應點擊,請附加點擊接聽器來列出 ViewHolder 中的項目,並處理 ViewModel 中的點擊。

如要讓 RecyclerView 中的項目回應點擊,請按照下列指示操作:

  • 建立可接受 lambda 並指派給 onClick() 函式的監聽器類別。
class SleepNightListener(val clickListener: (sleepId: Long) -> Unit) {
   fun onClick(night: SleepNight) = clickListener(night.nightId)
}
  • 設定檢視的點擊事件監聽器。
android:onClick="@{() -> clickListener.onClick(sleep)}"
  • 將點擊接聽器傳送至轉接程式建構函式,進入檢視區塊,然後將其新增至繫結物件。
class SleepNightAdapter(val clickListener: SleepNightListener):
       ListAdapter<DataItem, RecyclerView.ViewHolder>(SleepNightDiffCallback()
holder.bind(getItem(position)!!, clickListener)
binding.clickListener = clickListener
  • 在顯示回收器檢視的片段中,您可以在建立轉接程式時將 lambda 傳遞給轉接程式,以定義點擊接聽器。
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
      sleepTrackerViewModel.onSleepNightClicked(nightId)
})
  • 在檢視模型中實作點擊處理常式。如果是清單項目的點擊次數,通常會觸發前往詳細片段的導覽動作。

Udacity 課程:

Android 開發人員說明文件:

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

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

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

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

回答這些問題

第 1 題

假設您的應用程式含有RecyclerView,當中會顯示購物清單中的商品。您的應用程式也定義了點擊監聽器類別:

class ShoppingListItemListener(val clickListener: (itemId: Long) -> Unit) {
    fun onClick(cartItem: CartItem) = clickListener(cartItem.itemId)
}

如何將 ShoppingListItemListener 設為資料繫結?請選取一項。

▢ 在包含購物清單的 RecyclerView 的版面配置檔案中,為 ShoppingListItemListener 新增 <data> 變數。

▢ 在用來定義購物清單中單列版面配置的版面配置檔案,請新增 ShoppingListItemListener<data> 變數。

▢ 在 ShoppingListItemListener 類別中,新增可啟用資料繫結的函式:

fun onBinding (cartItem: CartItem) {dataBindingEnable(true)}

▢ 使用 ShoppingListItemListener 類別的 onClick() 函式,新增可啟用資料繫結的呼叫:

fun onClick(cartItem: CartItem) = { 
    clickListener(cartItem.itemId)
    dataBindingEnable(true)
}

第 2 題

您該如何加入 android:onClick 屬性,將 RecyclerView 中的項目設為回應點擊?請選取所有適用選項。

▢ 在顯示 RecyclerView 的版面配置檔案中,加入 <androidx.recyclerview.widget.RecyclerView>

▢ 將項目新增至資料列中某個項目的版面配置檔案。如要讓所有項目可供點擊,請將其新增至含有該列項目的上層資料檢視。

▢ 將項目新增至資料列中某個項目的版面配置檔案。如果你想在項目中提供一個 TextView 供使用者點選,請在 <TextView> 中新增該元素。

▢ 一律在 MainActivity 中加入版面配置檔案。

開始下一門課程:7.5:RecyclerView 中的標頭