Grundlagen von Android und Kotlin 07.1: Grundlagen von RecyclerView

Dieses Codelab ist Teil des Kurses „Grundlagen von Android und Kotlin“. Sie können diesen Kurs am besten nutzen, wenn Sie die Codelabs der Reihe nach durcharbeiten. Alle Codelabs des Kurses sind auf der Landingpage für Codelabs zu den Grundlagen von Android und Kotlin aufgeführt.

Einführung

In diesem Codelab erfahren Sie, wie Sie mit RecyclerView Listen mit Elementen anzeigen. In diesem Codelab bauen Sie auf der Schlaftracker-App aus der vorherigen Codelab-Reihe auf und lernen eine bessere und vielseitigere Methode zum Anzeigen von Daten kennen, indem Sie ein RecyclerView mit einer empfohlenen Architektur verwenden.

Was Sie bereits wissen sollten

Sie sollten mit Folgendem vertraut sein:

  • Eine einfache Benutzeroberfläche mit einer Aktivität, Fragmenten und Ansichten erstellen.
  • Zwischen Fragmenten wechseln und safeArgs verwenden, um Daten zwischen Fragmenten zu übergeben.
  • Verwendung von Ansichtsmodellen, Ansichtsmodell-Factories, Transformationen und LiveData sowie deren Beobachtern.
  • Room-Datenbank erstellen, DAO erstellen und Entitäten definieren.
  • Verwendung von Coroutinen für Datenbankaufgaben und andere lang andauernde Aufgaben.

Lerninhalte

  • So verwenden Sie ein RecyclerView mit einem Adapter und einem ViewHolder, um eine Liste von Elementen anzuzeigen.

Aufgaben

  • Ändern Sie die App „TrackMySleepQuality“ aus der vorherigen Lektion so, dass ein RecyclerView verwendet wird, um Daten zur Schlafqualität anzuzeigen.

In diesem Codelab erstellen Sie den RecyclerView-Teil einer App, die die Schlafqualität erfasst. Die App verwendet eine Room-Datenbank, um Schlafdaten im Zeitverlauf zu speichern.

Die Schlaf-Tracker-App für Einsteiger hat zwei Bildschirme, die durch Fragmente dargestellt werden (siehe Abbildung unten).

Auf dem ersten Bildschirm links befinden sich Schaltflächen zum Starten und Beenden des Trackings. Auf diesem Bildschirm werden auch alle Schlafdaten des Nutzers angezeigt. Mit der Schaltfläche Löschen werden alle Daten, die die App für den Nutzer erhoben hat, dauerhaft gelöscht. Auf dem zweiten Bildschirm rechts können Sie eine Bewertung der Schlafqualität auswählen.

Diese App verwendet eine vereinfachte Architektur mit einem UI-Controller, ViewModel und LiveData. Die App verwendet auch eine Room-Datenbank, um Schlafdaten dauerhaft zu speichern.

Die Liste der Schlafnächte, die auf dem ersten Bildschirm angezeigt wird, ist funktional, aber nicht schön. Die App verwendet einen komplexen Formatierer, um Textstrings für die Textansicht und Zahlen für die Qualität zu erstellen. Außerdem ist dieses Design nicht skalierbar. Nachdem Sie alle Probleme in diesem Codelab behoben haben, hat die endgültige App dieselbe Funktionalität und der Hauptbildschirm sieht so aus:

Die Anzeige einer Liste oder eines Rasters mit Daten ist eine der häufigsten UI-Aufgaben in Android. Listen können einfach oder sehr komplex sein. In einer Liste von Textansichten können einfache Daten wie eine Einkaufsliste angezeigt werden. In einer komplexen Liste, z. B. einer kommentierten Liste von Urlaubszielen, werden dem Nutzer möglicherweise viele Details in einem scrollbaren Raster mit Überschriften angezeigt.

Um all diese Anwendungsfälle zu unterstützen, bietet Android das RecyclerView-Widget.

Der größte Vorteil von RecyclerView ist, dass es für große Listen sehr effizient ist:

  • Standardmäßig verarbeitet RecyclerView nur Elemente, die derzeit auf dem Bildschirm sichtbar sind, oder zeichnet sie. Wenn Ihre Liste beispielsweise tausend Elemente enthält, aber nur 10 Elemente sichtbar sind, wird RecyclerView nur so viel Arbeit leisten, dass 10 Elemente auf dem Bildschirm dargestellt werden. Wenn der Nutzer scrollt, ermittelt RecyclerView, welche neuen Elemente auf dem Bildschirm angezeigt werden sollen, und führt nur die erforderlichen Schritte aus, um diese Elemente darzustellen.
  • Wenn ein Element vom Bildschirm scrollt, werden die Aufrufe des Elements wiederverwendet. Das bedeutet, dass das Element mit neuen Inhalten gefüllt wird, die auf dem Bildschirm eingeblendet werden. Durch dieses RecyclerView-Verhalten wird viel Verarbeitungszeit gespart und das Scrollen in Listen ist flüssiger.
  • Wenn sich ein Element ändert, kann RecyclerView dieses eine Element aktualisieren, anstatt die gesamte Liste neu zu zeichnen. Das ist eine enorme Effizienzsteigerung beim Anzeigen von Listen komplexer Elemente.

