本程式碼研究室是 Android Kotlin 基礎課程的一部分。使用程式碼研究室逐步完成程式碼課程後,您將能充分發揮本課程的潛能。所有課程程式碼研究室清單均列於 Android Kotlin 基礎程式碼研究室到達網頁。
簡介
大多數應用程式都會使用清單及格線顯示項目,讓使用者與這些項目互動。輕觸清單中的項目並查看項目的詳細資料,是這類互動的常見用途。為此,您可以新增點擊詳細資料檢視畫面,藉此回應使用者輕觸項目的點擊事件監聽器。
在這個程式碼研究室中,您從前一版的程式碼研究室中,將睡眠追蹤應用程式的擴充版本新增到您的 RecyclerView
。
須知事項
- 運用活動、片段和檢視來建構基本的使用者介面。
- 在片段之間瀏覽,並使用
safeArgs
在片段之間傳送資料。 - 查看模型、查看模型工廠、轉換,以及
LiveData
及其觀測器。 - 如何建立
Room
資料庫、建立資料存取物件 (DAO) 以及定義實體。 - 如何使用協同程式處理資料庫和其他長時間執行的工作。
- 如何實作具有
Adapter
、ViewHolder
和項目版面配置的基本RecyclerView
。 - 如何實作
RecyclerView
的資料繫結。 - 如何建立及使用繫結轉接程式來轉換資料。
- 如何使用
GridLayoutManager
。
課程內容
- 如何讓
RecyclerView
中的項目可供點擊。導入點擊接聽器,即可在有人點擊商品時前往詳細檢視。
執行步驟
- 根據本系列中先前程式碼研究室的 TrackMySleepquality 應用程式,進行擴充。
- 在清單中加入點擊接聽器,開始聽取使用者互動。當使用者輕觸清單項目時,就會觸發特定片段的瀏覽作業,其中會詳細列出使用者點擊的項目。範例程式碼提供詳細片段的程式碼和導覽程式碼。
啟動睡眠追蹤器應用程式有兩個畫面,以片段表示,如下圖所示。
第一個畫面 (如左側所示) 為開始和停止追蹤的按鈕。螢幕會顯示使用者的某些睡眠資料。[清除] 按鈕永久刪除應用程式已收集的所有資料。第二個螢幕則是根據選取睡眠品質評分的方式。
這個應用程式使用簡化的架構搭配 UI 控制器、檢視模型、LiveData
,以及可保存睡眠資料的 Room
資料庫。
在這個程式碼研究室中,您可以新增使用者輕觸網格中的項目時進行回應的功能 (如下所示)。此畫面的程式碼 (片段、檢視模型和導覽) 提供入門應用程式,您將執行點擊處理機制。
步驟 1:取得入門應用程式
- 從 GitHub 下載 RecyclerViewClickHandler-Starter 程式碼,並在 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
以及navigateToSleepTracker
變數,以便控制按下 [關閉] 按鈕時回到SleepTrackerFragment
。getNightWithId()
函式會傳回LiveData<SleepNight>
,並定義於SleepDatabaseDao
(在database
套件中) 中。 - 在
sleepdetail
套件中,開啟並檢查SleepDetailFragment
的程式碼。請注意資料繫結的設定方式、檢視模型和導覽觀察器。 - 在
sleepdetail
套件中,開啟並檢查SleepDetailViewModelFactory
的程式碼。 - 在版面配置資料夾中檢查
fragment_sleep_detail.xml
。請注意,在<data>
標記中定義出的sleepDetailViewModel
變數後,就能從資料檢視模型來顯示各資料。
版面配置包含ConstraintLayout
,其中包含睡眠品質的ImageView
、TextView
為睡眠評分的TextView
、Button
用於關閉詳細資料片段的Button
。 - 開啟
navigation.xml
檔案。針對「sleep_tracker_fragment
」,請留意sleep_detail_fragment
的新動作。action_sleep_tracker_fragment_to_sleepDetailFragment
在這項工作中,你可以更新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() =
}
- 向
onClick()
添加SleepNight
類型的函數引數night
。資料檢視會顯示所顯示項目,因此必須傳送這項資訊來處理點擊。
class SleepNightListener() {
fun onClick(night: SleepNight) =
}
- 如要定義
onClick()
的功能,請在SleepNightListener
建構函式中提供clickListener
回呼,並將其指派給onClick()
。
將處理點擊的 lambda 命名為clickListener
,這樣有助於追蹤各類別之間的點擊。clickListener
回呼只需有night.nightId
就能存取資料庫中的資料。你完成的「SleepNightListener
」課程應如下方所示。
class SleepNightListener(val clickListener: (sleepId: Long) -> Unit) {
fun onClick(night: SleepNight) = clickListener(night.nightId)
}
- 開啟 list_item_sleep_night.xml。
- 在
data
區塊中新增變數,讓SleepNightListener
類別可透過資料繫結取得。為新的<variable>
提供name
的name
。將type
設為com.example.android.trackmysleepquality.sleeptracker.SleepNightListener
類別的完整名稱,如下所示。您現在可以透過這個版面配置存取「SleepNightListener
」中的「onClick()
」函式。
<variable
name="clickListener"
type="com.example.android.trackmysleepquality.sleeptracker.SleepNightListener" />
- 如要監聽此清單項目中任何部分的點擊,請在
ConstraintLayout
中加入android:onClick
屬性。
使用資料繫結 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)
- 將
clickListener
參數加到bind()
。方法是將遊標放到錯誤上,然後在錯誤上按下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()
click 處理常式函式。
fun onSleepNightClicked(id: Long) {
}
- 在「
onSleepNightClicked()
」中,將「_navigateToSleepDetail
」設為已點選的夜間夜晚,於_navigateToSleepDetail
觸發「導航」。
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
以顯示吐司。
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
})
- 只要在程式碼下方加入下列程式碼,即可在
sleepTrackerViewModel
輕觸項目時呼叫點擊處理常式onSleepNighClicked()
。進入nightId
,以便檢視模型判斷要睡覺的是哪一個夜晚。這會導致錯誤發生,因為您尚未定義onSleepNightClicked()
。您可以依個人需求保留、留言或刪除吐司。
sleepTrackerViewModel.onSleepNightClicked(nightId)
加入程式碼以觀察點擊次數:
- 開啟 SleepTrackerFragment.kt。
- 在
onCreateView()
的manager
宣告上方,加入程式碼來觀察新的navigateToSleepDetail
LiveData
。當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 題
您該如何加入 android:onClick
屬性,將 RecyclerView
中的項目設為回應點擊?請選取所有適用選項。
▢ 在顯示 RecyclerView
的版面配置檔案中,加入 <androidx.recyclerview.widget.RecyclerView>
▢ 將項目新增至資料列中某個項目的版面配置檔案。如要讓所有項目可供點擊,請將其新增至含有該列項目的上層資料檢視。
▢ 將項目新增至資料列中某個項目的版面配置檔案。如果你想在項目中提供一個 TextView
供使用者點選,請在 <TextView>
中新增該元素。
▢ 一律在 MainActivity
中加入版面配置檔案。
開始下一門課程: