Ten moduł Codelab jest częścią kursu Android Kotlin Fundamentals. Najwięcej korzyści przyniesie Ci ukończenie wszystkich ćwiczeń w kolejności. Wszystkie ćwiczenia z tego kursu znajdziesz na stronie docelowej kursu Android Kotlin Fundamentals.
Wprowadzenie
Większość aplikacji, które korzystają z list i siatek wyświetlających elementy, umożliwia użytkownikom interakcję z tymi elementami. Kliknięcie elementu na liście i wyświetlenie jego szczegółów to bardzo częsty przypadek użycia tego typu interakcji. Aby to zrobić, możesz dodać odbiorniki kliknięć, które reagują na kliknięcia użytkownika, wyświetlając widok szczegółowy.
W tym ćwiczeniu z programowania dodasz interakcję do aplikacji RecyclerView, rozwijając rozszerzoną wersję aplikacji do śledzenia snu z poprzedniej serii ćwiczeń z programowania.
Co warto wiedzieć
- Tworzenie podstawowego interfejsu użytkownika za pomocą aktywności, fragmentów i widoków.
- przechodzenie między fragmentami i używanie
safeArgsdo przekazywania danych między fragmentami; - Wyświetlanie modeli, fabryk modeli, przekształceń i
LiveDataoraz ich obserwatorów. - Jak utworzyć bazę danych
Room, obiekt dostępu do danych (DAO) i zdefiniować encje. - Jak używać korutyn do obsługi baz danych i innych długotrwałych zadań.
- Jak wdrożyć podstawowy
RecyclerViewzAdapter,ViewHolderi układem elementów. - Jak zaimplementować wiązanie danych w przypadku
RecyclerView. - Jak tworzyć i używać adapterów powiązań do przekształcania danych.
- Instrukcje korzystania z
GridLayoutManager.
Czego się nauczysz
- Jak sprawić, aby elementy w
RecyclerViewbyły klikalne. Zaimplementuj detektor kliknięć, aby po kliknięciu elementu przejść do widoku szczegółów.
Jakie zadania wykonasz
- Skorzystaj z rozszerzonej wersji aplikacji TrackMySleepQuality z poprzedniego laboratorium w tej serii.
- Dodaj do listy odbiornik kliknięć i zacznij nasłuchiwać interakcji użytkownika. Gdy użytkownik kliknie element listy, nastąpi przejście do fragmentu ze szczegółami klikniętego elementu. Kod początkowy zawiera kod fragmentu szczegółów oraz kod nawigacji.
Aplikacja do śledzenia snu ma 2 ekrany reprezentowane przez fragmenty, jak pokazano na ilustracji poniżej.
|
|
Pierwszy ekran, widoczny po lewej stronie, zawiera przyciski rozpoczynania i zatrzymywania śledzenia. Na ekranie wyświetlają się niektóre dane dotyczące snu użytkownika. Przycisk Wyczyść trwale usuwa wszystkie dane zebrane przez aplikację na temat użytkownika. Drugi ekran, widoczny po prawej stronie, służy do wybierania oceny jakości snu.
Ta aplikacja korzysta z uproszczonej architektury z kontrolerem interfejsu, modelem widoku i LiveData oraz bazą danych Room do przechowywania danych o śnie.

W tym laboratorium dodasz możliwość reagowania na kliknięcie elementu w siatce przez użytkownika, co spowoduje wyświetlenie ekranu szczegółów podobnego do tego poniżej. Kod tego ekranu (fragment, model widoku i nawigacja) jest dostępny w aplikacji startowej. Ty musisz zaimplementować mechanizm obsługi kliknięć.