In der folgenden Sequenz sehen Sie, dass eine Ansicht mit Daten gefüllt wurde: ABC. Nachdem diese Ansicht vom Bildschirm gescrollt ist, verwendet RecyclerView die Ansicht für neue Daten, XYZ.

Das Adaptermuster

Wenn Sie in Länder reisen, in denen andere Steckdosen verwendet werden, wissen Sie wahrscheinlich, wie Sie Ihre Geräte mit einem Adapter an Steckdosen anschließen können. Mit dem Adapter können Sie einen Steckertyp in einen anderen umwandeln, d. h. eine Schnittstelle in eine andere konvertieren.

Das Adaptermuster in der Softwareentwicklung hilft einem Objekt, mit einer anderen API zu arbeiten. RecyclerView verwendet einen Adapter, um App-Daten in ein Format zu transformieren, das RecyclerView anzeigen kann. Dabei wird nicht geändert, wie die App die Daten speichert und verarbeitet. Für die Schlaftracking-App erstellen Sie einen Adapter, der Daten aus der Room-Datenbank in ein Format umwandelt, das RecyclerView anzeigen kann, ohne die ViewModel zu ändern.

RecyclerView implementieren

Damit Ihre Daten in einem RecyclerView angezeigt werden können, benötigen Sie die folgenden Elemente:

  • Anzuzeigende Daten.
  • Eine RecyclerView-Instanz, die in Ihrer Layoutdatei definiert ist und als Container für die Ansichten dient.
  • Ein Layout für ein Datenelement.
    Wenn alle Listenelemente gleich aussehen, können Sie dasselbe Layout für alle verwenden. Das ist aber nicht zwingend. Das Elementlayout muss separat vom Layout des Fragments erstellt werden, damit jeweils eine Elementansicht erstellt und mit Daten gefüllt werden kann.
  • Ein Layoutmanager.
    Der Layoutmanager kümmert sich um die Organisation (das Layout) von UI-Komponenten in einer Ansicht.
  • Ein View-Holder.
    Der View-Holder erweitert die Klasse ViewHolder. Sie enthält die Ansichtsinformationen zum Anzeigen eines Elements aus dem Layout des Elements. View-Holder fügen auch Informationen hinzu, die RecyclerView verwendet, um Views effizient auf dem Bildschirm zu verschieben.
  • Ein Adapter
    : Der Adapter verbindet Ihre Daten mit dem RecyclerView. Die Daten werden so angepasst, dass sie in einem ViewHolder dargestellt werden können. Ein RecyclerView verwendet den Adapter, um herauszufinden, wie die Daten auf dem Bildschirm angezeigt werden sollen.

In dieser Aufgabe fügen Sie Ihrer Layoutdatei ein RecyclerView hinzu und richten ein Adapter ein, um dem RecyclerView Schlafdaten zur Verfügung zu stellen.

Schritt 1: RecyclerView mit LayoutManager hinzufügen

In diesem Schritt ersetzen Sie die ScrollView durch eine RecyclerView in der Datei fragment_sleep_tracker.xml.

  1. Laden Sie die App RecyclerViewFundamentals-Starter von GitHub herunter.
  2. Erstellen Sie die App und führen Sie sie aus. Die Daten werden als einfacher Text angezeigt.
  3. Öffnen Sie die Layoutdatei fragment_sleep_tracker.xml in Android Studio auf dem Tab Design.
  4. Löschen Sie im Bereich Komponentenbaum die ScrollView. Mit dieser Aktion wird auch die TextView in der ScrollView gelöscht.
  5. Scrollen Sie im Bereich Palette in der Liste der Komponententypen auf der linken Seite zu Container und wählen Sie diese Option aus.
  6. Ziehen Sie ein RecyclerView aus dem Bereich Palette in den Bereich Komponentenbaum. Legen Sie das RecyclerView in das ConstraintLayout ein.

  1. Wenn ein Dialogfeld geöffnet wird, in dem Sie gefragt werden, ob Sie eine Abhängigkeit hinzufügen möchten, klicken Sie auf OK, damit Android Studio die Abhängigkeit recyclerview zu Ihrer Gradle-Datei hinzufügt. Das kann einige Sekunden dauern. Danach wird Ihre App synchronisiert.

  1. Öffnen Sie die Datei build.gradle des Moduls, scrollen Sie zum Ende und notieren Sie sich die neue Abhängigkeit, die in etwa so aussieht:
implementation 'androidx.recyclerview:recyclerview:1.0.0'
  1. Zurück zu fragment_sleep_tracker.xml wechseln
  2. Suchen Sie auf dem Tab Text nach dem unten gezeigten RecyclerView-Code:
<androidx.recyclerview.widget.RecyclerView
   android:layout_width="match_parent"
   android:layout_height="match_parent" />
  1. Geben Sie für RecyclerView den id sleep_list an.
