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
Z tego ćwiczenia w Codelabs dowiesz się, jak używać RecyclerView do wyświetlania list elementów. Na podstawie aplikacji do śledzenia snu z poprzedniej serii ćwiczeń z programowania dowiesz się, jak lepiej i wszechstronniej wyświetlać dane za pomocą RecyclerView z zalecaną architekturą.
Co warto wiedzieć
Musisz znać:
- 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; - Używanie modeli widoku, fabryk modeli widoku, przekształceń i
LiveDataoraz ich obserwatorów. - Tworzenie bazy danych
Room, tworzenie obiektu DAO i definiowanie jednostek. - Używanie korutyn do zadań związanych z bazą danych i innych długotrwałych zadań.
Czego się nauczysz
- Jak używać
RecyclerViewzAdapteriViewHolderdo wyświetlania listy produktów.
Jakie zadania wykonasz
- Zmień aplikację TrackMySleepQuality z poprzedniej lekcji, aby wyświetlać dane o jakości snu za pomocą elementu
RecyclerView.
W tym ćwiczeniu utworzysz część RecyclerView aplikacji, która śledzi jakość snu. Aplikacja używa bazy danych Room do przechowywania danych o śnie na przestrzeni czasu.
Aplikacja do śledzenia snu ma 2 ekrany reprezentowane przez fragmenty, jak pokazano na rysunku poniżej.

Na pierwszym ekranie (po lewej) znajdują się przyciski rozpoczynania i zatrzymywania śledzenia. Na tym ekranie wyświetlają się też wszystkie 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 ViewModel i LiveData. Aplikacja korzysta też z bazy danych Room, aby dane o śnie były trwałe.

Lista nocy snu wyświetlana na pierwszym ekranie jest funkcjonalna, ale nie wygląda zbyt dobrze. Aplikacja używa złożonego formatowania do tworzenia ciągów tekstowych dla widoku tekstu i liczb dla jakości. Poza tym ten projekt nie jest skalowalny. Po rozwiązaniu wszystkich tych problemów w tym laboratorium kodowania aplikacja będzie miała taką samą funkcjonalność, a ekran główny będzie wyglądać tak:

Wyświetlanie listy lub siatki danych to jedno z najczęstszych zadań interfejsu w Androidzie. Listy mogą być proste lub bardzo złożone. Lista widoków tekstowych może zawierać proste dane, np. listę zakupów. Złożona lista, np. lista miejsc na wakacje z adnotacjami, może wyświetlać użytkownikowi wiele szczegółów w przewijanej siatce z nagłówkami.
Aby obsługiwać wszystkie te przypadki użycia, Android udostępnia RecyclerView.

Największą zaletą RecyclerView jest to, że jest bardzo wydajny w przypadku dużych list:
- Domyślnie
RecyclerViewprzetwarza lub rysuje tylko elementy, które są obecnie widoczne na ekranie. Jeśli na przykład lista zawiera tysiąc elementów, ale widocznych jest tylko 10, funkcjaRecyclerViewwykonuje tylko tyle pracy, ile potrzeba do narysowania 10 elementów na ekranie. Gdy użytkownik przewija stronę,RecyclerViewokreśla, które nowe elementy powinny pojawić się na ekranie, i wykonuje tylko tyle pracy, ile jest potrzebne do ich wyświetlenia. - Gdy element zniknie z ekranu, jego wyświetlenia są ponownie wykorzystywane. Oznacza to, że element jest wypełniony nowymi treściami, które przewijają się na ekranie. Takie
RecyclerViewzachowanie pozwala zaoszczędzić dużo czasu przetwarzania i zapewnia płynne przewijanie list. - Gdy element się zmieni, zamiast ponownie rysować całą listę,
RecyclerViewmoże zaktualizować tylko ten element. To ogromny wzrost wydajności podczas wyświetlania list złożonych elementów.
W sekwencji pokazanej poniżej widać, że jeden widok został wypełniony danymi ABC. Gdy ten widok zniknie z ekranu, RecyclerView ponownie użyje go do wyświetlenia nowych danych, XYZ.
Wzorzec adaptera
Jeśli podróżujesz między krajami, w których używa się różnych gniazdek elektrycznych, prawdopodobnie wiesz, jak podłączyć urządzenia do gniazdek za pomocą adaptera. Adapter umożliwia przekształcenie jednego typu wtyczki w inny, czyli tak naprawdę przekształcenie jednego interfejsu w inny.
Wzorzec adaptera w inżynierii oprogramowania pomaga obiektowi współpracować z innym interfejsem API. RecyclerView używa adaptera do przekształcania danych aplikacji w formę, którą RecyclerView może wyświetlać, bez zmiany sposobu przechowywania i przetwarzania danych przez aplikację. W przypadku aplikacji do śledzenia snu tworzysz adapter, który dostosowuje dane z bazy danych Room do formatu, który RecyclerView potrafi wyświetlić, bez zmiany ViewModel.
Implementowanie widoku RecyclerView