Krok 1. Pobierz aplikację startową
- Pobierz kod początkowy RecyclerViewClickHandler z GitHuba i otwórz projekt w Android Studio.
- Skompiluj i uruchom aplikację do śledzenia snu.
[Opcjonalnie] Zaktualizuj aplikację, jeśli chcesz używać aplikacji z poprzedniego ćwiczenia z programowania
Jeśli w tym laboratorium chcesz korzystać z aplikacji startowej udostępnionej w GitHubie, przejdź do następnego kroku.
Jeśli chcesz nadal używać własnej aplikacji do śledzenia snu, którą utworzono w poprzednim laboratorium, postępuj zgodnie z poniższymi instrukcjami, aby zaktualizować istniejącą aplikację i dodać do niej kod fragmentu ekranu szczegółów.
- Nawet jeśli kontynuujesz pracę nad istniejącą aplikacją, pobierz z GitHuba kod początkowy RecyclerViewClickHandler, aby móc skopiować pliki.
- Skopiuj wszystkie pliki z pakietu
sleepdetail. - W folderze
layoutskopiuj plikfragment_sleep_detail.xml. - Skopiuj zaktualizowaną zawartość pliku
navigation.xml, która dodaje nawigację dlasleep_detail_fragment. - W
databasepakiecie wSleepDatabaseDaododaj nową metodę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>- W pliku
res/values/stringsdodaj ten zasób ciągu znaków:
<string name="close">Close</string>- Wyczyść i ponownie skompiluj aplikację, aby zaktualizować wiązanie danych.
Krok 2. Sprawdź kod ekranu szczegółów snu
W tym laboratorium kodowania zaimplementujesz moduł obsługi kliknięć, który przechodzi do fragmentu zawierającego szczegóły klikniętej nocy snu. Kod początkowy zawiera już fragment i graf nawigacji dla tego SleepDetailFragment, ponieważ jest to dość dużo kodu, a fragmenty i nawigacja nie są częścią tego laboratorium. Zapoznaj się z tym kodem:
- W aplikacji znajdź pakiet
sleepdetail. Ten pakiet zawiera fragment, model widoku i fabrykę modelu widoku dla fragmentu, który wyświetla szczegóły dotyczące jednej nocy snu. - W pakiecie
sleepdetailotwórz i sprawdź kodSleepDetailViewModel. Ten model widoku przyjmuje klucz dlaSleepNighti obiekt DAO w konstruktorze.
W treści klasy znajduje się kod, który pobieraSleepNightdla danego klucza, oraz zmiennanavigateToSleepTracker, która umożliwia powrót doSleepTrackerFragmentpo naciśnięciu przycisku Zamknij.
FunkcjagetNightWithId()zwraca wartośćLiveData<SleepNight>i jest zdefiniowana wSleepDatabaseDao(w pakieciedatabase). - W pakiecie
sleepdetailotwórz i sprawdź kodSleepDetailFragment. Zwróć uwagę na konfigurację powiązania danych, model widoku i obserwatora nawigacji. - W pakiecie
sleepdetailotwórz i sprawdź kodSleepDetailViewModelFactory. - W folderze układu sprawdź
fragment_sleep_detail.xml. Zwróć uwagę na zmiennąsleepDetailViewModelzdefiniowaną w tagu<data>, aby uzyskać dane do wyświetlenia w każdym widoku z modelu widoku.
Układ zawiera elementConstraintLayout, który zawiera elementImageViewdla jakości snu, elementTextViewdla oceny jakości, elementTextViewdla długości snu i elementButtondo zamknięcia fragmentu szczegółów. - Otwórz plik
navigation.xml. Zwróć uwagę na nowe działanie w przypadkusleep_detail_fragmentwsleep_tracker_fragment.
Nowe działanie,action_sleep_tracker_fragment_to_sleepDetailFragment, to przejście z fragmentu śledzenia snu na ekran szczegółów.
W tym zadaniu zaktualizujesz RecyclerView, aby reagował na kliknięcia użytkownika, wyświetlając ekran szczegółów klikniętego elementu.
Odbieranie kliknięć i ich obsługa to zadanie dwuetapowe: najpierw musisz nasłuchiwać i odbierać kliknięcia oraz określać, który element został kliknięty. Następnie musisz zareagować na kliknięcie, wykonując działanie.
Gdzie najlepiej dodać odbiornik kliknięć w tej aplikacji?
SleepTrackerFragmentzawiera wiele widoków, więc nasłuchiwanie zdarzeń kliknięcia na poziomie fragmentu nie powie Ci, który element został kliknięty. Nie powie Ci nawet, czy kliknięto element, czy jeden z innych elementów interfejsu.- Słuchając na poziomie
RecyclerView, trudno jest dokładnie określić, który element na liście kliknął użytkownik. - Najlepszym sposobem na uzyskanie informacji o klikniętym elemencie jest użycie obiektu
ViewHolder, ponieważ reprezentuje on jeden element listy.
Chociaż ViewHolder to świetne miejsce do nasłuchiwania kliknięć, zwykle nie jest to odpowiednie miejsce do ich obsługi. Gdzie najlepiej obsługiwać kliknięcia?
Adapterwyświetla elementy danych w widokach, dzięki czemu możesz obsługiwać kliknięcia w adapterze. Zadaniem adaptera jest jednak dostosowywanie danych do wyświetlania, a nie obsługa logiki aplikacji.- Kliknięcia należy zwykle obsługiwać w
ViewModel, ponieważViewModelma dostęp do danych i logiki, które pozwalają określić, co ma się stać w odpowiedzi na kliknięcie.
Krok 1. Utwórz odbiornik kliknięć i wywołaj go z układu elementu
- W folderze
sleeptrackerotwórz plik SleepNightAdapter.kt. - Na końcu pliku, na najwyższym poziomie, utwórz nową klasę odbiornika
SleepNightListener.
class SleepNightListener() {
}- W klasie
SleepNightListenerdodaj funkcjęonClick(). Gdy klikniesz widok, który wyświetla element listy, wywoła on tę funkcjęonClick(). (Właściwośćandroid:onClickwidoku danych ustawisz później na tę funkcję).
class SleepNightListener() {
fun onClick() =
}- Dodaj argument funkcji
nighttypuSleepNightdoonClick(). Widok wie, który element wyświetla, i musi przekazać te informacje, aby obsłużyć kliknięcie.
class SleepNightListener() {
fun onClick(night: SleepNight) =
}- Aby zdefiniować działanie
onClick(), podaj wywołanie zwrotneclickListenerw konstruktorzeSleepNightListeneri przypisz je doonClick().
Nadanie wyrażeniu lambda obsługującemu kliknięcie nazwyclickListenerułatwia śledzenie go podczas przekazywania między klasami. Wywołanie zwrotneclickListenerpotrzebuje tylkonight.nightId, aby uzyskać dostęp do danych z bazy danych. Ukończona klasaSleepNightListenerpowinna wyglądać jak poniższy kod.
class SleepNightListener(val clickListener: (sleepId: Long) -> Unit) {
fun onClick(night: SleepNight) = clickListener(night.nightId)
}- Otwórz plik list_item_sleep_night.xml..
- W bloku
datadodaj nową zmienną, aby udostępnić klasęSleepNightListenerza pomocą powiązania danych. Nadaj nowemu<variable>nameo wartościclickListener.. Ustawtypena pełną nazwę klasycom.example.android.trackmysleepquality.sleeptracker.SleepNightListener, jak pokazano poniżej. W tym układzie możesz teraz uzyskać dostęp do funkcjionClick()wSleepNightListener.
<variable
name="clickListener"
type="com.example.android.trackmysleepquality.sleeptracker.SleepNightListener" />- Aby nasłuchiwać kliknięć w dowolnej części elementu listy, dodaj atrybut
android:onClickdo elementuConstraintLayout.
Ustaw atrybut naclickListener:onClick(sleep)za pomocą lambdy wiązania danych, jak pokazano poniżej:
android:onClick="@{() -> clickListener.onClick(sleep)}"Krok 2. Przekaż odbiornik kliknięć do uchwytu widoku i obiektu powiązania
- Otwórz plik SleepNightAdapter.kt.
- Zmodyfikuj konstruktor klasy
SleepNightAdapter, aby otrzymywał obiektval clickListener: SleepNightListener. Gdy adapter powiążeViewHolder, będzie musiał przekazać mu ten odbiornik kliknięć.
class SleepNightAdapter(val clickListener: SleepNightListener):
ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {- W pliku
onBindViewHolder()zaktualizuj wywołanie funkcjiholder.bind(), aby przekazywać detektor kliknięć do funkcjiViewHolder. Otrzymasz błąd kompilatora, ponieważ do wywołania funkcji został dodany parametr.
holder.bind(getItem(position)!!, clickListener)- Dodaj parametr
clickListenerdobind(). Aby to zrobić, umieść kursor na błędzie i naciśnijAlt+Enter(Windows) lubOption+Enter(Mac), jak pokazano na zrzucie ekranu poniżej.
- W klasie
ViewHolder, w funkcjibind()przypisz detektor kliknięć do obiektubinding. Wyświetla się błąd, ponieważ musisz zaktualizować obiekt powiązania.
binding.clickListener = clickListener- Aby zaktualizować powiązanie danych, wyczyść i ponownie skompiluj projekt. (Może być też konieczne unieważnienie pamięci podręcznych). Pobierasz więc odbiornik kliknięć z konstruktora adaptera i przekazujesz go do obiektu widoku i obiektu powiązania.
Krok 3. Wyświetlaj powiadomienie po dotknięciu elementu
Masz już kod, który rejestruje kliknięcie, ale nie masz jeszcze kodu, który określa, co się stanie po kliknięciu elementu listy. Najprostsza odpowiedź to wyświetlenie powiadomienia z symbolem nightId po kliknięciu elementu. Dzięki temu sprawdzisz, czy po kliknięciu elementu listy rejestrowany i przekazywany jest prawidłowy nightId.
- Otwórz plik SleepTrackerFragment.kt.
- W sekcji
onCreateView()znajdź zmiennąadapter. Zwróć uwagę, że wyświetla błąd, ponieważ oczekuje teraz parametru odbiornika kliknięć. - Zdefiniuj odbiornik kliknięć, przekazując lambdę do funkcji
SleepNightAdapter. Ta prosta lambda wyświetla tylko powiadomienie z wartościąnightId, jak pokazano poniżej. Musisz zaimportowaćToast. Poniżej znajdziesz pełną, zaktualizowaną definicję.
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
})- Uruchom aplikację, kliknij elementy i sprawdź, czy wyświetla się komunikat z prawidłowym
nightId. Ponieważ elementy mają rosnące wartościnightId, a aplikacja wyświetla najpierw najnowszą noc, element z najniższą wartościąnightIdznajduje się na dole listy.
W tym zadaniu zmienisz zachowanie po kliknięciu elementu w RecyclerView, tak aby zamiast wyświetlać powiadomienie, aplikacja przechodziła do fragmentu ze szczegółami, który zawiera więcej informacji o klikniętej nocy.
Krok 1. Przejdź do kliknięcia
W tym kroku zamiast wyświetlać tylko powiadomienie, zmienisz lambdę odbiornika kliknięć w pliku onCreateView() elementu SleepTrackerFragment, aby przekazywać nightId do SleepTrackerViewModel i wywoływać nawigację do SleepDetailFragment.
Zdefiniuj funkcję obsługi kliknięcia:
- Otwórz plik SleepTrackerViewModel.kt.
- W obszarze
SleepTrackerViewModel, pod koniec, zdefiniuj funkcję obsługi kliknięciaonSleepNightClicked().
fun onSleepNightClicked(id: Long) {
}- W
onSleepNightClicked()wywołaj nawigację, ustawiając_navigateToSleepDetailna przekazanyidklikniętej nocy snu.
fun onSleepNightClicked(id: Long) {
_navigateToSleepDetail.value = id
}- Użyj mechanizmu
_navigateToSleepDetail. Podobnie jak wcześniej zdefiniujprivate MutableLiveDatadla stanu nawigacji. oraz publiczny getterval.
private val _navigateToSleepDetail = MutableLiveData<Long>()
val navigateToSleepDetail
get() = _navigateToSleepDetail- Określ metodę wywoływaną po zakończeniu nawigacji w aplikacji. Nazwij go
onSleepDetailNavigated()i ustaw jego wartość nanull.
fun onSleepDetailNavigated() {
_navigateToSleepDetail.value = null
}Dodaj kod, aby wywołać funkcję obsługi kliknięć:
- Otwórz plik SleepTrackerFragment.kt i przewiń w dół do kodu, który tworzy adapter i definiuje
SleepNightListener, aby wyświetlić powiadomienie.
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
})- Dodaj poniższy kod pod powiadomieniem, aby wywołać procedurę obsługi kliknięć
onSleepNighClicked()wsleepTrackerViewModelpo kliknięciu elementu. Przekaż wnightId, aby model widoku wiedział, którą noc snu ma pobrać. W takim przypadku pojawi się błąd, ponieważ nie zdefiniowano jeszcze zmiennejonSleepNightClicked(). Możesz zachować, wykomentować lub usunąć powiadomienie.
sleepTrackerViewModel.onSleepNightClicked(nightId)Dodaj kod, aby obserwować kliknięcia:
- Otwórz plik SleepTrackerFragment.kt.
- W
onCreateView(), tuż nad deklaracjąmanager, dodaj kod, aby obserwować nowynavigateToSleepDetailLiveData. Gdy zmieni sięnavigateToSleepDetail, przejdź doSleepDetailFragment, przekazującnight, a następnie wywołajonSleepDetailNavigated(). Ponieważ robiliśmy to już w poprzednich ćwiczeniach, oto kod:
sleepTrackerViewModel.navigateToSleepDetail.observe(this, Observer { night ->
night?.let {
this.findNavController().navigate(
SleepTrackerFragmentDirections
.actionSleepTrackerFragmentToSleepDetailFragment(night))
sleepTrackerViewModel.onSleepDetailNavigated()
}
})- Uruchamiasz kod, klikasz element i … aplikacja się zawiesza.
Obsługa wartości null w adapterach powiązań:
- Uruchom ponownie aplikację w trybie debugowania. Kliknij element i odfiltruj logi, aby wyświetlić błędy. Wyświetli ślad stosu, który będzie zawierać informacje podobne do tych poniżej.
Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter itemNiestety zrzut stosu nie wskazuje, gdzie występuje ten błąd. Jedną z wad wiązania danych jest to, że może ono utrudniać debugowanie kodu. Aplikacja ulega awarii po kliknięciu elementu, a jedyny nowy kod służy do obsługi kliknięcia.
Okazuje się jednak, że dzięki temu nowemu mechanizmowi obsługi kliknięć adaptery powiązań mogą być wywoływane z wartością null dla item. W szczególności po uruchomieniu aplikacji zmienna LiveData ma wartość null, więc do każdego z adapterów musisz dodać sprawdzanie wartości null.
- W przypadku każdego adaptera powiązania w
BindingUtils.ktzmień typ argumentuitemna dopuszczający wartość null i otocz treść znakiemitem?.let{...}. Na przykład adapter dosleepQualityStringbędzie wyglądać tak: Podobnie zmień pozostałe adaptery.
@BindingAdapter("sleepQualityString")
fun TextView.setSleepQualityString(item: SleepNight?) {
item?.let {
text = convertNumericQualityToString(item.sleepQuality, context.resources)
}
}- Uruchom aplikację. Kliknij element, aby otworzyć widok szczegółów.
Projekt Android Studio: RecyclerViewClickHandler.
Aby elementy w RecyclerView reagowały na kliknięcia, dołącz do nich w ViewHolder detektory kliknięć i obsługuj kliknięcia w ViewModel.
Aby elementy w RecyclerView reagowały na kliknięcia, musisz wykonać te czynności:
- Utwórz klasę detektora, która przyjmuje wyrażenie lambda i przypisuje je do funkcji
onClick().
class SleepNightListener(val clickListener: (sleepId: Long) -> Unit) {
fun onClick(night: SleepNight) = clickListener(night.nightId)
}- Ustaw odbiornik kliknięć w widoku.
android:onClick="@{() -> clickListener.onClick(sleep)}"- Przekaż odbiornik kliknięć do konstruktora adaptera, do elementu View Holder i dodaj go do obiektu powiązania.
class SleepNightAdapter(val clickListener: SleepNightListener):
ListAdapter<DataItem, RecyclerView.ViewHolder>(SleepNightDiffCallback()holder.bind(getItem(position)!!, clickListener)binding.clickListener = clickListener- W fragmencie, który wyświetla widok cykliczny, w miejscu tworzenia adaptera zdefiniuj detektor kliknięć, przekazując do adaptera wyrażenie lambda.
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
sleepTrackerViewModel.onSleepNightClicked(nightId)
})- Zaimplementuj moduł obsługi kliknięć w modelu widoku. W przypadku kliknięć elementów listy zwykle powoduje to przejście do fragmentu ze szczegółami.
Kurs Udacity:
Dokumentacja dla deweloperów aplikacji na Androida:
W tej sekcji znajdziesz listę możliwych zadań domowych dla uczniów, którzy wykonują ten moduł w ramach kursu prowadzonego przez instruktora. Nauczyciel musi:
- W razie potrzeby przypisz pracę domową.
- Poinformuj uczniów, jak przesyłać projekty.
- Oceń zadania domowe.
Instruktorzy mogą korzystać z tych sugestii w dowolnym zakresie i mogą zadawać inne zadania domowe, które uznają za odpowiednie.
Jeśli wykonujesz ten kurs samodzielnie, możesz użyć tych zadań domowych, aby sprawdzić swoją wiedzę.
Odpowiedz na te pytania
Pytanie 1
Załóżmy, że Twoja aplikacja zawiera RecyclerView, która wyświetla produkty na liście zakupów. Aplikacja definiuje też klasę odbiornika kliknięć:
class ShoppingListItemListener(val clickListener: (itemId: Long) -> Unit) {
fun onClick(cartItem: CartItem) = clickListener(cartItem.itemId)
}Jak udostępnić ShoppingListItemListener na potrzeby powiązania danych? Wybierz jedną z opcji.
▢ W pliku układu, który zawiera element RecyclerView wyświetlający listę zakupów, dodaj zmienną <data> dla elementu ShoppingListItemListener.
▢ W pliku układu, który definiuje układ pojedynczego wiersza na liście zakupów, dodaj zmienną <data> dla ShoppingListItemListener.
▢ W klasie ShoppingListItemListener dodaj funkcję, która włączy wiązanie danych:
fun onBinding (cartItem: CartItem) {dataBindingEnable(true)}▢ W klasie ShoppingListItemListener, wewnątrz funkcji onClick(), dodaj wywołanie, aby włączyć wiązanie danych:
fun onClick(cartItem: CartItem) = {
clickListener(cartItem.itemId)
dataBindingEnable(true)
}Pytanie 2
Gdzie należy dodać atrybut android:onClick, aby elementy w RecyclerView reagowały na kliknięcia? Zaznacz wszystkie pasujące opcje.
▢ W pliku układu, który wyświetla RecyclerView, dodaj go do <androidx.recyclerview.widget.RecyclerView>.
▢ Dodaj go do pliku układu elementu w wierszu. Jeśli chcesz, aby cały element był klikalny, dodaj go do widoku nadrzędnego, który zawiera elementy w wierszu.
▢ Dodaj go do pliku układu elementu w wierszu. Jeśli chcesz, aby pojedynczy znak TextView w elemencie był klikalny, dodaj go do elementu <TextView>.
▢ Zawsze dodawaj go do pliku układu dla MainActivity.
Otwórz następną lekcję:

