Android Kotlin Fundamentals 07.1: Podstawowe informacje o recyklingu

Te ćwiczenia są częścią kursu Android Kotlin Fundamentals. Skorzystaj z tego kursu, jeśli będziesz wykonywać kolejno kilka ćwiczeń z programowania. Wszystkie ćwiczenia z kursu są wymienione na stronie docelowej ćwiczeń z programowania na temat Kotlin.

Wprowadzenie

Z tego ćwiczenia z programowania dowiesz się, jak używać listy RecyclerView do wyświetlania list elementów. Korzystając z aplikacji do monitorowania snu z poprzedniej serii ćwiczeń z programowania, poznasz lepszy i bardziej uniwersalny sposób wyświetlania danych z zalecaną architekturą RecyclerView.

Co musisz wiedzieć

Pamiętaj:

  • Tworzenie podstawowego interfejsu użytkownika za pomocą aktywności, fragmentów i widoków danych.
  • Przechodzenie między fragmentami i przekazywanie danych między nimi za pomocą safeArgs.
  • Korzystając z widoków modeli, możesz wyświetlać fabryki, przekształcenia oraz obiekty LiveData i ich obserwatorów.
  • Tworzenie bazy danych Room, tworzenie DAO i definiowanie jednostek.
  • Korzystanie z algorytmów baz danych i innych zadań długotrwałych.

Czego się nauczysz

  • Aby użyć listy RecyclerView z elementami Adapter i ViewHolder, aby wyświetlić listę elementów.

Jakie zadania wykonasz:

  • Zmień aplikację TrackMySleepQuality z poprzedniej lekcji, by korzystać z RecyclerView w celu wyświetlania danych o jakości snu.

W ramach tego ćwiczenia tworzysz część RecyclerView aplikacji, która monitoruje jakość snu. Aplikacja używa bazy danych Room do przechowywania danych o śnie.

Początkowa aplikacja do monitorowania snu ma 2 ekrany reprezentowane przez fragmenty, jak widać na ilustracji poniżej.

Pierwszy ekran (po lewej stronie) ma przyciski do włączania i wyłączania śledzenia. Na tym ekranie widoczne są również wszystkie dane dotyczące snu użytkownika. Kliknięcie przycisku Wyczyść powoduje trwałe usunięcie wszystkich danych użytkownika zbieranych przez aplikację. Na drugim ekranie (po prawej) możesz wybrać ocenę jakości snu.

Ta aplikacja używa uproszczonej architektury z kontrolerem interfejsu, ViewModel i LiveData. Aplikacja używa też bazy danych Room, by trwałe dane o śnie.

Lista nocy snu widoczna na pierwszym ekranie działa, ale nie jest piękna. Aplikacja używa złożonego formatowania, aby tworzyć ciągi tekstowe na potrzeby widoku tekstu oraz liczby na potrzeby jakości. Ten projekt również nie jest skalowany. Gdy rozwiążesz te problemy z ćwiczeń z programowania, końcowa aplikacja uzyska te same funkcje, a ekran główny będzie wyglądał tak:

Wyświetlanie listy lub siatki danych to jedno z najczęstszych zadań Androida w interfejsie użytkownika. Listy mogą być bardzo łatwe lub bardzo złożone. Lista widoków tekstowych może zawierać proste dane, np. listę zakupów. Złożona lista, na przykład z adnotacjami na temat miejsc docelowych wakacji, może wyświetlać użytkownikowi wiele szczegółów w przewijanej siatce z nagłówkami.

Aby zapewnić działanie wszystkich tych zastosowań, Android ma widżet RecyclerView.

Ogromną zaletą stosowania atrybutu RecyclerView jest to, że jest bardzo wydajna w przypadku dużych list:

  • Domyślnie RecyclerView przetwarza i rysuje tylko elementy, które są obecnie widoczne na ekranie. Jeśli na przykład lista zawiera tysiące elementów, ale widocznych jest tylko 10 elementów, RecyclerView wystarczy, aby udało się narysować na ekranie 10 elementów. Gdy użytkownik przewija stronę, RecyclerView znajduje informacje o nowych elementach na ekranie i wystarczająco dużo, by je wyświetlić.
  • Gdy element przewija się poza ekranem, następuje odświeżenie jego wyświetleń. Oznacza to, że element jest wypełniony nowymi treściami przewijanymi na ekran. To działanie RecyclerView pozwala zaoszczędzić czas potrzebny na przetwarzanie i ułatwia płynne przewijanie list.
  • Gdy element się zmieni, zamiast zmieniać jego całą listę, RecyclerView może zaktualizować ten element. To ogromny wzrost wydajności, gdy wyświetlasz listy złożonych elementów.

Jak widać, w sekwencji widać, że jeden widok został wypełniony danymi: ABC. Po tym czasie RecyclerView przewinie widok poza Twój ekran, aby użyć go ponownie: XYZ.

Wzór adaptera

Jeśli podróżujesz między krajami, które korzystają z innych gniazdek elektrycznych, prawdopodobnie wiesz, jak podłączyć urządzenia do gniazdka za pomocą zasilacza. Adapter umożliwia konwertowanie jednego typu wtyczki na drugi, co powoduje konwertowanie jednego interfejsu na drugi.

Wzorzec dostosowania używany w inżynierii oprogramowania pomaga w obsłudze obiektu przy użyciu innego interfejsu API. RecyclerView wykorzystuje adapter, aby przekształcić dane aplikacji w coś, co RecyclerView może wyświetlić, bez zmiany sposobu przechowywania i przetwarzania danych przez aplikację. W przypadku aplikacji do monitorowania snu tworzysz adapter, który dostosowuje dane z bazy danych Room do kodu, który RecyclerView wie, jak wyświetlać, bez zmieniania elementu ViewModel.

Wdrażanie RecyclerView

Aby wyświetlać dane w narzędziu RecyclerView, potrzebujesz tych elementów:

  • Dane do wyświetlenia.
  • Instancja RecyclerView zdefiniowana w pliku układu, która będzie pełnić funkcję kontenera widoków danych.
  • Układ jednego elementu danych.
    Jeśli wszystkie elementy listy wyglądają tak samo, możesz wykorzystać ten sam układ dla wszystkich, ale nie jest to wymagane. Układ elementu trzeba utworzyć oddzielnie od fragmentu, aby można było utworzyć widok danych elementu i wypełnić go danymi.
  • Menedżer układu.
    Menedżer układu obsługuje organizację (układ) komponentów interfejsu w widoku danych.
  • Obiekt wyświetlania.
    Powiększa on klasę ViewHolder. Zawiera on informacje o widoku dla wyświetlania jednego elementu z układu elementu. Właściciele widoków danych dodają też informacje, których RecyclerView używa do efektywnego przenoszenia widoków na ekran.
  • Adapter.
    Adapter łączy dane z RecyclerView. Dostosowuje on dane tak, by były wyświetlane w narzędziu ViewHolder. RecyclerView używa adaptera, aby określić sposób wyświetlania danych na ekranie.

W tym zadaniu dodasz RecyclerView do pliku układu i skonfigurujesz Adapter, aby wyświetlić dane dotyczące snu w narzędziu RecyclerView.

Krok 1. Dodaj RecyclerView z LayoutManager

W tym kroku zastąpisz ScrollView ciągiem RecyclerView w pliku fragment_sleep_tracker.xml.

  1. Pobierz aplikację RecyclerViewFundamentals-Starter z GitHuba.
  2. Utwórz i uruchom aplikację. Zwróć uwagę na to, jak dane są wyświetlane w postaci zwykłego tekstu.
  3. Otwórz plik układu fragment_sleep_tracker.xml na karcie Projekt w Android Studio.
  4. W panelu Drzewo komponentów usuń ScrollView. Ta czynność usuwa też element TextView, który jest wewnątrz ScrollView.
  5. W panelu Paleta przewiń listę typów komponentów po lewej stronie, aby znaleźć opcję Kontenery, a potem ją wybierz.
  6. Przeciągnij RecyclerView z panelu Paleta do panelu Drzewo komponentów. Umieść RecyclerView w obiekcie ConstraintLayout.

  1. Jeśli pojawi się okno z pytaniem, czy chcesz dodać zależność, kliknij OK, aby zezwolić Android Studio na dodanie zależności recyclerview do pliku Gradle. Synchronizacja aplikacji może potrwać kilka sekund.

  1. Otwórz plik build.gradle modułu, przewiń na koniec i zanotuj nową zależność, która wygląda podobnie do tego 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. RecyclerView id otrzymuje sleep_list.
android:id="@+id/sleep_list"
  1. Umieść RecyclerView w obszarze ConstraintLayout, aby zajmował pozostałą część ekranu. Aby to zrobić, przypisz górną granicę RecyclerView do przycisku Start, na dole do przycisku Wyczyść i do obu stron elementu nadrzędnego. Ustaw szerokość i wysokość układu na 0 dp w Edytorze układu lub w formacie XML, korzystając z 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 potrzebuje menedżera układu, który informuje go, jak umieścić elementy na liście. Android udostępnia LinearLayoutManager, który domyślnie rozmieszcza elementy w pionowej liście pełnej szerokości wierszy.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
  1. Przejdź na kartę Projekt i zauważ, że dodane ograniczenia spowodowały, że okno RecyclerView rozwinęło się w dostępnym miejscu.

Krok 2. Utwórz układ elementu listy i właściciela tekstu

RecyclerView to tylko kontener. W tym kroku utworzysz układ i infrastrukturę elementów, które będą wyświetlane w obrębie obiektu RecyclerView.

Aby jak najszybciej przejść do działającego obiektu RecyclerView, użyj uproszczonej listy, która wyświetla tylko jakość snu w postaci liczby. Aby to zrobić, potrzebujesz właściciela widoku danych, TextItemViewHolder. Musisz też mieć widok danych TextView. W dalszej części dowiesz się więcej o posiadaczach widoków danych i o tym, jak rozłożyć wszystkie dane dotyczące snu.

  1. Utwórz plik układu o nazwie text_item_view.xml. Nie ma znaczenia, którego używasz jako elementu głównego, ponieważ zastąpisz kod szablonu.
  2. W aplikacji text_item_view.xml usuń cały podany kod.
  3. Dodaj TextView z dopełnieniem 16dp na początku i na końcu oraz rozmiar tekstu 24sp. Dopasuj szerokość do elementu nadrzędnego, a wysokość zawija zawartość. Ten widok jest wyświetlany w RecyclerView, więc nie musisz go umieszczać w sekcji 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 aplikację Util.kt. Przewiń w dół i dodaj definicję podaną poniżej, co spowoduje utworzenie klasy TextItemViewHolder. Umieść kod na końcu pliku po ostatnim nawiasie klamrowym. Kod zostanie przesłany w usłudze Util.kt, ponieważ jest on tymczasowy i zastąpisz go później.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
  1. Jeśli pojawi się taka prośba, zaimportuj android.widget.TextView i androidx.recyclerview.widget.RecyclerView.

Krok 3. Utwórz adapter snu

Podstawowym zadaniem wdrożenia RecyclerView jest utworzenie adaptera. Potrzebujesz prostego widoku dla widoku elementu i układu każdego elementu. Teraz możesz utworzyć adapter. Adapter tworzy uchwyt widoku danych i wypełnia go danymi, które RecyclerView ma wyświetlić.

  1. W pakiecie sleeptracker utwórz nową klasę Kotlin o nazwie SleepNightAdapter.
  2. Rozszerz klasę SleepNightAdapter o RecyclerView.Adapter. Klasa o nazwie SleepNightAdapter służy do przekształcania obiektu SleepNight w element, którego RecyclerView może używać. Adapter musi wiedzieć, którego uchwytu użyć, więc przekaż kartę TextItemViewHolder. Gdy pojawi się prośba o zaimportowanie niezbędnych komponentów, wyświetli się błąd, ponieważ istnieją metody obowiązkowe.
class SleepNightAdapter: RecyclerView.Adapter<TextItemViewHolder>() {}
  1. Na najwyższym poziomie jednostki organizacyjnej SleepNightAdapter utwórz zmienną listOf SleepNight do przechowywania danych.
var data =  listOf<SleepNight>()
  1. W polu SleepNightAdapter zastąp getItemCount(), by wyświetlić rozmiar listy nocy w data. RecyclerView musi wiedzieć, ile elementów ma wyświetlać adapter, i wywołuje polecenie getItemCount().
override fun getItemCount() = data.size
  1. W SleepNightAdapter zastąp funkcję onBindViewHolder(), jak pokazano poniżej.

    Funkcja onBindViewHolder() jest wywoływana przez funkcję RecyclerView w celu wyświetlenia danych w jednym miejscu na liście w określonym miejscu. Metoda onBindViewHolder() przyjmuje więc dwa argumenty: właściciel widoku i pozycję danych do powiązania. W przypadku tej aplikacji właścicielem jest TextItemViewHolder, a pozycja jest na liście.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}
  1. W onBindViewHolder() utwórz zmienną dla 1 produktu w danym miejscu w danych.
 val item = data[position]
  1. Utworzony przez Ciebie element ViewHolder ma usługę o nazwie textView. W ramach onBindViewHolder() ustaw text z textView liczby na jakość snu. Ten kod wyświetla tylko listę liczb, ale ten prosty przykład pokazuje, jak adapter importuje dane na wyświetlacz i na ekran.
holder.textView.text = item.sleepQuality.toString()
  1. W SleepNightAdapter zastępuj i wdrażaj element onCreateViewHolder(), który jest wywoływany, gdy RecyclerView do wyświetlania elementu potrzebuje właściciela widoku.

    Ta funkcja przyjmuje 2 parametry i zwraca ViewHolder. Parametr parent, czyli grupa widoku, do której należy posiadacz widoku, to zawsze RecyclerView. Parametr viewType jest używany, gdy wiele elementów występuje w jednym elemencie RecyclerView. Jeśli na przykład umieścisz listę widoków tekstu, obrazu i filmu w tym samym obiekcie RecyclerView, funkcja onCreateViewHolder() będzie musiała określić, jakiego typu widoku należy użyć.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}
  1. W onCreateViewHolder() utwórz instancję LayoutInflater.

    W narzędziu do wypełnienia układów widać, jak można tworzyć widoki danych z układów XML. context zawiera informacje o tym, jak prawidłowo zawyżać widok. W widoku z widoku z tagu recyklingu przekazujesz w kontekście grupy widoku danych parent, czyli RecyclerView.
val layoutInflater = LayoutInflater.from(parent.context)
  1. W onCreateViewHolder() utwórz view, prosząc layoutinflater o zwiększenie budżetu.

    Przekaż układ XML dla tego widoku i grupę widoków danych parent. Trzecim argumentem logicznym jest attachToRoot. Ten argument musi mieć wartość false, ponieważ RecyclerView dodaje ten element do hierarchii widoków w chwili, gdy ma to miejsce.
val view = layoutInflater
       .inflate(R.layout.text_item_view, parent, false) as TextView
  1. W: onCreateViewHolder() zwracaj TextItemViewHolder z view.
return TextItemViewHolder(view)
  1. Adapter musi poinformować RecyclerView, gdy data się zmieni, ponieważ RecyclerView nie wie nic o danych. Wiadomo tylko na temat właścicieli widoków danych, które otrzymał od adaptera.

    Aby poinformować RecyclerView, gdy wyświetlane są dane, dodaj niestandardowe ustawienie do zmiennej data, która znajduje się u góry klasy SleepNightAdapter. W konfiguracji ustaw nową wartość data, a następnie wywołaj metodę notifyDataSetChanged(), aby wznowić tworzenie listy z nowymi danymi.
var data =  listOf<SleepNight>()
   set(value) {
       field = value
       notifyDataSetChanged()
   }

Krok 4. Poinformuj RecyclerView o adapterze

Aby uzyskać uchwyty do wyświetlania, RecyclerView musi znać adapter.

  1. Otwórz aplikację SleepTrackerFragment.kt.
  2. W onCreateview() utwórz adapter. Umieść ten kod po utworzeniu modelu ViewModel i przed instrukcją return.
val adapter = SleepNightAdapter()
  1. Powiąż adapter z RecyclerView.
binding.sleepList.adapter = adapter
  1. Wyczyść i odbuduj projekt, aby zaktualizować obiekt binding.

    Jeśli nadal występują błędy związane z binding.sleepList lub binding.FragmentSleepTrackerBinding, unieważnij pamięć podręczną i uruchom ponownie urządzenie. (Wybierz Plik > Unieważnij pamięć podręczną / uruchom ponownie).

    Jeśli uruchomisz aplikację teraz, nie wystąpią błędy, ale nie zobaczysz żadnych danych. Aby to zrobić, kliknij Rozpocznij, a następnie Zatrzymaj.

Krok 5. Pobierz dane do przejściówki

Na razie masz adapter i sposób przesyłania danych z adaptera do RecyclerView. Teraz musisz pobrać dane do adaptera z urządzenia ViewModel.

  1. Otwórz aplikację SleepTrackerViewModel.
  2. Znajdź zmienną nights, która przechowuje wszystkie dostępne dane dotyczące snu. Zmienna nights jest ustawiana przez wywołanie getAllNights() w bazie danych.
  3. Usuń pole private z tabeli nights, ponieważ utworzysz obserwatora, który musi uzyskać dostęp do tej zmiennej. Deklaracja powinna wyglądać tak:
val nights = database.getAllNights()
  1. W pakiecie database otwórz SleepDatabaseDao.
  2. Znajdź funkcję getAllNights(). Pamiętaj, że ta funkcja zwraca listę wartości SleepNight jako LiveData. Oznacza to, że zmienna nights zawiera wartość LiveData, która jest aktualizowana przez parametr Room. Możesz ją obserwować, gdy wartość nights się zmieni.
  3. Otwórz aplikację SleepTrackerFragment.
  4. W onCreateView() poniżej kodu adapter utwórz obserwator na zmiennej nights.

    Dodając fragment viewLifecycleOwner jako właściciela cyklu życia, możesz zapewnić, że to obserwator będzie aktywny tylko wtedy, gdy na ekranie widoczny jest RecyclerView.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   })
  1. Za każdym razem, gdy otrzymasz niepustą wartość (w przypadku klasy nights), przypisz ją do adaptera data. To jest pełny kod dla obserwatora i ustawienia danych:
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   it?.let {
       adapter.data = it
   }
})
  1. Utwórz i uruchom kod.

    Jeśli adapter działa, na liście znajdują się wartości dotyczące jakości snu. Zrzut ekranu po lewej stronie pokazuje -1 po kliknięciu Start. Zrzut ekranu po prawej stronie pokazuje zaktualizowany wynik jakości, gdy klikniesz Zatrzymaj i wybierzesz ocenę jakości.

Krok 6. Dowiedz się, w jaki sposób użytkownicy korzystają z ekranów

RecyclerView Odświeża właścicieli widoków danych, co oznacza, że używa ich ponownie. RecyclerView

Te elementy widoku są odświeżane, więc upewnij się, że onBindViewHolder() ustawia lub zresetuje wszelkie dostosowania, które poprzednie elementy mogły ustawiać na takim elemencie.

Możesz na przykład ustawić kolor tekstu na czerwony w widokach, w których ocena jakości jest niższa lub równa 1, co oznacza słabą jakość snu.

  1. W klasie SleepNightAdapter dodaj następujący kod na końcu pola onBindViewHolder().
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
}
  1. Uruchom aplikację.
  2. Dodaj dane dotyczące niskiej jakości snu, a ich liczba będzie czerwona.
  3. Dodaj wysokie oceny jakości snu, dopóki na ekranie nie pojawi się czerwona liczba.

    Gdy RecyclerView będzie ponownie używać tych samych właścicieli, w celu uzyskania wysokiej jakości ocena wykorzysta też 1 z tych użytkowników. Najwyższa ocena jest błędnie wyświetlana na czerwono.

  1. Aby rozwiązać ten problem, dodaj instrukcję else, aby ustawić kolor na czarny, jeśli jakość nie jest niższa od tej wartości.

    Jeśli oba te warunki są jednoznacznie określone, właściciel widoku użyje koloru odpowiedniego 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ę, a liczby powinny zawsze mieć odpowiedni kolor.

Gratulacje! Teraz masz w pełni funkcjonalne podstawowe urządzenie RecyclerView.

W tym zadaniu zastąpisz prosty widok, który może wyświetlać więcej danych w nocy.

Prosty element ViewHolder dodany do Util.kt powoduje zawijanie TextView w TextItemViewHolder.

class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)

Dlaczego RecyclerView nie używa bezpośrednio właściwości TextView? Jeden wiersz kodu zapewnia wiele funkcji. ViewHolder opisuje widok elementu i metadane miejsca w obrębie obiektu RecyclerView. RecyclerView korzysta z tej funkcji do prawidłowego pozycjonowania widoku podczas przewijania listy oraz do tworzenia interesujących rzeczy, np. animacji, gdy elementy są dodawane lub usuwane w Adapter.

Jeśli RecyclerView potrzebuje dostępu do widoków przechowywanych w narzędziu ViewHolder, może to zrobić za pomocą właściwości itemView właściciela widoku. Zmienna RecyclerView używa itemView, gdy wiąże element, który ma być wyświetlany na ekranie, rysuje dekoracje w widoku takim jak obramowanie, oraz implementuje ułatwienia dostępu.

Krok 1. Utwórz układ elementu

W tym kroku utworzysz plik układu 1 elementu. Układ składa się z: ConstraintLayout z ImageView dla jakości snu, TextView dla długości snu oraz TextView dla jakości w formie tekstu. Ponieważ układy zostały już wcześniej skonfigurowane, 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 nowo 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. Otwórz kartę Projekt w Android Studio. W widoku projektu Twój układ wygląda jak zrzut ekranu po lewej stronie. W widoku planu wygląda jak zrzut ekranu po prawej stronie.

Krok 2. Utwórz obiekt ViewHolder

  1. Otwórz aplikację SleepNightAdapter.kt.
  2. Utwórz klasę wewnątrz SleepNightAdapter o nazwie ViewHolder i przedłuż ją o RecyclerView.ViewHolder.
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}
  1. Więcej informacji o wyświetleniach znajdziesz w sekcji ViewHolder. Potrzebujesz odwołania do widoków, które ViewHolder aktualizuje. Za każdym razem, gdy to zrobisz, ViewHolder musisz uzyskać dostęp do obrazu i obu tych elementów. (Przekonwertujesz ten kod, by używać powiązania danych później).
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 ViewHolder w SleepNightAdapter

  1. W definicji SleepNightAdapter zamiast TextItemViewHolder użyj właśnie utworzonej wartości SleepNightAdapter.ViewHolder.
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {

Zaktualizuj onCreateViewHolder():

  1. Zmień podpis onCreateViewHolder(), aby zwracał ViewHolder.
  2. Zmień inflator układu, aby użyć właściwego zasobu układu: list_item_sleep_night.
  3. Usuń obsadę na: TextView.
  4. Zamiast zwracać TextItemViewHolder, zwróć ViewHolder.

    Oto 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ń podpis onBindViewHolder(), tak by parametr holder był elementem ViewHolder, a nie TextItemViewHolder.
  2. W kodzie onBindViewHolder() usuń cały kod, z wyjątkiem definicji item.
  3. Określ val res zawierający odniesienie do resources dla tego widoku.
val res = holder.itemView.context.resources
  1. Ustaw czas trwania tekstu tekstu w widoku tekstowym sleepLength. Skopiuj poniższy kod, który wywołuje funkcję formatowania dostarczoną z kodem startowym.
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
  1. Wystąpił błąd, ponieważ trzeba określić convertDurationToFormatted(). Otwórz plik Util.kt i usuń z komentarza kod oraz powiązane z nim importy. (Wybierz Kod > Skomentuj z wierszem Komentarze).
  2. Za onBindViewHolder() użyj ustawienia convertNumericQualityToString(), aby ustawić jakość.
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
  1. Być może trzeba będzie zaimportować te funkcje ręcznie.
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 ukończona funkcja onBindViewHolder(), która ustawia wszystkie dane dla obiektu 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ę. Ekran powinien wyglądać jak na zrzucie ekranu poniżej, wyświetlając ikonę jakości snu oraz tekst dotyczący czasu trwania i jakości snu.

Twoja transakcja RecyclerView została zakończona! Wiesz już, jak stosować Adapter i ViewHolder. Połączone razem, aby wyświetlić listę z RecyclerView Adapter.

Twój kod do tej pory pokazuje proces tworzenia adaptera i uchwytu wyświetlania. Możesz jednak poprawić ten kod. Kod do wyświetlenia i kod do zarządzania właścicielami widoków są wymieszane, a onBindViewHolder() zna szczegóły aktualizowania ViewHolder.

W aplikacji w wersji produkcyjnej może być używanych kilku posiadaczy widoków danych, bardziej złożone adaptery czy zmiany wprowadzane przez wielu deweloperów. Musisz uporządkować kod w taki sposób, by wszystko, co było związane z właścicielem widoku, było widoczne tylko w tym widoku.

Krok 1. refaktoryzacja onBindViewHolder()

W tym kroku refaktoryzacja kodu i przeniesienie wszystkich funkcji widoku do ViewHolder. Celem tego refaktoryzacji nie jest zmiana wyglądu aplikacji dla użytkownika, ale ułatwienie i usprawnienie pracy programistów przy tworzeniu kodu. Na szczęście Android Studio ma narzędzia, które Ci w tym pomogą.

  1. W polu SleepNightAdapter w polu onBindViewHolder() wybierz wszystko oprócz instrukcji, aby zadeklarować zmienną item.
  2. Kliknij prawym przyciskiem myszy, a następnie wybierz Fact > Extract > Function.
  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. Umieść kursor na słowie holder parametru holder o wartości bind(). Naciśnij Alt+Enter (Option+Enter na Macu), by otworzyć menu intencji. Wybierz Przekształć parametr w odbiornika, aby przekonwertować go na funkcję rozszerzenia o takim podpisie:
private fun ViewHolder.bind(item: SleepNight) {...}
  1. Wytnij i wklej funkcję bind() w narzędziu ViewHolder.
  2. Udostępnij bind() publicznie.
  3. W razie potrzeby zaimportuj bind() do adaptera.
  4. Ponieważ znajduje się on teraz w ViewHolder, możesz usunąć część ViewHolder podpisu. 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: refaktoryzacja onCreateViewHolder

Metoda onCreateViewHolder() w adapterze powoduje aktualnie odświeżenie widoku z zasobu układu ViewHolder. Jednak inflacja nie ma nic wspólnego z adapterem, a tylko z ViewHolder. Inflacja powinna się pojawić w ViewHolder.

  1. W narzędziu onCreateViewHolder() zaznacz cały kod w treści.
  2. Kliknij prawym przyciskiem myszy, a następnie wybierz Fact > Extract > Function.
  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), by otworzyć menu intencji.
  5. Wybierz 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. Udostępnij from() publicznie.
  8. W onCreateViewHolder() zmień instrukcję return, by zwracała wynik wywołania from() w klasie ViewHolder.

    Ukończone metody onCreateViewHolder() i from() powinny wyglądać tak jak poniżej, a Twój kod powinien być tworzony i uruchamiany 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ń podpis klasy ViewHolder, tak aby konstruktor był prywatny. Ponieważ from() jest teraz metodą zwracającą nową instancję ViewHolder, nikt nie musi już wywoływać konstruktora elementu ViewHolder.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
  1. Uruchom aplikację. Powinna być kompilowana i uruchamiana w taki sam sposób jak wcześniej, czyli po refaktoryzacji.

Projekt w Androidzie Studio: RecyclerViewFundamentals

  • Wyświetlanie listy lub siatki danych to jedno z najczęstszych zadań Androida w interfejsie użytkownika. RecyclerView został zaprojektowany z myślą o wydajności, nawet jeśli wyświetlasz bardzo duże listy.
  • RecyclerView umożliwia tylko przetwarzanie i rysowanie elementów widocznych obecnie na ekranie.
  • Gdy element przewija się na ekranie, jest odświeżany. Oznacza to, że element jest wypełniony nowymi treściami przewijanymi na ekran.
  • Wzorzec adaptacyjny w inżynierii oprogramowania ułatwia współpracę obiektu z innym interfejsem API. RecyclerView wykorzystuje adapter, aby przekształcić dane aplikacji w coś, co można wyświetlić, bez konieczności zmiany sposobu przechowywania i przetwarzania danych przez aplikację.

Aby wyświetlać dane w narzędziu RecyclerView, potrzebujesz tych elementów:

  • RecyclerView
    – aby utworzyć instancję RecyclerView, zdefiniuj element <RecyclerView> w pliku układu.
  • ManagerManager
    RecyclerView RecyclerView służy do porządkowania układu elementów w elemencie RecyclerView, na przykład do umieszczenia ich na siatce lub liście liniowej.

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

    Możesz też automatycznie skonfigurować LayoutManager w elemencie RecyclerView. Omówimy tę technikę w późniejszych ćwiczeniach 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 sposób ich wyświetlania w ViewHolder. Powiąż adapter z urządzeniem RecyclerView.

    Po uruchomieniu RecyclerView użyje adaptera, aby określić, jak chcesz wyświetlić dane na ekranie.

    Adapter wymaga zaimplementowania tych metod:
    getItemCount(), aby zwrócić liczbę elementów.
    onCreateViewHolder(), aby zwrócić ViewHolder element z listy.
    onBindViewHolder(), aby dostosować dane do widoków elementu na liście.

  • ViewHolder
    ViewHolder zawiera informacje o wyświetlaniu jednego elementu z układu elementu.
  • Metoda onBindViewHolder() w adapterze dostosowuje dane do widoków. Zawsze zastępujesz tę metodę. Zazwyczaj onBindViewHolder() zwiększa układ elementu i umieszcza dane w widokach układu.
  • RecyclerView nie wie nic o danych, więc Adapter musi informować RecyclerView, gdy dane się zmienią. Użyj notifyDataSetChanged(), by powiadomić Adapter, że dane zostały zmienione.

Kurs Udacity:

Dokumentacja dla programistów Androida:

Ta sekcja zawiera listę możliwych zadań domowych dla uczniów, którzy pracują w ramach tego ćwiczenia w ramach kursu prowadzonego przez nauczyciela. To nauczyciel może wykonać te czynności:

  • W razie potrzeby przypisz zadanie domowe.
  • Poinformuj uczniów, jak przesyłać zadania domowe.
  • Oceń projekty domowe.

Nauczyciele mogą wykorzystać te sugestie tak długo, jak chcą lub chcą, i mogą przypisać dowolne zadanie domowe.

Jeśli samodzielnie wykonujesz te ćwiczenia z programowania, możesz sprawdzić swoją wiedzę w tych zadaniach domowych.

Odpowiedz na te pytania

Pytanie 1

Jak RecyclerView wyświetla elementy? Wybierz wszystkie pasujące odpowiedzi.

▢ wyświetla listę na siatce lub liście.

▢ przewijanie w pionie lub poziomie.

▢ Przewijanie ukośne na większych urządzeniach, takich jak tablety.

▢ zezwala na układy niestandardowe, gdy lista lub siatka nie wystarczają do użytku.

Pytanie 2

Jakie są zalety korzystania z RecyclerView? Wybierz wszystkie pasujące odpowiedzi.

▢ efektywnie wyświetlają duże listy.

▢ Automatycznie aktualizuje dane.

▢ minimalizuje odświeżanie, gdy element jest aktualizowany, usuwany lub dodawany do listy.

▢ wykorzystuje ponownie widok przewijany poza ekranem, by wyświetlić kolejny element przewijany na ekranie.

Pytanie 3

Jakie są przyczyny użycia adapterów? Wybierz wszystkie pasujące odpowiedzi.

▢ rozdzielenie wątpliwości ułatwia zmianę i testowanie kodu.

RecyclerView nie ma wpływu na wyświetlane dane.

▢ Warstwy przetwarzania danych nie muszą się martwić o sposób wyświetlania danych.

▢ Aplikacja będzie działać szybciej.

Pytanie 4

Które z poniższych stwierdzeń na temat firmy ViewHolder są prawdziwe? Wybierz wszystkie pasujące odpowiedzi.

▢ Układ ViewHolder jest zdefiniowany w plikach układu XML.

▢ Dla każdego zbioru danych w zbiorze danych jest dostępny ViewHolder.

RecyclerView może zawierać więcej niż 1 element ViewHolder.

Adapter wiąże dane z elementem ViewHolder.

Rozpocznij następną lekcję: 7.2: DiffUtil i wiązanie danych za pomocą RecyclerView