android:id="@+id/sleep_list"
  1. Positionieren Sie die RecyclerView so, dass sie den verbleibenden Teil des Bildschirms innerhalb der ConstraintLayout einnimmt. Dazu müssen Sie die Oberkante von RecyclerView an die Schaltfläche Start, die Unterkante an die Schaltfläche Löschen und die Seiten an das übergeordnete Element binden. Legen Sie im Layout-Editor oder in XML die Layoutbreite und ‑höhe mit dem folgenden Code auf 0 dp fest:
    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. Fügen Sie dem RecyclerView-XML einen Layoutmanager hinzu. Jede RecyclerView benötigt einen Layoutmanager, der angibt, wie Elemente in der Liste positioniert werden. Android bietet ein LinearLayoutManager, mit dem die Elemente standardmäßig in einer vertikalen Liste mit Zeilen in voller Breite angeordnet werden.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
  1. Wechseln Sie zum Tab Design. Die hinzugefügten Einschränkungen haben dazu geführt, dass sich RecyclerView ausgedehnt hat, um den verfügbaren Platz zu füllen.

Schritt 2: Layout für Listenelement und Text-ViewHolder erstellen

Die RecyclerView ist nur ein Container. In diesem Schritt erstellen Sie das Layout und die Infrastruktur für die Elemente, die in der RecyclerView angezeigt werden sollen.

Damit Sie so schnell wie möglich ein funktionierendes RecyclerView erhalten, verwenden Sie zuerst ein einfaches Listenelement, in dem nur die Schlafqualität als Zahl angezeigt wird. Dazu benötigen Sie einen View-Holder, TextItemViewHolder. Außerdem benötigen Sie eine Ansicht, also eine TextView, für die Daten. In einem späteren Schritt erfahren Sie mehr über View-Holder und wie Sie alle Schlafdaten anordnen.

  1. Erstellen Sie eine Layoutdatei mit dem Namen text_item_view.xml. Es spielt keine Rolle, was Sie als Stammelement verwenden, da Sie den Vorlagencode ersetzen.
  2. Löschen Sie in text_item_view.xml den gesamten angegebenen Code.
  3. Fügen Sie ein TextView mit einem 16dp-Abstand am Anfang und Ende sowie einer Textgröße von 24sp hinzu. Die Breite soll dem übergeordneten Element entsprechen und die Höhe soll sich an den Inhalt anpassen. Da diese Ansicht in RecyclerView angezeigt wird, müssen Sie sie nicht in ein ViewGroup einfügen.
<?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. Öffnen Sie Util.kt. Scrollen Sie zum Ende und fügen Sie die unten gezeigte Definition hinzu, mit der die Klasse TextItemViewHolder erstellt wird. Fügen Sie den Code am Ende der Datei nach der letzten schließenden geschweiften Klammer ein. Der Code gehört in Util.kt, da dieser View-Holder temporär ist und später ersetzt wird.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
  1. Importieren Sie bei Aufforderung android.widget.TextView und androidx.recyclerview.widget.RecyclerView.

Schritt 3: SleepNightAdapter erstellen

Die Hauptaufgabe bei der Implementierung von RecyclerView ist die Erstellung des Adapters. Sie haben einen einfachen View-Holder für die Artikelansicht und ein Layout für jeden Artikel. Sie können jetzt einen Adapter erstellen. Der Adapter erstellt einen View-Holder und füllt ihn mit Daten für die anzuzeigende RecyclerView.

  1. Erstellen Sie im Paket sleeptracker eine neue Kotlin-Klasse mit dem Namen SleepNightAdapter.
  2. Lassen Sie die Klasse SleepNightAdapter die Klasse RecyclerView.Adapter erweitern. Die Klasse heißt SleepNightAdapter, weil sie ein SleepNight-Objekt in etwas umwandelt, das RecyclerView verwenden kann. Der Adapter muss wissen, welcher View-Holder verwendet werden soll. Übergeben Sie daher TextItemViewHolder. Importieren Sie die erforderlichen Komponenten, wenn Sie dazu aufgefordert werden. Anschließend wird ein Fehler angezeigt, da obligatorische Methoden implementiert werden müssen.
class SleepNightAdapter: RecyclerView.Adapter<TextItemViewHolder>() {}
  1. Erstellen Sie auf der obersten Ebene von SleepNightAdapter eine listOf-SleepNight-Variable, um die Daten zu speichern.
var data =  listOf<SleepNight>()
  1. Überschreibe in SleepNightAdapter getItemCount(), um die Größe der Liste der Schlafnächte in data zurückzugeben. Der RecyclerView muss wissen, wie viele Elemente der Adapter für die Anzeige hat. Dazu wird getItemCount() aufgerufen.
override fun getItemCount() = data.size
  1. Überschreiben Sie in SleepNightAdapter die Funktion onBindViewHolder(), wie unten gezeigt.

    Die Funktion onBindViewHolder() wird von RecyclerView aufgerufen, um die Daten für ein Listenelement an der angegebenen Position anzuzeigen. Die Methode onBindViewHolder() hat also zwei Argumente: einen View-Holder und eine Position der zu bindenden Daten. In dieser App ist der Platzhalter TextItemViewHolder und die Position die Position in der Liste.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}
  1. Erstellen Sie in onBindViewHolder() eine Variable für ein Element an einer bestimmten Position in den Daten.
 val item = data[position]
  1. Die von Ihnen erstellte ViewHolder hat ein Attribut mit dem Namen textView. Legen Sie in onBindViewHolder() die text des textView auf die Schlafqualitätszahl fest. In diesem Code wird nur eine Liste von Zahlen angezeigt. Anhand dieses einfachen Beispiels können Sie jedoch sehen, wie der Adapter die Daten in den View-Holder und auf den Bildschirm überträgt.
holder.textView.text = item.sleepQuality.toString()
  1. Überschreiben und implementieren Sie in SleepNightAdapter onCreateViewHolder(). Diese Methode wird aufgerufen, wenn für RecyclerView ein View-Holder zur Darstellung eines Elements benötigt wird.

    Diese Funktion verwendet zwei Parameter und gibt einen ViewHolder zurück. Der Parameter parent, die Ansichtsgruppe, die den Ansichtshalter enthält, ist immer RecyclerView. Der Parameter viewType wird verwendet, wenn es mehrere Ansichten in derselben RecyclerView gibt. Wenn Sie beispielsweise eine Liste mit Textansichten, ein Bild und ein Video in dasselbe RecyclerView einfügen, muss die Funktion onCreateViewHolder() wissen, welcher Ansichtstyp verwendet werden soll.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}
  1. Erstelle in onCreateViewHolder() eine Instanz von LayoutInflater.

    Der LayoutInflater weiß, wie Ansichten aus XML-Layouts erstellt werden. context enthält Informationen dazu, wie die Ansicht richtig aufgebläht wird. In einem Adapter für eine Recycler-Ansicht übergeben Sie immer den Kontext der parent-Ansichtsgruppe, also RecyclerView.
val layoutInflater = LayoutInflater.from(parent.context)
  1. Erstellen Sie in onCreateViewHolder() das view, indem Sie layoutinflater bitten, es aufzublasen.

    Übergeben Sie das XML-Layout für die Ansicht und die parent-Ansichtsgruppe für die Ansicht. Das dritte (boolesche) Argument ist attachToRoot. Dieses Argument muss false sein, da RecyclerView dieses Element zur richtigen Zeit für Sie in die Ansichtshierarchie einfügt.
val view = layoutInflater
       .inflate(R.layout.text_item_view, parent, false) as TextView
  1. Geben Sie in onCreateViewHolder() ein TextItemViewHolder zurück, das mit view erstellt wurde.
return TextItemViewHolder(view)
  1. Der Adapter muss RecyclerView darüber informieren, wenn sich data ändert, da RecyclerView nichts über die Daten weiß. Es kennt nur die View-Holder, die der Adapter ihm zur Verfügung stellt.

    Damit die RecyclerView weiß, wann sich die angezeigten Daten geändert haben, fügen Sie der data-Variablen oben in der SleepNightAdapter-Klasse einen benutzerdefinierten Setter hinzu. Weisen Sie im Setter data einen neuen Wert zu und rufen Sie dann notifyDataSetChanged() auf, um die Liste mit den neuen Daten neu zu zeichnen.
var data =  listOf<SleepNight>()
   set(value) {
       field = value
       notifyDataSetChanged()
   }

Schritt 4: RecyclerView über den Adapter informieren

RecyclerView muss wissen, welcher Adapter zum Abrufen von Viewholdern verwendet werden soll.

  1. Öffnen Sie SleepTrackerFragment.kt.
  2. Erstellen Sie in onCreateview() einen Adapter. Fügen Sie diesen Code nach der Erstellung des ViewModel-Modells und vor der return-Anweisung ein.
val adapter = SleepNightAdapter()
  1. Verknüpfen Sie adapter mit RecyclerView.
binding.sleepList.adapter = adapter
  1. Bereinigen Sie Ihr Projekt und erstellen Sie es neu, um das binding-Objekt zu aktualisieren.

    Wenn weiterhin Fehler im Zusammenhang mit binding.sleepList oder binding.FragmentSleepTrackerBinding angezeigt werden, löschen Sie die Caches und starten Sie das Gerät neu. Wählen Sie Datei > Caches ungültig machen / Neu starten aus.

    Wenn Sie die App jetzt ausführen, treten keine Fehler auf. Wenn Sie auf Start und dann auf Beenden tippen, werden jedoch keine Daten angezeigt.

Schritt 5: Daten in den Adapter übertragen

Bisher haben Sie einen Adapter und eine Möglichkeit, Daten vom Adapter in die RecyclerView zu übertragen. Jetzt müssen Sie Daten aus ViewModel in den Adapter übertragen.

  1. Öffnen Sie SleepTrackerViewModel.
  2. Suchen Sie nach der Variablen nights, in der alle Schlafnächte gespeichert sind. Das sind die Daten, die angezeigt werden sollen. Die Variable nights wird durch Aufrufen von getAllNights() für die Datenbank festgelegt.
  3. Entfernen Sie private aus nights, da Sie einen Observer erstellen, der auf diese Variable zugreifen muss. Ihre Erklärung sollte so aussehen:
val nights = database.getAllNights()
  1. Öffnen Sie im Paket database die Datei SleepDatabaseDao.
  2. Suchen Sie die Funktion getAllNights(). Diese Funktion gibt eine Liste von SleepNight-Werten als LiveData zurück. Das bedeutet, dass die Variable nights den Wert LiveData enthält, der von Room aktualisiert wird. Sie können nights beobachten, um zu sehen, wann sich der Wert ändert.
  3. Öffnen Sie SleepTrackerFragment.
  4. Erstellen Sie in onCreateView() unter der Erstellung von adapter einen Observer für die Variable nights.

    Wenn Sie viewLifecycleOwner des Fragments als Lifecycle-Inhaber angeben, ist dieser Observer nur aktiv, wenn RecyclerView auf dem Bildschirm angezeigt wird.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   })
  1. Weisen Sie im Observer den Wert der nights des Adapters der data zu, sobald Sie einen Wert ungleich null erhalten. Hier ist der vollständige Code für den Observer und das Festlegen der Daten:
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   it?.let {
       adapter.data = it
   }
})
  1. Code erstellen und ausführen

    Wenn dein Adapter funktioniert, werden die Zahlen zur Schlafqualität als Liste angezeigt. Auf dem Screenshot links wird nach dem Tippen auf Start „-1“ angezeigt. Auf dem Screenshot rechts siehst du die aktualisierte Schlafqualitätszahl, nachdem du auf Beenden getippt und eine Qualitätsbewertung ausgewählt hast.

Schritt 6: Informationen zum Recycling von Viewholdern

RecyclerView verwendet View-Holder wieder. Wenn eine Ansicht vom Bildschirm scrollt, wird sie von RecyclerView für die Ansicht wiederverwendet, die gerade auf den Bildschirm scrollt.

Da diese View-Holder wiederverwendet werden, muss onBindViewHolder() alle Anpassungen festlegen oder zurücksetzen, die zuvor für einen View-Holder festgelegt wurden.

Sie können beispielsweise die Textfarbe in Ansichtshaltern, die Qualitätsbewertungen von 1 oder weniger enthalten und für schlechten Schlaf stehen, auf Rot setzen.

  1. Fügen Sie in der Klasse SleepNightAdapter am Ende von onBindViewHolder() den folgenden Code hinzu.
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
}
  1. Starten Sie die App.
  2. Wenn Sie Daten mit niedriger Schlafqualität hinzufügen, wird die Zahl rot dargestellt.
  3. Füge hohe Bewertungen für die Schlafqualität hinzu, bis auf dem Display eine rote hohe Zahl angezeigt wird.

    Da RecyclerView View-Holder wiederverwendet, wird schließlich einer der roten View-Holder für eine hohe Qualitätsbewertung wiederverwendet. Die hohe Altersfreigabe wird fälschlicherweise rot angezeigt.

  1. Fügen Sie dazu eine else-Anweisung hinzu, um die Farbe auf Schwarz zu setzen, wenn die Qualität nicht kleiner oder gleich 1 ist.

    Da beide Bedingungen explizit sind, verwendet der ViewHolder für jedes Element die richtige Textfarbe.
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
} else {
   // reset
   holder.textView.setTextColor(Color.BLACK) // black
}
  1. Führen Sie die App aus. Die Zahlen sollten immer die richtige Farbe haben.

Glückwunsch! Sie haben jetzt eine voll funktionsfähige einfache RecyclerView.

In dieser Aufgabe ersetzen Sie den einfachen View-Holder durch einen, der mehr Daten für eine Schlafnacht anzeigen kann.

Das einfache ViewHolder, das Sie Util.kt hinzugefügt haben, umschließt ein TextView in einem TextItemViewHolder.

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

Warum wird in RecyclerView nicht einfach ein TextView verwendet? Diese eine Zeile Code bietet viele Funktionen. Ein ViewHolder beschreibt eine Artikelansicht und Metadaten zu ihrer Position im RecyclerView. RecyclerView ist auf diese Funktion angewiesen, um die Ansicht beim Scrollen der Liste richtig zu positionieren und interessante Dinge wie das Animieren von Ansichten zu ermöglichen, wenn Elemente in der Adapter hinzugefügt oder entfernt werden.

Wenn RecyclerView auf die im ViewHolder gespeicherten Ansichten zugreifen muss, kann es dies über die itemView-Property des Ansichtsinhabers tun. RecyclerView verwendet itemView, wenn ein Element gebunden wird, um auf dem Bildschirm angezeigt zu werden, wenn Dekorationen wie ein Rahmen um eine Ansicht gezeichnet werden und zur Implementierung von Bedienungshilfen.

Schritt 1: Artikel-Layout erstellen

In diesem Schritt erstellen Sie die Layoutdatei für ein Element. Das Layout besteht aus einem ConstraintLayout mit einem ImageView für die Schlafqualität, einem TextView für die Schlafdauer und einem TextView für die Qualität als Text. Da Sie bereits Layouts erstellt haben, können Sie den bereitgestellten XML-Code kopieren und einfügen.

  1. Erstellen Sie eine neue Layoutressourcendatei mit dem Namen list_item_sleep_night.
  2. Ersetzen Sie den gesamten Code in der Datei durch den folgenden Code. Machen Sie sich dann mit dem gerade erstellten Layout vertraut.
<?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. Wechseln Sie in Android Studio zum Tab Design. In der Designansicht sieht Ihr Layout wie im Screenshot links unten aus. In der Blueprint-Ansicht sieht es so aus wie im Screenshot rechts.

Schritt 2: ViewHolder erstellen

  1. Öffnen Sie SleepNightAdapter.kt.
  2. Erstellen Sie in SleepNightAdapter eine Klasse namens ViewHolder, die RecyclerView.ViewHolder erweitert.
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}
  1. Rufen Sie in ViewHolder Verweise auf die Ansichten ab. Sie benötigen einen Verweis auf die Ansichten, die durch diese ViewHolder aktualisiert werden. Jedes Mal, wenn Sie diese ViewHolder binden, müssen Sie auf das Bild und beide Textansichten zugreifen. (Sie konvertieren diesen Code später für die Verwendung von Data Binding.)
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)

Schritt 3: ViewHolder in SleepNightAdapter verwenden

  1. Verwenden Sie in der SleepNightAdapter-Definition anstelle von TextItemViewHolder die SleepNightAdapter.ViewHolder, die Sie gerade erstellt haben.
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {

Aktualisieren Sie onCreateViewHolder():

  1. Ändern Sie die Signatur von onCreateViewHolder(), damit ViewHolder zurückgegeben wird.
  2. Ändern Sie den Layout-Inflater, damit die richtige Layout-Ressource list_item_sleep_night verwendet wird.
  3. Entferne die Übertragung auf TextView.
  4. Geben Sie anstelle eines TextItemViewHolder ein ViewHolder zurück.

    Hier ist die aktualisierte onCreateViewHolder()-Funktion:
    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)
    }

Aktualisieren Sie onBindViewHolder():

  1. Ändern Sie die Signatur von onBindViewHolder() so, dass der Parameter holder ein ViewHolder anstelle eines TextItemViewHolder ist.
  2. Löschen Sie in onBindViewHolder() den gesamten Code mit Ausnahme der Definition von item.
  3. Definieren Sie eine val res, die eine Referenz auf die resources für diese Ansicht enthält.
val res = holder.itemView.context.resources
  1. Legen Sie den Text der sleepLength-Textansicht auf die Dauer fest. Kopieren Sie den folgenden Code, der eine Formatierungsfunktion aufruft, die mit dem Startcode bereitgestellt wird.
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
  1. Das führt zu einem Fehler, da convertDurationToFormatted() definiert werden muss. Öffnen Sie Util.kt und entfernen Sie die Kommentarzeichen für den Code und die zugehörigen Importe. Wählen Sie Code > Comment with Line comments aus.
  2. Zurück in onBindViewHolder() können Sie mit convertNumericQualityToString() die Qualität festlegen.
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
  1. Möglicherweise müssen Sie diese Funktionen manuell importieren.
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString
  1. Legen Sie das richtige Symbol für die Qualität fest. Das neue ic_sleep_active-Symbol ist im Startercode enthalten.
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. Hier ist die aktualisierte onBindViewHolder()-Funktion, in der alle Daten für ViewHolder festgelegt werden:
   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. Führen Sie Ihre App aus. Auf dem Display sollte nun das Symbol für die Schlafqualität zusammen mit Text für die Schlafdauer und die Schlafqualität angezeigt werden.

RecyclerView ist jetzt abgeschlossen. Sie haben gelernt, wie Sie ein Adapter und ein ViewHolder implementieren und zusammenfügen, um eine Liste mit einem RecyclerView Adapter anzuzeigen.

Ihr bisheriger Code zeigt, wie ein Adapter und ein View-Holder erstellt werden. Sie können diesen Code jedoch verbessern. Der Code zum Anzeigen und Verwalten von ViewHoldern ist vermischt und onBindViewHolder() kennt Details zum Aktualisieren von ViewHolder.

In einer Produktions-App haben Sie möglicherweise mehrere View-Holder, komplexere Adapter und mehrere Entwickler, die Änderungen vornehmen. Sie sollten Ihren Code so strukturieren, dass alles, was mit einem View-Holder zusammenhängt, nur im View-Holder enthalten ist.

Schritt 1: onBindViewHolder() umgestalten

In diesem Schritt refaktorieren Sie den Code und verschieben die gesamte ViewHolder-Funktionalität in ViewHolder. Ziel dieser Refaktorierung ist es nicht, die Darstellung der App für den Nutzer zu ändern, sondern die Arbeit am Code für Entwickler zu vereinfachen und sicherer zu machen. Glücklicherweise bietet Android Studio Tools, die Ihnen dabei helfen.

  1. Wählen Sie in SleepNightAdapter in onBindViewHolder() alles außer der Anweisung zum Deklarieren der Variablen item aus.
  2. Klicken Sie mit der rechten Maustaste und wählen Sie Refactor > Extract > Function (Umgestalten > Extrahieren > Funktion) aus.
  3. Nennen Sie die Funktion bind und übernehmen Sie die vorgeschlagenen Parameter. Klicken Sie auf OK.

    Die Funktion bind() wird unter onBindViewHolder() platziert.
    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. Bewegen Sie den Cursor auf das Wort holder des Parameters holder von bind(). Drücken Sie Alt+Enter (Option+Enter auf einem Mac), um das Intention-Menü zu öffnen. Wählen Sie Parameter in Empfänger konvertieren aus, um dies in eine Erweiterungsfunktion mit der folgenden Signatur zu konvertieren:
private fun ViewHolder.bind(item: SleepNight) {...}
  1. Schneiden Sie die bind()-Funktion aus und fügen Sie sie in ViewHolder ein.
  2. Machen Sie bind() öffentlich.
  3. Importieren Sie bind() bei Bedarf in den Adapter.
  4. Da sie sich jetzt in der ViewHolder befindet, können Sie den ViewHolder-Teil der Signatur entfernen. Hier ist der endgültige Code für die Funktion bind() in der Klasse 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
   })
}

Schritt 2: onCreateViewHolder umgestalten

Die Methode onCreateViewHolder() im Adapter ruft derzeit die Ansicht aus der Layoutressource für die ViewHolder ab. Die Inflation hat jedoch nichts mit dem Adapter zu tun, sondern mit dem ViewHolder. Die Inflation sollte im ViewHolder erfolgen.

  1. Wählen Sie in onCreateViewHolder() den gesamten Code im Funktionskörper aus.
  2. Klicken Sie mit der rechten Maustaste und wählen Sie Refactor > Extract > Function (Umgestalten > Extrahieren > Funktion) aus.
  3. Nennen Sie die Funktion from und übernehmen Sie die vorgeschlagenen Parameter. Klicken Sie auf OK.
  4. Setzen Sie den Cursor auf den Funktionsnamen from. Drücken Sie Alt+Enter (Option+Enter auf einem Mac), um das Intention-Menü zu öffnen.
  5. Wählen Sie In Begleitobjekt verschieben aus. Die from()-Funktion muss sich in einem Companion-Objekt befinden, damit sie für die ViewHolder-Klasse aufgerufen werden kann, nicht für eine ViewHolder-Instanz.
  6. Verschieben Sie das companion-Objekt in die ViewHolder-Klasse.
  7. Machen Sie from() öffentlich.
  8. Ändern Sie in onCreateViewHolder() die return-Anweisung so, dass das Ergebnis des Aufrufs von from() in der Klasse ViewHolder zurückgegeben wird.

    Die fertigen Methoden onCreateViewHolder() und from() sollten wie im folgenden Code aussehen. Ihr Code sollte ohne Fehler kompiliert und ausgeführt werden.
    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. Ändern Sie die Signatur der Klasse ViewHolder so, dass der Konstruktor privat ist. Da from() jetzt eine Methode ist, die eine neue ViewHolder-Instanz zurückgibt, gibt es keinen Grund mehr, den Konstruktor von ViewHolder aufzurufen.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
  1. Führen Sie die App aus. Sie sollte wie zuvor erstellt und ausgeführt werden. Das ist das gewünschte Ergebnis nach dem Refactoring.

Android Studio-Projekt: RecyclerViewFundamentals

  • Die Anzeige einer Liste oder eines Rasters mit Daten ist eine der häufigsten UI-Aufgaben in Android. RecyclerView ist so konzipiert, dass es auch bei der Anzeige extrem großer Listen effizient ist.
  • RecyclerView führt nur die Arbeit aus, die zum Verarbeiten oder Zeichnen von Elementen erforderlich ist, die derzeit auf dem Bildschirm sichtbar sind.
  • Wenn ein Element aus dem Bildschirm gescrollt wird, werden seine Ansichten wiederverwendet. Das bedeutet, dass das Element mit neuen Inhalten gefüllt wird, die auf dem Bildschirm eingeblendet werden.
  • Das Adaptermuster in der Softwareentwicklung hilft einem Objekt, mit einer anderen API zusammenzuarbeiten. RecyclerView verwendet einen Adapter, um App-Daten in ein Format zu transformieren, das angezeigt werden kann. Dazu muss nicht geändert werden, wie die App Daten speichert und verarbeitet.

Damit Ihre Daten in einem RecyclerView angezeigt werden können, benötigen Sie die folgenden Elemente:

  • RecyclerView
    : Wenn Sie eine Instanz von RecyclerView erstellen möchten, definieren Sie ein <RecyclerView>-Element in der Layoutdatei.
  • LayoutManager
    : Ein RecyclerView verwendet einen LayoutManager, um das Layout der Elemente im RecyclerView zu organisieren, z. B. in einem Raster oder einer linearen Liste.

    Legen Sie im <RecyclerView> in der Layoutdatei das Attribut app:layoutManager auf den Layoutmanager fest, z. B. LinearLayoutManager oder GridLayoutManager.

    Sie können den LayoutManager für ein RecyclerView auch programmatisch festlegen. Diese Technik wird in einem späteren Codelab behandelt.
  • Layout für jedes Element
    : Erstellen Sie ein Layout für ein Datenelement in einer XML-Layoutdatei.
  • Adapter
    : Erstellen Sie einen Adapter, der die Daten vorbereitet und festlegt, wie sie in einem ViewHolder dargestellt werden. Verknüpfen Sie den Adapter mit dem RecyclerView.

    Wenn RecyclerView ausgeführt wird, wird der Adapter verwendet, um zu ermitteln, wie die Daten auf dem Bildschirm angezeigt werden sollen.

    Für den Adapter müssen Sie die folgenden Methoden implementieren:
    getItemCount(), um die Anzahl der Elemente zurückzugeben.
    onCreateViewHolder(), um das ViewHolder für ein Element in der Liste zurückzugeben.
    onBindViewHolder(), um die Daten an die Ansichten für ein Element in der Liste anzupassen.

  • ViewHolder
    Ein ViewHolder enthält die Ansichtsinformationen zum Anzeigen eines Elements aus dem Layout des Elements.
  • Die Methode onBindViewHolder() im Adapter passt die Daten an die Ansichten an. Sie überschreiben diese Methode immer. Normalerweise wird das Layout für ein Element durch onBindViewHolder() aufgebläht und die Daten werden in die Ansichten im Layout eingefügt.
  • Da RecyclerView nichts über die Daten weiß, muss Adapter RecyclerView informieren, wenn sich die Daten ändern. Verwenden Sie notifyDataSetChanged(), um die Adapter darüber zu informieren, dass sich die Daten geändert haben.

Udacity-Kurs:

Android-Entwicklerdokumentation:

In diesem Abschnitt werden mögliche Hausaufgaben für Schüler und Studenten aufgeführt, die dieses Codelab im Rahmen eines von einem Kursleiter geleiteten Kurses durcharbeiten. Es liegt in der Verantwortung des Kursleiters, Folgendes zu tun:

  • Weisen Sie bei Bedarf Aufgaben zu.
  • Teilen Sie den Schülern/Studenten mit, wie sie Hausaufgaben abgeben können.
  • Benoten Sie die Hausaufgaben.

Lehrkräfte können diese Vorschläge nach Belieben nutzen und auch andere Hausaufgaben zuweisen, die sie für angemessen halten.

Wenn Sie dieses Codelab selbst durcharbeiten, können Sie mit diesen Hausaufgaben Ihr Wissen testen.

Beantworten Sie diese Fragen

Frage 1

Wie werden Elemente in RecyclerView angezeigt? Wähle alle zutreffenden Antworten aus.

▢ Elemente in einer Liste oder einem Raster anzeigen

▢ Vertikales oder horizontales Scrollen.

▢ Auf größeren Geräten wie Tablets wird diagonal gescrollt.

▢ Ermöglicht benutzerdefinierte Layouts, wenn eine Liste oder ein Raster für den Anwendungsfall nicht ausreicht.

Frage 2

Welche Vorteile bietet die Verwendung von RecyclerView? Wähle alle zutreffenden Antworten aus.

▢ Große Listen werden effizient angezeigt.

▢ Die Daten werden automatisch aktualisiert.

▢ Die Notwendigkeit von Aktualisierungen wird minimiert, wenn ein Element aktualisiert, gelöscht oder der Liste hinzugefügt wird.

▢ Die Ansicht, die aus dem Bildschirm gescrollt wird, wird wiederverwendet, um das nächste Element anzuzeigen, das auf den Bildschirm gescrollt wird.

Frage 3

Was sind einige der Gründe für die Verwendung von Adaptern? Wähle alle zutreffenden Antworten aus.

▢ Durch die Trennung von Belangen lässt sich Code einfacher ändern und testen.

▢ RecyclerView ist unabhängig von den angezeigten Daten.

▢ Die Datenverarbeitungsebenen müssen sich nicht darum kümmern, wie Daten angezeigt werden.

▢ Die App wird schneller ausgeführt.

Frage 4

Welche der folgenden Aussagen über ViewHolder sind richtig? Wähle alle zutreffenden Antworten aus.

▢ Das ViewHolder-Layout wird in XML-Layoutdateien definiert.

▢ Es gibt ein ViewHolder für jede Dateneinheit im Dataset.

▢ Sie können mehrere ViewHolder in einem RecyclerView haben.

▢ Die Adapter bindet Daten an die ViewHolder.

Fahren Sie mit der nächsten Lektion fort: 7.2: DiffUtil und Datenbindung mit RecyclerView