Aby wyświetlić dane w RecyclerView, potrzebujesz tych elementów:
- Dane do wyświetlenia.
- Instancja
RecyclerViewzdefiniowana w pliku układu, która będzie pełnić rolę kontenera widoków. - Układ jednego elementu danych.
Jeśli wszystkie elementy listy wyglądają tak samo, możesz użyć tego samego układu dla wszystkich, ale nie jest to obowiązkowe. Układ elementu musi być utworzony oddzielnie od układu fragmentu, aby można było utworzyć i wypełnić danymi jeden widok elementu naraz. - Menedżer układu.
Menedżer układu odpowiada za organizację (układ) komponentów interfejsu w widoku. - Obiekt View Holder.
Obiekt View Holder rozszerza klasęViewHolder. Zawiera informacje o widoku służące do wyświetlania jednego elementu z układu elementu. Obiekty View Holder dodają też informacje, któreRecyclerViewwykorzystuje do efektywnego przesuwania widoków po ekranie. - Adapter.
Adapter łączy dane zRecyclerView. Dostosowuje dane, aby można je było wyświetlić wViewHolder.RecyclerViewużywa adaptera, aby określić, jak wyświetlać dane na ekranie.
W tym zadaniu dodasz do pliku układu element RecyclerView i skonfigurujesz element Adapter, aby udostępniać dane o śnie elementowi RecyclerView.
Krok 1. Dodaj element RecyclerView z elementem LayoutManager
W tym kroku zastąpisz ScrollView elementem RecyclerView w pliku fragment_sleep_tracker.xml.
- Pobierz aplikację RecyclerViewFundamentals-Starter z GitHub.
- Skompiluj i uruchom aplikację. Zwróć uwagę, jak dane są wyświetlane jako zwykły tekst.
- Otwórz plik układu
fragment_sleep_tracker.xmlna karcie Design w Android Studio. - W panelu Drzewo komponentów usuń
ScrollView. Spowoduje to też usunięcieTextViewznajdującego się wScrollView. - W panelu Paleta przewiń listę typów komponentów po lewej stronie, aby znaleźć Kontenery, a następnie wybierz tę opcję.
- Przeciągnij
RecyclerViewz panelu Paleta do panelu Drzewo komponentów. UmieśćRecyclerViewwConstraintLayout.

- Jeśli otworzy się okno z pytaniem, czy chcesz dodać zależność, kliknij OK, aby Android Studio dodało zależność
recyclerviewdo pliku Gradle. Może to potrwać kilka sekund, a potem aplikacja zostanie zsynchronizowana.

- Otwórz plik modułu
build.gradle, przewiń go na koniec i zwróć uwagę na nową zależność, która wygląda podobnie do poniższego kodu:
implementation 'androidx.recyclerview:recyclerview:1.0.0'
- Wróć do
fragment_sleep_tracker.xml. - Na karcie Tekst znajdź kod
RecyclerViewwidoczny poniżej:
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent" />- Nadaj
RecyclerViewido wartościsleep_list.
android:id="@+id/sleep_list"- Ustaw
RecyclerViewtak, aby zajmował pozostałą część ekranu wConstraintLayout. Aby to zrobić, przypisz górną krawędź elementuRecyclerViewdo przycisku Start, dolną do przycisku Wyczyść, a każdą z boków do elementu nadrzędnego. Ustaw szerokość i wysokość układu na 0 dp w edytorze układu lub w XML, używając tego kodu:
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/clear_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/stop_button"- Dodaj menedżera układu do pliku XML
RecyclerView. Każdy elementRecyclerViewwymaga menedżera układu, który określa sposób rozmieszczenia elementów na liście. Android udostępniaLinearLayoutManager, który domyślnie układa elementy w pionowej liście wierszy o pełnej szerokości.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"- Przejdź na kartę Projekt. Zauważysz, że dodane ograniczenia spowodowały rozszerzenie elementu
RecyclerView, aby wypełnić dostępną przestrzeń.

Krok 2. Utwórz układ elementu listy i uchwyt widoku tekstu
RecyclerView to tylko kontener. W tym kroku utworzysz układ i infrastrukturę elementów, które mają być wyświetlane w RecyclerView.
Aby jak najszybciej uzyskać działający RecyclerView, najpierw użyjesz prostego elementu listy, który wyświetla tylko jakość snu w postaci liczby. W tym celu potrzebujesz uchwytu widoku TextItemViewHolder. Potrzebujesz też widoku, czyli TextView, do wyświetlania danych. (W dalszej części dowiesz się więcej o uchwytach widoku i o tym, jak wyświetlać wszystkie dane o śnie).
- Utwórz plik układu o nazwie
text_item_view.xml. Nie ma znaczenia, jakiego elementu głównego użyjesz, ponieważ zastąpisz kod szablonu. - W
text_item_view.xmlusuń cały podany kod. - Dodaj
TextViewz wypełnieniem16dpna początku i na końcu oraz rozmiarem tekstu24sp. Szerokość ma być dopasowana do elementu nadrzędnego, a wysokość do treści. Ten widok jest wyświetlany wRecyclerView, więc nie musisz umieszczać go wViewGroup.
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:textSize="24sp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />- Otwórz pokój
Util.kt. Przewiń do końca i dodaj definicję pokazaną poniżej, która tworzy klasęTextItemViewHolder. Umieść kod na dole pliku, po ostatnim nawiasie klamrowym zamykającym. Kod umieszczamy wUtil.kt, ponieważ ten uchwyt widoku jest tymczasowy i później go zastąpimy.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)- Jeśli pojawi się prośba, zaimportuj
android.widget.TextViewiandroidx.recyclerview.widget.RecyclerView.
Krok 3. Utwórz SleepNightAdapter
Głównym zadaniem podczas wdrażania RecyclerView jest utworzenie adaptera. Masz prosty uchwyt widoku dla widoku elementu i układ dla każdego elementu. Możesz teraz utworzyć adapter. Adapter tworzy uchwyt widoku i wypełnia go danymi, które mają być wyświetlane w RecyclerView.
- W pakiecie
sleeptrackerutwórz nową klasę Kotlin o nazwieSleepNightAdapter. - Spraw, aby klasa
SleepNightAdapterrozszerzała klasęRecyclerView.Adapter. Klasa ta nosi nazwęSleepNightAdapter, ponieważ dostosowuje obiektSleepNightdo formatu, który może być używany przezRecyclerView. Adapter musi wiedzieć, którego uchwytu widoku użyć, więc przekażTextItemViewHolder. Gdy pojawi się odpowiedni komunikat, zaimportuj niezbędne komponenty. Następnie zobaczysz błąd, ponieważ musisz zaimplementować obowiązkowe metody.
class SleepNightAdapter: RecyclerView.Adapter<TextItemViewHolder>() {}- Na najwyższym poziomie
SleepNightAdapterutwórz zmiennąlistOfSleepNight, która będzie przechowywać dane.
var data = listOf<SleepNight>()- W
SleepNightAdapterzastąpgetItemCount(), aby zwrócić rozmiar listy nocy snu wdata.RecyclerViewmusi wiedzieć, ile elementów ma adapter, aby je wyświetlić. W tym celu wywołuje funkcjęgetItemCount().
override fun getItemCount() = data.size- W pliku
SleepNightAdapterzastąp funkcjęonBindViewHolder(), jak pokazano poniżej.
FunkcjaonBindViewHolder()jest wywoływana przezRecyclerVieww celu wyświetlenia danych jednego elementu listy w określonej pozycji. MetodaonBindViewHolder()przyjmuje więc 2 argumenty: uchwyt widoku i pozycję danych do powiązania. W przypadku tej aplikacji posiadaczem jestTextItemViewHolder, a pozycja to pozycja na liście.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}- W
onBindViewHolder()utwórz zmienną dla jednego elementu w danym miejscu w danych.
val item = data[position]- Utworzony przez Ciebie
ViewHolderma usługę o nazwietextView. W sekcjionBindViewHolder()ustawtextelementutextViewna liczbę odpowiadającą jakości snu. Ten kod wyświetla tylko listę liczb, ale ten prosty przykład pokazuje, jak adapter umieszcza dane w obiekcie widoku i na ekranie.
holder.textView.text = item.sleepQuality.toString()- W
SleepNightAdapterzastąp i wdrożonCreateViewHolder(), który jest wywoływany, gdyRecyclerViewpotrzebuje uchwytu widoku do reprezentowania elementu.
Ta funkcja przyjmuje 2 parametry i zwraca wartośćViewHolder. Parametrparent, czyli grupa widoków, która zawiera uchwyt widoku, jest zawsze parametremparent.RecyclerViewParametrviewTypejest używany, gdy w tym samymRecyclerViewjest kilka widoków. Jeśli na przykład umieścisz listę widoków tekstu, obraz i film w tym samymRecyclerView, funkcjaonCreateViewHolder()będzie musiała wiedzieć, jakiego typu widoku użyć.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}- W
onCreateViewHolder()utwórz instancjęLayoutInflater.
Inflator układu wie, jak tworzyć widoki z układów XML.contextzawiera informacje o tym, jak prawidłowo rozwinąć widok. W adapterze widoku recyklera zawsze przekazujesz kontekst grupy widokówparent, czyliRecyclerView.
val layoutInflater = LayoutInflater.from(parent.context)- W
onCreateViewHolder()utwórzview, prosząclayoutinflatero napompowanie go.
Przekaż układ XML widoku iparentgrupę widoków. Trzeci argument (logiczny) toattachToRoot. Ten argument musi mieć wartośćfalse, ponieważRecyclerViewdodaje ten element do hierarchii widoków w odpowiednim momencie.
val view = layoutInflater
.inflate(R.layout.text_item_view, parent, false) as TextView- W
onCreateViewHolder()zwróćTextItemViewHolderkupiony za pomocąview.
return TextItemViewHolder(view)- Adapter musi informować
RecyclerViewo zmianiedata, ponieważRecyclerViewnie ma żadnych informacji o danych. Zna tylko elementy widoku, które przekazuje mu adapter.
Aby poinformowaćRecyclerViewo zmianie wyświetlanych danych, dodaj niestandardowy setter do zmiennejdatau góry klasySleepNightAdapter. W funkcji ustawiającej przypisz zmiennejdatanową wartość, a następnie wywołaj funkcjęnotifyDataSetChanged(), aby ponownie narysować listę z nowymi danymi.
var data = listOf<SleepNight>()
set(value) {
field = value
notifyDataSetChanged()
}Krok 4. Poinformuj RecyclerView o adapterze
RecyclerView musi znać adapter, którego ma użyć do pobierania elementów widoku.
- Otwórz pokój
SleepTrackerFragment.kt. - W narzędziu
onCreateview()utwórz adapter. Umieść ten kod po utworzeniu modeluViewModeli przed instrukcjąreturn.
val adapter = SleepNightAdapter()- Powiąż
adapterzRecyclerView.
binding.sleepList.adapter = adapter- Wyczyść i ponownie skompiluj projekt, aby zaktualizować obiekt
binding.
Jeśli nadal widzisz błędy związane zbinding.sleepListlubbinding.FragmentSleepTrackerBinding, unieważnij pamięć podręczną i uruchom ponownie. (Wybierz File > Invalidate Caches / Restart).
Jeśli teraz uruchomisz aplikację, nie będzie w niej błędów, ale po kliknięciu Start, a potem Stop nie zobaczysz żadnych danych.
Krok 5. Przesyłanie danych do adaptera
Masz już adapter i sposób na przesyłanie danych z niego do RecyclerView. Teraz musisz przenieść dane z ViewModel do adaptera.
- Otwórz pokój
SleepTrackerViewModel. - Znajdź zmienną
nights, która przechowuje wszystkie noce snu, czyli dane do wyświetlenia. Zmiennanightsjest ustawiana przez wywołanie funkcjigetAllNights()w bazie danych. - Usuń
privateznights, ponieważ utworzysz obserwatora, który będzie potrzebować dostępu do tej zmiennej. Deklaracja powinna wyglądać tak:
val nights = database.getAllNights()- W pakiecie
databaseotwórzSleepDatabaseDao. - Znajdź funkcję
getAllNights(). Zwraca ona listęSleepNightwartości jakoLiveData. Oznacza to, że zmiennanightszawiera wartośćLiveData, która jest aktualizowana przezRoom, a Ty możesz obserwować zmiennąnights, aby wiedzieć, kiedy się zmienia. - Otwórz pokój
SleepTrackerFragment. - W
onCreateView()poniżej utworzeniaadapterutwórz obserwatora zmiennejnights.
PodającviewLifecycleOwnerfragmentu jako właściciela cyklu życia, możesz mieć pewność, że ten obserwator będzie aktywny tylko wtedy, gdyRecyclerViewbędzie na ekranie.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
})- W obserwatorze, gdy uzyskasz wartość inną niż null (w przypadku
nights), przypisz ją dodataw adapterze. Oto gotowy kod obserwatora i ustawiania danych:
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.data = it
}
})- Skompiluj i uruchom kod.
Jeśli adapter działa, zobaczysz listę wartości dotyczących jakości snu. Zrzut ekranu po lewej stronie pokazuje wartość -1 po kliknięciu Start. Zrzut ekranu po prawej stronie pokazuje zaktualizowaną liczbę określającą jakość snu po kliknięciu Zatrzymaj i wybraniu oceny jakości.

