這個程式碼研究室是 Android Kotlin 基礎知識課程的一部分。如果您按部就班完成程式碼研究室,就能充分體驗到本課程的價值。所有課程程式碼研究室都列在 Android Kotlin 基礎知識程式碼研究室到達網頁。
簡介
大多數使用清單和格線顯示項目的應用程式,都會讓使用者與項目互動。輕觸清單中的項目並查看項目詳細資料,是這類互動的常見用途。如要達成這個目標,您可以新增點擊事件監聽器,在使用者輕觸項目時顯示詳細資料檢視畫面。
在本程式碼研究室中,您將為 RecyclerView 新增互動功能,並以先前一系列程式碼研究室的睡眠追蹤器應用程式擴充版本為基礎。
必備知識
- 使用活動、片段和檢視畫面建構基本使用者介面。
- 在片段之間導覽,以及使用
safeArgs在片段之間傳遞資料。 - 查看模型、模型工廠、轉換和
LiveData及其觀察者。 - 如何建立
Room資料庫、建立資料存取物件 (DAO) 及定義實體。 - 如何使用協同程式處理資料庫和其他長時間執行的工作。
- 如何使用
Adapter、ViewHolder和項目版面配置實作基本RecyclerView。 - 如何為
RecyclerView實作資料繫結。 - 如何建立及使用繫結轉接程式來轉換資料。
- 如何使用
GridLayoutManager。
課程內容
- 如何將
RecyclerView中的項目設為可點擊。實作點擊事件監聽器,在點選項目時前往詳細資料檢視畫面。
學習內容
- 以本系列先前程式碼研究室的 TrackMySleepQuality 應用程式擴充版本為基礎。
- 在清單中新增點擊事件監聽器,開始監聽使用者互動。輕觸清單項目時,系統會觸發導覽作業,前往顯示所點選項目詳細資料的片段。範例程式碼提供詳細資料片段的程式碼,以及導覽程式碼。
如圖所示,睡眠追蹤器應用程式一開始有兩個畫面,分別以片段表示。
|
|
左側顯示的第一個畫面有開始和停止追蹤的按鈕。畫面上會顯示部分使用者的睡眠資料。「清除」按鈕會永久刪除應用程式為使用者收集的所有資料。右側的第二個畫面用於選取睡眠品質評分。
這個應用程式採用簡化架構,包含 UI 控制器、檢視模型和 LiveData,以及用於保存睡眠資料的 Room 資料庫。

在本程式碼研究室中,您會新增功能,讓使用者輕觸格線中的項目時,系統會顯示詳細資料畫面,如下所示。這個畫面的程式碼 (片段、檢視區塊模型和導覽) 隨附於入門應用程式,您將實作點擊處理機制。

步驟 1:取得範例應用程式
- 從 GitHub 下載 RecyclerViewClickHandler 範例程式碼,並在 Android Studio 中開啟專案。
- 建構並執行入門睡眠追蹤器應用程式。
[選用] 如果要使用先前程式碼研究室的應用程式,請更新應用程式
如果您要使用本程式碼研究室 GitHub 提供的範例應用程式,請跳至下一個步驟。
如果您想繼續使用在上一個程式碼研究室中建構的睡眠追蹤器應用程式,請按照下列操作說明更新現有應用程式,使其包含詳細資料畫面片段的程式碼。
- 即使您要繼續使用現有應用程式,也請從 GitHub 取得 RecyclerViewClickHandler-Starter 程式碼,以便複製檔案。
- 複製
sleepdetail套件中的所有檔案。 - 在
layout資料夾中,複製fragment_sleep_detail.xml檔案。 - 複製
navigation.xml的更新內容,其中新增了sleep_detail_fragment的導覽功能。 - 在
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>- 在
res/values/strings中新增下列字串資源:
<string name="close">Close</string>- 清除並重建應用程式,更新資料繫結。
步驟 2:檢查睡眠詳細資料畫面的程式碼
在本程式碼研究室中,您會實作點擊處理常式,前往顯示所點選睡眠夜詳細資料的片段。您的範例程式碼已包含這個 SleepDetailFragment 的片段和導覽圖,因為程式碼相當多,且片段和導覽不屬於本程式碼研究室的內容。請熟悉下列程式碼:
- 在應用程式中找出
sleepdetail套件。這個套件包含片段、檢視區塊模型,以及片段的檢視區塊模型工廠,可顯示一晚的睡眠詳細資料。 - 在
sleepdetail套件中,開啟並檢查SleepDetailViewModel的程式碼。這個檢視區塊模型會在建構函式中採用SleepNight的鍵和 DAO。
類別主體包含的程式碼可取得指定鍵的SleepNight,以及在按下「關閉」按鈕時,控制導覽返回SleepTrackerFragment的navigateToSleepTracker變數。getNightWithId()函式會傳回LiveData<SleepNight>,並在SleepDatabaseDao(位於database套件中) 定義。 - 在
sleepdetail套件中,開啟並檢查SleepDetailFragment的程式碼。請注意資料繫結、檢視模型和導覽觀察器的設定。 - 在
sleepdetail套件中,開啟並檢查SleepDetailViewModelFactory的程式碼。 - 在版面配置資料夾中,檢查
fragment_sleep_detail.xml。請注意<data>標記中定義的sleepDetailViewModel變數,可從檢視模型取得要在每個檢視畫面中顯示的資料。
版面配置包含ConstraintLayout,其中含有睡眠品質的ImageView、品質評分的TextView、睡眠時間長度的TextView,以及關閉詳細資料片段的Button。 - 開啟
navigation.xml檔案,請注意sleep_tracker_fragment的新動作。
新動作action_sleep_tracker_fragment_to_sleepDetailFragment是從睡眠追蹤器片段導覽至詳細資料畫面。sleep_detail_fragment
在這項工作中,您要更新 RecyclerView,以便在使用者輕觸項目時顯示該項目的詳細資料畫面。
接收及處理點擊是兩部分的工作:首先,您需要監聽及接收點擊,並判斷點擊的項目。接著,您需要以動作回應點擊。
那麼,為這個應用程式新增點擊監聽器的最佳位置為何?
SleepTrackerFragment會代管許多檢視區塊,因此在片段層級監聽點擊事件時,您無法得知點選的是哪個項目。系統甚至不會告訴您點選的是項目還是其他 UI 元素。- 在
RecyclerView層級監聽時,很難確切判斷使用者點選清單中的哪個項目。 - 如要取得有關單一點擊項目的資訊,最佳步調是在
ViewHolder物件中,因為這代表一個清單項目。
雖然 ViewHolder 是監聽點擊的絕佳位置,但這通常不是最佳的處理場所。那麼,處理點擊的最佳位置在哪裡?
Adapter會在檢視畫面中顯示資料項目,因此您可以在轉接器中處理點擊。不過,轉接器的工作是調整資料以便顯示,而不是處理應用程式邏輯。- 您通常應在
ViewModel中處理點擊,因為ViewModel可存取資料和邏輯,以決定回應點擊的後續作業。
步驟 1:建立點擊事件監聽器,並從項目版面配置觸發
- 在
sleeptracker資料夾中,開啟 SleepNightAdapter.kt。 - 在檔案結尾的頂層,建立新的接聽程式類別
SleepNightListener。
class SleepNightListener() {
}- 在
SleepNightListener類別中,新增onClick()函式。當顯示清單項目的檢視區塊遭到點選時,該檢視區塊會呼叫這個onClick()函式。(您稍後會將檢視區塊的android:onClick屬性設為這個函式)。
class SleepNightListener() {
fun onClick() =
}- 將
SleepNight類型的函式引數night新增至onClick()。檢視畫面知道要顯示哪個項目,而這項資訊必須傳遞,才能處理點擊動作。
class SleepNightListener() {
fun onClick(night: SleepNight) =
}- 如要定義
onClick()的用途,請在SleepNightListener的建構函式中提供clickListener回呼,並指派給onClick()。
為處理點擊的 lambda 命名 (clickListener),有助於追蹤在類別之間傳遞的 lambda。clickListener回呼只需要night.nightId即可存取資料庫中的資料。完成的SleepNightListener類別應如以下程式碼所示。
class SleepNightListener(val clickListener: (sleepId: Long) -> Unit) {
fun onClick(night: SleepNight) = clickListener(night.nightId)
}- 開啟「list_item_sleep_night.xml」list_item_sleep_night.xml.。
- 在
data區塊中新增變數,透過資料繫結提供SleepNightListener類別。將新的<variable>命名為name,並將type設為類別com.example.android.trackmysleepquality.sleeptracker.SleepNightListener的完整名稱,如下所示。clickListener.您現在可以從這個版面配置存取SleepNightListener中的onClick()函式。
<variable
name="clickListener"
type="com.example.android.trackmysleepquality.sleeptracker.SleepNightListener" />- 如要監聽這類清單項目的任何點擊事件,請將
android:onClick屬性新增至ConstraintLayout。
使用資料繫結 lambda 將屬性設為clickListener:onClick(sleep),如下所示:
android:onClick="@{() -> clickListener.onClick(sleep)}"步驟 2:將點擊事件監聽器傳遞至檢視區塊持有者和繫結物件
- 開啟「SleepNightAdapter.kt」。
- 修改
SleepNightAdapter類別的建構函式,以接收val clickListener: SleepNightListener。轉接器繫結ViewHolder時,必須提供這個點按事件監聽器。
class SleepNightAdapter(val clickListener: SleepNightListener):
ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {- 在
onBindViewHolder()中,更新對holder.bind()的呼叫,將點擊事件監聽器也傳遞至ViewHolder。您會在函式呼叫中新增參數,因此會收到編譯器錯誤。
holder.bind(getItem(position)!!, clickListener)- 在
bind()中新增clickListener參數。方法是將游標放在錯誤上,然後按下Alt+Enter鍵 (Windows) 或Option+Enter鍵 (Mac),如下列螢幕截圖所示。
- 在
ViewHolder類別的bind()函式中,將點按事件監聽器指派給binding物件。您會看到錯誤訊息,因為您需要更新繫結物件。
binding.clickListener = clickListener- 如要更新資料繫結,請清除並重建專案。(您可能也需要撤銷快取)。因此,您已從轉接器建構函式取得點擊事件監聽器,並一路傳遞至檢視區塊持有者和繫結物件。
步驟 3:在輕觸項目時顯示訊息
您現在已加入程式碼來擷取點擊動作,但尚未實作輕觸清單項目時的動作。最簡單的回應方式是顯示含有 nightId 的訊息方塊,指出點選了某個項目。這會驗證使用者點選清單項目時,系統是否擷取並傳遞正確的 nightId。
- 開啟 SleepTrackerFragment.kt。
- 在
onCreateView()中找出adapter變數。請注意,現在會顯示錯誤,因為它預期會有點擊事件接聽程式參數。 - 將 lambda 傳遞至
SleepNightAdapter,定義點擊事件監聽器。這個簡單的 lambda 只會顯示含有nightId的浮動式訊息,如下所示。您必須匯入Toast。以下是更新後的完整定義。
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
})- 執行應用程式、輕觸項目,並確認項目顯示的訊息含有正確的
nightId。由於項目的nightId值會遞增,且應用程式會先顯示最近一晚的睡眠記錄,因此nightId值最低的項目會顯示在清單底部。
在這項工作中,您要變更點選 RecyclerView 中項目時的行為,讓應用程式導覽至詳細資料片段,顯示所點選夜晚的詳細資訊,而不是顯示訊息。
步驟 1:按一下即可前往
在這個步驟中,您不會只顯示浮動式訊息,而是變更 SleepTrackerFragment 中 onCreateView() 的點擊事件監聽器 lambda,將 nightId 傳遞至 SleepTrackerViewModel,並觸發前往 SleepDetailFragment 的導覽作業。
定義點按事件處理常式函式:
- 開啟 SleepTrackerViewModel.kt。
- 在
SleepTrackerViewModel內,接近結尾處,定義onSleepNightClicked()點按事件處理常式函式。
fun onSleepNightClicked(id: Long) {
}- 在
onSleepNightClicked()中,將_navigateToSleepDetail設為所點選睡眠夜的傳入id,藉此觸發導覽。
fun onSleepNightClicked(id: Long) {
_navigateToSleepDetail.value = id
}- 實作
_navigateToSleepDetail。如先前所述,請為導覽狀態定義private MutableLiveData。以及可公開取得的val。
private val _navigateToSleepDetail = MutableLiveData<Long>()
val navigateToSleepDetail
get() = _navigateToSleepDetail- 定義應用程式完成導覽後要呼叫的方法。命名為
onSleepDetailNavigated(),並將值設為null。
fun onSleepDetailNavigated() {
_navigateToSleepDetail.value = null
}新增程式碼來呼叫點擊處理常式:
- 開啟 SleepTrackerFragment.kt,然後向下捲動至建立配接器並定義
SleepNightListener的程式碼,顯示 Toast。
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
})- 在祝賀訊息下方新增下列程式碼,在輕觸項目時呼叫
sleepTrackerViewModel中的點擊處理常式onSleepNighClicked()。傳入nightId,讓檢視畫面模型知道要取得哪個睡眠夜間資料。這樣會發生錯誤,因為您尚未定義onSleepNightClicked()。您可以視需要保留、註解掉或刪除祝賀訊息。
sleepTrackerViewModel.onSleepNightClicked(nightId)新增程式碼來觀察點擊次數:
- 開啟「SleepTrackerFragment.kt」SleepTrackerFragment.kt。
- 在
onCreateView()中,於manager的宣告上方,新增程式碼來觀察新的navigateToSleepDetailLiveData。navigateToSleepDetail變更時,請前往SleepDetailFragment並傳入night,然後呼叫onSleepDetailNavigated()。您曾在先前的程式碼研究室中執行這項操作,因此這裡提供程式碼:
sleepTrackerViewModel.navigateToSleepDetail.observe(this, Observer { night ->
night?.let {
this.findNavController().navigate(
SleepTrackerFragmentDirections
.actionSleepTrackerFragmentToSleepDetailFragment(night))
sleepTrackerViewModel.onSleepDetailNavigated()
}
})- 執行程式碼,按一下某個項目,然後應用程式就會當機。
在繫結轉接程式中處理空值:
- 再次以偵錯模式執行應用程式。輕觸項目,然後篩選記錄檔,只顯示錯誤。系統會顯示堆疊追蹤記錄,包括類似下方的內容。
Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter item很遺憾,堆疊追蹤並未明確指出觸發這個錯誤的位置。資料繫結的缺點之一是可能會增加程式碼的偵錯難度。當您點選項目時,應用程式會當機,而唯一的新程式碼是處理點選作業。
不過,事實證明,有了這項新的點擊處理機制,繫結轉接器現在可以透過 item 的 null 值呼叫。具體來說,應用程式啟動時,LiveData 會以 null 形式啟動,因此您必須為每個轉接程式新增空值檢查。
- 在
BindingUtils.kt中,針對每個繫結介面卡,將item引數的型別變更為可為空值,並以item?.let{...}包裝主體。舉例來說,sleepQualityString的轉接器會如下所示。以同樣方式變更其他轉接頭。
@BindingAdapter("sleepQualityString")
fun TextView.setSleepQualityString(item: SleepNight?) {
item?.let {
text = convertNumericQualityToString(item.sleepQuality, context.resources)
}
}- 執行應用程式。輕觸項目,開啟詳細資料檢視畫面。
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 題
如果要讓 RecyclerView 中的項目對點擊做出回應,應在何處加入 android:onClick 屬性?請選取所有適用選項。
▢ 在顯示 RecyclerView 的版面配置檔案中,將其新增至 <androidx.recyclerview.widget.RecyclerView>
▢ 在版面配置檔案中,為資料列項目新增該屬性。如果想讓整個項目可供點選,請將該項目加到含有該列項目的父項檢視區塊中。
▢ 在版面配置檔案中,為資料列項目新增該屬性。如果想讓項目中的單一 TextView 可供點選,可將該屬性加到 <TextView> 中。
▢ 一律將其新增至 MainActivity 的版面配置檔案。
開始下一個課程:

