Android Kotlin Fundamentals 07.1: podstawy RecyclerView

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 safeArgs do przekazywania danych między fragmentami;
  • Używanie modeli widoku, fabryk modeli widoku, przekształceń i LiveData oraz 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ć RecyclerViewAdapterViewHolder do 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 ViewModelLiveData. 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 RecyclerView przetwarza 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, funkcja RecyclerView wykonuje tylko tyle pracy, ile potrzeba do narysowania 10 elementów na ekranie. Gdy użytkownik przewija stronę, RecyclerView okreś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 RecyclerView zachowanie pozwala zaoszczędzić dużo czasu przetwarzania i zapewnia płynne przewijanie list.
  • Gdy element się zmieni, zamiast ponownie rysować całą listę, RecyclerView moż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 RecyclerView zdefiniowana 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óre RecyclerView wykorzystuje do efektywnego przesuwania widoków po ekranie.
  • Adapter.
    Adapter łączy dane z RecyclerView. Dostosowuje dane, aby można je było wyświetlić w ViewHolder. RecyclerView uż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.

  1. Pobierz aplikację RecyclerViewFundamentals-Starter z GitHub.
  2. Skompiluj i uruchom aplikację. Zwróć uwagę, jak dane są wyświetlane jako zwykły tekst.
  3. Otwórz plik układu fragment_sleep_tracker.xml na karcie Design w Android Studio.
  4. W panelu Drzewo komponentów usuń ScrollView. Spowoduje to też usunięcie TextView znajdującego się w ScrollView.
  5. W panelu Paleta przewiń listę typów komponentów po lewej stronie, aby znaleźć Kontenery, a następnie wybierz tę opcję.
  6. Przeciągnij RecyclerView z panelu Paleta do panelu Drzewo komponentów. Umieść RecyclerViewConstraintLayout.

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

  1. 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'
  1. Wróć do fragment_sleep_tracker.xml.
  2. Na karcie Tekst znajdź kod RecyclerView widoczny poniżej:
<androidx.recyclerview.widget.RecyclerView
   android:layout_width="match_parent"
   android:layout_height="match_parent" />
  1. Nadaj RecyclerView id o wartości sleep_list.
android:id="@+id/sleep_list"
  1. Ustaw RecyclerView tak, aby zajmował pozostałą część ekranu w ConstraintLayout. Aby to zrobić, przypisz górną krawędź elementu RecyclerView do 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"
  1. Dodaj menedżera układu do pliku XML RecyclerView. Każdy element RecyclerView wymaga menedżera układu, który określa sposób rozmieszczenia elementów na liście. Android udostępnia LinearLayoutManager, który domyślnie układa elementy w pionowej liście wierszy o pełnej szerokości.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
  1. 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).

  1. 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.
  2. text_item_view.xml usuń cały podany kod.
  3. Dodaj TextView z wypełnieniem 16dp na początku i na końcu oraz rozmiarem tekstu 24sp. Szerokość ma być dopasowana do elementu nadrzędnego, a wysokość do treści. Ten widok jest wyświetlany w RecyclerView, więc nie musisz umieszczać go w ViewGroup.
<?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" />
  1. 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 w Util.kt, ponieważ ten uchwyt widoku jest tymczasowy i później go zastąpimy.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
  1. Jeśli pojawi się prośba, zaimportuj android.widget.TextViewandroidx.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.

  1. W pakiecie sleeptracker utwórz nową klasę Kotlin o nazwie SleepNightAdapter.
  2. Spraw, aby klasa SleepNightAdapter rozszerzała klasę RecyclerView.Adapter. Klasa ta nosi nazwę SleepNightAdapter, ponieważ dostosowuje obiekt SleepNight do formatu, który może być używany przez RecyclerView. 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>() {}
  1. Na najwyższym poziomie SleepNightAdapter utwórz zmienną listOf SleepNight, która będzie przechowywać dane.
var data =  listOf<SleepNight>()
  1. SleepNightAdapter zastąp getItemCount(), aby zwrócić rozmiar listy nocy snu w data. RecyclerView musi wiedzieć, ile elementów ma adapter, aby je wyświetlić. W tym celu wywołuje funkcję getItemCount().
override fun getItemCount() = data.size
  1. W pliku SleepNightAdapter zastąp funkcję onBindViewHolder(), jak pokazano poniżej.

    Funkcja onBindViewHolder() jest wywoływana przez RecyclerView w celu wyświetlenia danych jednego elementu listy w określonej pozycji. Metoda onBindViewHolder() przyjmuje więc 2 argumenty: uchwyt widoku i pozycję danych do powiązania. W przypadku tej aplikacji posiadaczem jest TextItemViewHolder, a pozycja to pozycja na liście.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}
  1. onBindViewHolder() utwórz zmienną dla jednego elementu w danym miejscu w danych.
 val item = data[position]
  1. Utworzony przez Ciebie ViewHolder ma usługę o nazwie textView. W sekcji onBindViewHolder() ustaw text elementu textView na 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()
  1. SleepNightAdapter zastąp i wdroż onCreateViewHolder(), który jest wywoływany, gdy RecyclerView potrzebuje uchwytu widoku do reprezentowania elementu.

    Ta funkcja przyjmuje 2 parametry i zwraca wartość ViewHolder. Parametr parent, czyli grupa widoków, która zawiera uchwyt widoku, jest zawsze parametrem parent.RecyclerView Parametr viewType jest używany, gdy w tym samym RecyclerView jest kilka widoków. Jeśli na przykład umieścisz listę widoków tekstu, obraz i film w tym samym RecyclerView, funkcja onCreateViewHolder() będzie musiała wiedzieć, jakiego typu widoku użyć.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}
  1. onCreateViewHolder() utwórz instancję LayoutInflater.

    Inflator układu wie, jak tworzyć widoki z układów XML. context zawiera informacje o tym, jak prawidłowo rozwinąć widok. W adapterze widoku recyklera zawsze przekazujesz kontekst grupy widoków parent, czyli RecyclerView.
val layoutInflater = LayoutInflater.from(parent.context)
  1. W onCreateViewHolder() utwórz view, prosząc layoutinflater o napompowanie go.

    Przekaż układ XML widoku i parent grupę widoków. Trzeci argument (logiczny) to attachToRoot. Ten argument musi mieć wartość false, ponieważ RecyclerView dodaje ten element do hierarchii widoków w odpowiednim momencie.
val view = layoutInflater
       .inflate(R.layout.text_item_view, parent, false) as TextView
  1. onCreateViewHolder() zwróć TextItemViewHolder kupiony za pomocą view.
return TextItemViewHolder(view)
  1. Adapter musi informować RecyclerView o zmianie data, ponieważ RecyclerView nie ma żadnych informacji o danych. Zna tylko elementy widoku, które przekazuje mu adapter.

    Aby poinformować RecyclerView o zmianie wyświetlanych danych, dodaj niestandardowy setter do zmiennej data u góry klasy SleepNightAdapter. W funkcji ustawiającej przypisz zmiennej data nową 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.

  1. Otwórz pokój SleepTrackerFragment.kt.
  2. W narzędziu onCreateview() utwórz adapter. Umieść ten kod po utworzeniu modelu ViewModel i przed instrukcją return.
val adapter = SleepNightAdapter()
  1. Powiąż adapterRecyclerView.
binding.sleepList.adapter = adapter
  1. Wyczyść i ponownie skompiluj projekt, aby zaktualizować obiekt binding.

    Jeśli nadal widzisz błędy związane z binding.sleepList lub binding.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.

  1. Otwórz pokój SleepTrackerViewModel.
  2. Znajdź zmienną nights, która przechowuje wszystkie noce snu, czyli dane do wyświetlenia. Zmienna nights jest ustawiana przez wywołanie funkcji getAllNights() w bazie danych.
  3. Usuń privatenights, ponieważ utworzysz obserwatora, który będzie potrzebować dostępu do tej zmiennej. Deklaracja powinna wyglądać tak:
val nights = database.getAllNights()
  1. W pakiecie database otwórz SleepDatabaseDao.
  2. Znajdź funkcję getAllNights(). Zwraca ona listę SleepNight wartości jako LiveData. Oznacza to, że zmienna nights zawiera wartość LiveData, która jest aktualizowana przez Room, a Ty możesz obserwować zmienną nights, aby wiedzieć, kiedy się zmienia.
  3. Otwórz pokój SleepTrackerFragment.
  4. onCreateView() poniżej utworzenia adapter utwórz obserwatora zmiennej nights.

    Podając viewLifecycleOwner fragmentu jako właściciela cyklu życia, możesz mieć pewność, że ten obserwator będzie aktywny tylko wtedy, gdy RecyclerView będzie na ekranie.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   })
  1. W obserwatorze, gdy uzyskasz wartość inną niż null (w przypadku nights), przypisz ją do data w adapterze. Oto gotowy kod obserwatora i ustawiania danych:
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   it?.let {
       adapter.data = it
   }
})
  1. 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.

  1. W klasie SleepNightAdapter dodaj ten kod na końcu onBindViewHolder().
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
}
  1. Uruchom aplikację.
  2. Jeśli dodasz dane o niskiej jakości snu, liczba będzie czerwona.
  3. Dodaj wysokie oceny jakości snu, aż na ekranie pojawi się czerwona liczba.

    Ponieważ RecyclerView ponownie 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.

  1. 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
}
  1. 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 ConstraintLayoutImageView 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.

  1. Utwórz nowy plik zasobu układu i nadaj mu nazwę list_item_sleep_night.
  2. 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>
  1. 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

  1. Otwórz pokój SleepNightAdapter.kt.
  2. Utwórz w pakiecie SleepNightAdapter klasę o nazwie ViewHolder, która będzie rozszerzać klasę RecyclerView.ViewHolder.
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}
  1. W obrębie ViewHolder uzyskaj odwołania do widoków. Musisz mieć odniesienie do widoków, które ta funkcja ViewHolder będzie aktualizować. Za każdym razem, gdy wiążesz ten ViewHolder, 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

  1. W definicji SleepNightAdapter zamiast TextItemViewHolder użyj utworzonego przed chwilą elementu SleepNightAdapter.ViewHolder.
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {

Zaktualizuj onCreateViewHolder():

  1. Zmień sygnaturę funkcji onCreateViewHolder(), aby zwracała wartość ViewHolder.
  2. Zmień inflator układu, aby używał prawidłowego zasobu układu, list_item_sleep_night.
  3. Usuń przesyłanie na urządzenie TextView.
  4. Zamiast zwracać wartość TextItemViewHolder, zwracaj wartość ViewHolder.

    Oto gotowa zaktualizowana funkcja onCreateViewHolder():
    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():

  1. Zmień sygnaturę funkcji onBindViewHolder() tak, aby parametr holder był typu ViewHolder zamiast TextItemViewHolder.
  2. W sekcji onBindViewHolder() usuń cały kod z wyjątkiem definicji item.
  3. Zdefiniuj val res, który zawiera odwołanie do resources w tym widoku.
val res = holder.itemView.context.resources
  1. Ustaw tekst widoku tekstu sleepLength na 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)
  1. Spowoduje to błąd, ponieważ wartość convertDurationToFormatted() musi być zdefiniowana. Otwórz plik Util.kt i usuń komentarz z kodu oraz powiązanych importów. (Wybierz Kod > Komentuj za pomocą komentarzy do wierszy).
  2. Wróć do onBindViewHolder() i użyj convertNumericQualityToString(), aby ustawić jakość.
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
  1. Może być konieczne ręczne zaimportowanie tych funkcji.
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString
  1. Ustaw odpowiednią ikonę jakości. Nowa ikona ic_sleep_active jest 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
})
  1. Oto gotowa zaktualizowana funkcja onBindViewHolder(), która ustawia wszystkie dane dla elementu ViewHolder:
   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
        })
    }
  1. 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.

  1. W SleepNightAdapter w onBindViewHolder() wybierz wszystko oprócz instrukcji deklarującej zmienną item.
  2. Kliknij prawym przyciskiem myszy i wybierz Refactor > Extract > Function (Refaktoryzacja > Wyodrębnij > Funkcja).
  3. Nazwij funkcję bind i zaakceptuj sugerowane parametry. Kliknij OK.

    Funkcja bind() jest umieszczona poniżej onBindViewHolder().
    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
        })
    }
  1. Ustaw kursor na słowie holder parametru holder funkcji bind(). Naciśnij Alt+Enter (Option+Enter na 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) {...}
  1. Wytnij i wklej funkcję bind() do funkcji ViewHolder.
  2. Ustaw bind() jako publiczną.
  3. W razie potrzeby włóż kartę bind() do adaptera.
  4. Ponieważ jest on teraz w sekcji ViewHolder, możesz usunąć z podpisu część ViewHolder. Oto ostateczny kod funkcji bind() w klasie ViewHolder.
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.

  1. onCreateViewHolder() zaznacz cały kod w treści funkcji.
  2. Kliknij prawym przyciskiem myszy i wybierz Refactor > Extract > Function (Refaktoryzacja > Wyodrębnij > Funkcja).
  3. Nazwij funkcję from i zaakceptuj sugerowane parametry. Kliknij OK.
  4. Umieść kursor na nazwie funkcji from. Naciśnij Alt+Enter (Option+Enter na Macu), aby otworzyć menu intencji.
  5. Kliknij Przenieś do obiektu towarzyszącego. Funkcja from() musi znajdować się w obiekcie towarzyszącym, aby można było ją wywołać w klasie ViewHolder, a nie w instancji ViewHolder.
  6. Przenieś obiekt companion do klasy ViewHolder.
  7. Ustaw from() jako publiczną.
  8. onCreateViewHolder() zmień instrukcję return, aby zwracała wynik wywołania from() w klasie ViewHolder.

    Ukończone metody onCreateViewHolder()from() 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)
   }
}
  1. Zmień sygnaturę klasy ViewHolder tak, aby konstruktor był prywatny. Ponieważ from() jest teraz metodą, która zwraca nową instancję ViewHolder, nie ma już powodu, aby ktokolwiek wywoływał konstruktor ViewHolder.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
  1. 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. RecyclerView jest zaprojektowany tak, aby działać wydajnie nawet w przypadku wyświetlania bardzo długich list.
  • RecyclerView wykonuje 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. RecyclerView uż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
    RecyclerView używa LayoutManager do organizowania układu elementów w RecyclerView, np. do rozmieszczania ich w siatce lub na liście liniowej.

    <RecyclerView> w pliku układu ustaw atrybut app:layoutManager na menedżera układu (np. LinearLayoutManager lub GridLayoutManager).

    Możesz też ustawić LayoutManager dla RecyclerView programowo. (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 w ViewHolder. Powiąż przejściówkę z RecyclerView.

    Gdy RecyclerView jest uruchomiony, używa adaptera, aby określić, jak wyświetlać dane na ekranie.

    Adapter wymaga zaimplementowania tych metod:
    getItemCount() zwraca liczbę elementów.
    onCreateViewHolder() zwraca ViewHolder dla elementu na liście.
    onBindViewHolder() dostosowuje dane do widoków elementu na liście.

  • ViewHolder
    A ViewHolder zawiera 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ę. Zazwyczaj onBindViewHolder() powiększa układ elementu i umieszcza dane w widokach w układzie.
  • Ponieważ RecyclerView nie ma informacji o danych, Adapter musi informować RecyclerView o zmianach tych danych. Użyj notifyDataSetChanged(), aby powiadomić Adapter o 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ę: 7.2. DiffUtil i powiązanie danych z RecyclerView