Krok 6. Dowiedz się, jak są poddawane recyklingowi uchwyty na wizytówki
RecyclerView ponownie wykorzystuje uchwyty widoku, co oznacza, że używa ich ponownie. Gdy widok zniknie z ekranu, RecyclerView ponownie użyje go do wyświetlenia widoku, który ma się pojawić na ekranie.
Ponieważ te obiekty są ponownie wykorzystywane, upewnij się, że funkcja onBindViewHolder() ustawia lub resetuje wszelkie dostosowania, które poprzednie elementy mogły wprowadzić w obiekcie widoku.
Możesz na przykład ustawić kolor tekstu na czerwony w elementach widoku, które zawierają oceny jakości snu mniejsze lub równe 1 i oznaczają słaby sen.
- W klasie
SleepNightAdapterdodaj ten kod na końcuonBindViewHolder().
if (item.sleepQuality <= 1) {
holder.textView.setTextColor(Color.RED) // red
}- Uruchom aplikację.
- Jeśli dodasz dane o niskiej jakości snu, liczba będzie czerwona.
- Dodaj wysokie oceny jakości snu, aż na ekranie pojawi się czerwona liczba.
PonieważRecyclerViewponownie wykorzystuje elementy widoku, w końcu użyje jednego z czerwonych elementów widoku do oceny wysokiej jakości. Wysoka ocena jest błędnie wyświetlana na czerwono.

- Aby to naprawić, dodaj instrukcję
else, która ustawi kolor na czarny, jeśli jakość nie jest mniejsza lub równa 1.
Gdy oba warunki są wyraźne, uchwyt widoku będzie używać prawidłowego koloru tekstu dla każdego elementu.
if (item.sleepQuality <= 1) {
holder.textView.setTextColor(Color.RED) // red
} else {
// reset
holder.textView.setTextColor(Color.BLACK) // black
}- Uruchom aplikację. Liczby powinny zawsze mieć prawidłowy kolor.
Gratulacje! Masz teraz w pełni funkcjonalną podstawową wersję RecyclerView.
W tym zadaniu zastąpisz prosty widok widokiem, który może wyświetlać więcej danych o śnie w nocy.
Prosty znak ViewHolder dodany do Util.kt po prostu umieszcza TextView w TextItemViewHolder.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)Dlaczego RecyclerView nie używa bezpośrednio TextView? Ta jedna linia kodu zapewnia wiele funkcji. ViewHolder opisuje widok elementu i metadane dotyczące jego miejsca w RecyclerView. RecyclerView korzysta z tej funkcji, aby prawidłowo pozycjonować widok podczas przewijania listy i wykonywać ciekawe czynności, takie jak animowanie widoków podczas dodawania lub usuwania elementów w Adapter.
Jeśli RecyclerView musi uzyskać dostęp do widoków przechowywanych w ViewHolder, może to zrobić za pomocą właściwości itemView obiektu View Holder. RecyclerView używa itemView podczas wiązania elementu do wyświetlenia na ekranie, rysowania dekoracji wokół widoku, np. obramowania, oraz do implementowania ułatwień dostępu.
Krok 1. Utwórz układ elementu
W tym kroku utworzysz plik układu dla jednego elementu. Układ składa się z ConstraintLayout z ImageView oznaczającym jakość snu, TextView oznaczającym długość snu i TextView oznaczającym jakość snu w formie tekstu. Ponieważ masz już doświadczenie w tworzeniu układów, skopiuj i wklej podany kod XML.
- Utwórz nowy plik zasobu układu i nadaj mu nazwę
list_item_sleep_night. - Zastąp cały kod w pliku kodem poniżej. Następnie zapoznaj się z utworzonym układem.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content">
<ImageView
android:id="@+id/quality_image"
android:layout_width="@dimen/icon_size"
android:layout_height="60dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@drawable/ic_sleep_5" />
<TextView
android:id="@+id/sleep_length"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/quality_image"
app:layout_constraintTop_toTopOf="@+id/quality_image"
tools:text="Wednesday" />
<TextView
android:id="@+id/quality_string"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="@+id/sleep_length"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/sleep_length"
app:layout_constraintTop_toBottomOf="@+id/sleep_length"
tools:text="Excellent!!!" />
</androidx.constraintlayout.widget.ConstraintLayout>- W Android Studio przejdź na kartę Design (Projekt). W widoku projektu układ wygląda tak jak na zrzucie ekranu po lewej stronie poniżej. W widoku planu wygląda to tak jak na zrzucie ekranu po prawej stronie.

Krok 2. Utwórz ViewHolder
- Otwórz pokój
SleepNightAdapter.kt. - Utwórz w pakiecie
SleepNightAdapterklasę o nazwieViewHolder, która będzie rozszerzać klasęRecyclerView.ViewHolder.
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}- W obrębie
ViewHolderuzyskaj odwołania do widoków. Musisz mieć odniesienie do widoków, które ta funkcjaViewHolderbędzie aktualizować. Za każdym razem, gdy wiążesz tenViewHolder, musisz uzyskać dostęp do obrazu i obu widoków tekstu. (Ten kod przekształcisz później, aby używać powiązania danych).
val sleepLength: TextView = itemView.findViewById(R.id.sleep_length)
val quality: TextView = itemView.findViewById(R.id.quality_string)
val qualityImage: ImageView = itemView.findViewById(R.id.quality_image)Krok 3. Użyj klasy ViewHolder w klasie SleepNightAdapter
- W definicji
SleepNightAdapterzamiastTextItemViewHolderużyj utworzonego przed chwilą elementuSleepNightAdapter.ViewHolder.
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {Zaktualizuj onCreateViewHolder():
- Zmień sygnaturę funkcji
onCreateViewHolder(), aby zwracała wartośćViewHolder. - Zmień inflator układu, aby używał prawidłowego zasobu układu,
list_item_sleep_night. - Usuń przesyłanie na urządzenie
TextView. - Zamiast zwracać wartość
TextItemViewHolder, zwracaj wartośćViewHolder.
Oto gotowa zaktualizowana funkcjaonCreateViewHolder():
override fun onCreateViewHolder(
parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater =
LayoutInflater.from(parent.context)
val view = layoutInflater
.inflate(R.layout.list_item_sleep_night,
parent, false)
return ViewHolder(view)
}Zaktualizuj onBindViewHolder():
- Zmień sygnaturę funkcji
onBindViewHolder()tak, aby parametrholderbył typuViewHolderzamiastTextItemViewHolder. - W sekcji
onBindViewHolder()usuń cały kod z wyjątkiem definicjiitem. - Zdefiniuj
valres, który zawiera odwołanie doresourcesw tym widoku.
val res = holder.itemView.context.resources- Ustaw tekst widoku tekstu
sleepLengthna czas trwania. Skopiuj poniższy kod, który wywołuje funkcję formatowania dostarczoną w kodzie początkowym.
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)- Spowoduje to błąd, ponieważ wartość
convertDurationToFormatted()musi być zdefiniowana. Otwórz plikUtil.kti usuń komentarz z kodu oraz powiązanych importów. (Wybierz Kod > Komentuj za pomocą komentarzy do wierszy). - Wróć do
onBindViewHolder()i użyjconvertNumericQualityToString(), aby ustawić jakość.
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)- Może być konieczne ręczne zaimportowanie tych funkcji.
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString- Ustaw odpowiednią ikonę jakości. Nowa ikona
ic_sleep_activejest dostępna w kodzie początkowym.
holder.qualityImage.setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})- Oto gotowa zaktualizowana funkcja
onBindViewHolder(), która ustawia wszystkie dane dla elementuViewHolder:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = data[position]
val res = holder.itemView.context.resources
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
holder.qualityImage.setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})
}- Uruchom aplikację. Wyświetlacz powinien wyglądać jak na zrzucie ekranu poniżej. Powinna być widoczna ikona jakości snu oraz tekst dotyczący czasu trwania i jakości snu.

Twój RecyclerView jest już gotowy. Dowiedziałeś(-aś) się, jak zaimplementować Adapter i ViewHolder, a następnie połączyć je, aby wyświetlić listę z RecyclerView Adapter.
Dotychczasowy kod pokazuje proces tworzenia adaptera i uchwytu widoku. Możesz jednak ulepszyć ten kod. Kod wyświetlania i kod zarządzania elementami widoku są pomieszane, a onBindViewHolder() zna szczegóły aktualizacji ViewHolder.
W aplikacji produkcyjnej możesz mieć wielu posiadaczy widoków, bardziej złożone adaptery i wielu programistów wprowadzających zmiany. Kod powinien być tak skonstruowany, aby wszystko, co jest związane z obiektem widoku, znajdowało się tylko w tym obiekcie.
Krok 1. Zmiana struktury metody onBindViewHolder()
W tym kroku refaktoryzujesz kod i przenosisz wszystkie funkcje uchwytu widoku do ViewHolder. Celem tej zmiany nie jest zmiana wyglądu aplikacji dla użytkownika, ale ułatwienie i zwiększenie bezpieczeństwa pracy deweloperów nad kodem. Na szczęście Android Studio ma narzędzia, które mogą w tym pomóc.
- W
SleepNightAdapterwonBindViewHolder()wybierz wszystko oprócz instrukcji deklarującej zmiennąitem. - Kliknij prawym przyciskiem myszy i wybierz Refactor > Extract > Function (Refaktoryzacja > Wyodrębnij > Funkcja).
- Nazwij funkcję
bindi zaakceptuj sugerowane parametry. Kliknij OK.
Funkcjabind()jest umieszczona poniżejonBindViewHolder().
private fun bind(holder: ViewHolder, item: SleepNight) {
val res = holder.itemView.context.resources
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
holder.quality.text = convertNumericQualityToString(item.sleepQuality, res)
holder.qualityImage.setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})
}- Ustaw kursor na słowie
holderparametruholderfunkcjibind(). NaciśnijAlt+Enter(Option+Enterna Macu), aby otworzyć menu intencji. Kliknij Convert parameter to receiver (Przekonwertuj parametr na odbiornik), aby przekonwertować go na funkcję rozszerzenia o tym sygnaturze:
private fun ViewHolder.bind(item: SleepNight) {...}- Wytnij i wklej funkcję
bind()do funkcjiViewHolder. - Ustaw
bind()jako publiczną. - W razie potrzeby włóż kartę
bind()do adaptera. - Ponieważ jest on teraz w sekcji
ViewHolder, możesz usunąć z podpisu częśćViewHolder. Oto ostateczny kod funkcjibind()w klasieViewHolder.
fun bind(item: SleepNight) {
val res = itemView.context.resources
sleepLength.text = convertDurationToFormatted(
item.startTimeMilli, item.endTimeMilli, res)
quality.text = convertNumericQualityToString(
item.sleepQuality, res)
qualityImage.setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})
}Krok 2. Zmiana struktury metody onCreateViewHolder
Metoda onCreateViewHolder() w adapterze obecnie powiększa widok z zasobu układu dla elementu ViewHolder. Inflacja nie ma jednak nic wspólnego z adapterem, a wszystko z ViewHolder. Inflacja powinna nastąpić w ViewHolder.
- W
onCreateViewHolder()zaznacz cały kod w treści funkcji. - Kliknij prawym przyciskiem myszy i wybierz Refactor > Extract > Function (Refaktoryzacja > Wyodrębnij > Funkcja).
- Nazwij funkcję
fromi zaakceptuj sugerowane parametry. Kliknij OK. - Umieść kursor na nazwie funkcji
from. NaciśnijAlt+Enter(Option+Enterna Macu), aby otworzyć menu intencji. - Kliknij Przenieś do obiektu towarzyszącego. Funkcja
from()musi znajdować się w obiekcie towarzyszącym, aby można było ją wywołać w klasieViewHolder, a nie w instancjiViewHolder. - Przenieś obiekt
companiondo klasyViewHolder. - Ustaw
from()jako publiczną. - W
onCreateViewHolder()zmień instrukcjęreturn, aby zwracała wynik wywołaniafrom()w klasieViewHolder.
Ukończone metodyonCreateViewHolder()ifrom()powinny wyglądać jak poniższy kod, a kod powinien się kompilować i uruchamiać bez błędów.
override fun onCreateViewHolder(parent: ViewGroup, viewType:
Int): ViewHolder {
return ViewHolder.from(parent)
}companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater
.inflate(R.layout.list_item_sleep_night, parent, false)
return ViewHolder(view)
}
}- Zmień sygnaturę klasy
ViewHoldertak, aby konstruktor był prywatny. Ponieważfrom()jest teraz metodą, która zwraca nową instancjęViewHolder, nie ma już powodu, aby ktokolwiek wywoływał konstruktorViewHolder.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){- Uruchom aplikację. Powinna się ona skompilować i uruchomić tak samo jak wcześniej, co jest pożądanym wynikiem po refaktoryzacji.
Projekt Android Studio: RecyclerViewFundamentals
- Wyświetlanie listy lub siatki danych to jedno z najczęstszych zadań interfejsu w Androidzie.
RecyclerViewjest zaprojektowany tak, aby działać wydajnie nawet w przypadku wyświetlania bardzo długich list. RecyclerViewwykonuje tylko czynności niezbędne do przetworzenia lub narysowania elementów, które są obecnie widoczne na ekranie.- Gdy element zniknie z ekranu, jego widoki są ponownie wykorzystywane. Oznacza to, że element jest wypełniony nowymi treściami, które przewijają się na ekranie.
- Wzorzec adaptera w inżynierii oprogramowania pomaga obiektowi współpracować z innym interfejsem API.
RecyclerViewużywa adaptera do przekształcania danych aplikacji w format, który może wyświetlać, bez konieczności zmiany sposobu przechowywania i przetwarzania danych przez aplikację.
Aby wyświetlić dane w RecyclerView, potrzebujesz tych elementów:
- RecyclerView
– aby utworzyć instancjęRecyclerView, zdefiniuj element<RecyclerView>w pliku układu. - LayoutManager
RecyclerViewużywaLayoutManagerdo organizowania układu elementów wRecyclerView, np. do rozmieszczania ich w siatce lub na liście liniowej.
W<RecyclerView>w pliku układu ustaw atrybutapp:layoutManagerna menedżera układu (np.LinearLayoutManagerlubGridLayoutManager).
Możesz też ustawićLayoutManagerdlaRecyclerViewprogramowo. (Ta technika zostanie omówiona w dalszej części ćwiczenia z programowania). - Układ każdego elementu
Utwórz układ jednego elementu danych w pliku układu XML. - Adapter
– utwórz adapter, który przygotuje dane i określi sposób ich wyświetlania wViewHolder. Powiąż przejściówkę zRecyclerView.
GdyRecyclerViewjest uruchomiony, używa adaptera, aby określić, jak wyświetlać dane na ekranie.
Adapter wymaga zaimplementowania tych metod:
–getItemCount()zwraca liczbę elementów.
–onCreateViewHolder()zwracaViewHolderdla elementu na liście.
–onBindViewHolder()dostosowuje dane do widoków elementu na liście. - ViewHolder
AViewHolderzawiera informacje o widoku służące do wyświetlania jednego elementu z układu elementu. - Metoda
onBindViewHolder()w adapterze dostosowuje dane do widoków. Zawsze zastępuj tę metodę. ZazwyczajonBindViewHolder()powiększa układ elementu i umieszcza dane w widokach w układzie. - Ponieważ
RecyclerViewnie ma informacji o danych,Adaptermusi informowaćRecyclerViewo zmianach tych danych. UżyjnotifyDataSetChanged(), aby powiadomićAdaptero zmianie danych.
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
Jak RecyclerView wyświetla produkty? Zaznacz wszystkie pasujące opcje.
▢ Wyświetla elementy w postaci listy lub siatki.
▢ Przewijanie w pionie lub w poziomie.
▢ Przewija się po przekątnej na większych urządzeniach, takich jak tablety.
▢ Umożliwia tworzenie niestandardowych układów, gdy lista lub siatka nie wystarczają w danym przypadku.
Pytanie 2
Jakie są zalety korzystania z RecyclerView? Zaznacz wszystkie pasujące opcje.
▢ Skuteczne wyświetlanie dużych list.
▢ Automatycznie aktualizuje dane.
▢ Minimalizuje potrzebę odświeżania, gdy element zostanie zaktualizowany, usunięty lub dodany do listy.
▢ Ponowne użycie widoku, który przewija się poza ekran, aby wyświetlić następny element przewijany na ekranie.
Pytanie 3
Jakie są powody używania adapterów? Zaznacz wszystkie pasujące opcje.
▢ Rozdzielenie odpowiedzialności ułatwia wprowadzanie zmian w kodzie i jego testowanie.
▢ RecyclerView nie zależy od wyświetlanych danych.
▢ Warstwy przetwarzania danych nie muszą się zajmować sposobem wyświetlania danych.
▢ Aplikacja będzie działać szybciej.
Pytanie 4
Które z poniższych stwierdzeń dotyczących ViewHolder są prawdziwe? Zaznacz wszystkie pasujące opcje.
▢ Układ ViewHolder jest zdefiniowany w plikach układu XML.
▢ W zbiorze danych jest 1 ViewHolder na każdą jednostkę danych.
▢ W jednym RecyclerView może być więcej niż jeden ViewHolder.
▢ Adapter wiąże dane z ViewHolder.
Rozpocznij kolejną lekcję: