Android Kotlin Fundamentals 07.1: RecyclerView-Grundlagen

Dieses Codelab ist Teil des Android Kotlin Fundamentals-Kurss. Sie profitieren von diesem Kurs, wenn Sie nacheinander die Codelabs durcharbeiten. Alle Kurs-Codelabs finden Sie auf der Landingpage für Kotlin-Grundlagen für Android-Entwickler.

Einführung

In diesem Codelab lernen Sie, wie Sie mit RecyclerView eine Liste von Elementen aufrufen. Bauen Sie auf der Schlaf-Tracker-App aus der vorherigen Serie von Codelabs auf und lernen Sie eine bessere und vielseitigere Art der Datendarstellung mithilfe einer RecyclerView mit einer empfohlenen Architektur kennen.

Was Sie bereits wissen sollten

Sie sollten mit Folgendem vertraut sein:

  • Erstellen einer einfachen Benutzeroberfläche mit einer Aktivität, Fragmenten und Ansichten
  • Die Navigation zwischen den Fragmenten und die Verwendung von safeArgs zum Übergeben von Daten zwischen Fragmenten.
  • Mithilfe von Modellen, Modellfabriken, Transformationen und LiveData sowie deren Betrachter ansehen
  • Erstellen einer Room-Datenbank, Erstellen eines DAO und Definieren von Entitäten
  • Koroutinen für Datenbankaufgaben und andere lang andauernde Aufgaben verwenden.

Lerninhalte

  • RecyclerView mit Adapter und ViewHolder verwenden, um eine Liste von Elementen aufzurufen.

Aufgaben

  • Ändere die TrackMySleep Quality-App aus der vorherigen Lektion, um eine RecyclerView zu nutzen, um Schlafqualitätsdaten anzuzeigen.

In diesem Codelab erstellst du den RecyclerView-Teil einer App, die deine Schlafqualität verfolgt. Die App verwendet eine Room-Datenbank, um Schlafdaten im Zeitverlauf zu speichern.

Die Starter-App für den Schlaftracker hat zwei Bildschirme, die als Fragmente dargestellt sind (siehe Abbildung unten).

Auf dem ersten Bildschirm auf der linken Seite finden Sie Schaltflächen zum Starten und Beenden der Aufzeichnung. Auf diesem Bildschirm werden auch alle Schlafdaten des Nutzers angezeigt. Mit der Schaltfläche Löschen werden alle Daten gelöscht, die die App für den Nutzer erfasst hat. Auf dem zweiten Bildschirm rechts wird die Qualität des Schlafs angezeigt.

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

Die auf dem ersten Bildschirm angezeigte Liste der Schlafabende ist funktionsfähig, aber nicht sehr gut. In der App werden komplexe Textformatierungen verwendet, um Textstrings für die Textansicht und Zahlen für die Qualität zu erstellen. Außerdem lässt sich dieses Design nicht skalieren. Nachdem Sie all diese Probleme in diesem Codelab behoben haben, hat die endgültige App dieselben Funktionen und der Hauptbildschirm sieht so aus:

Das Anzeigen einer Liste oder ein Raster von Daten ist eine der häufigsten Aufgaben auf der Android-Benutzeroberfläche. Die Listen variieren von einfach bis sehr komplex. Eine Liste mit Textaufrufen kann einfache Daten enthalten, wie etwa eine Einkaufsliste. In einer komplexen Liste, zum Beispiel mit einer kommentierten Liste von Reisezielen, werden dem Nutzer möglicherweise viele Details in einem scrollbaren Raster mit Headern angezeigt.

Für alle diese Anwendungsfälle bietet Android das Widget RecyclerView.

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

  • Standardmäßig kann RecyclerView nur Elemente verarbeiten oder zeichnen, die auf dem Bildschirm sichtbar sind. Beispiel: Wenn Ihre Liste 1.000 Elemente enthält, aber nur 10 Elemente sichtbar sind, reicht RecyclerView aus, 10 Elemente auf dem Bildschirm zu zeichnen. Wenn der Nutzer scrollt, entscheidet RecyclerView, welche Elemente auf dem Bildschirm zu sehen sein sollen, und reicht gerade aus, diese Elemente anzuzeigen.
  • Wenn ein Element vom Bildschirm scrollt, werden die Aufrufe des Elements recycelt. Das heißt, das Element wird mit neuem Content gefüllt, der auf dem Bildschirm scrollt. Mit diesem Verhalten von RecyclerView lässt sich viel Zeit sparen. Außerdem wird die Fließbewegung eingeschränkt.
  • Wenn ein Element geändert wird, zieht die RecyclerView dieses Element dann nicht mehr neu, sondern aktualisiert es vollständig. Das ist ein enormer Effizienzgewinn, wenn Sie Listen mit komplexen Elementen anzeigen!

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

Das Adaptermuster

Wenn Sie in andere Länder reisen, die unterschiedliche Steckdosen verwenden, wissen Sie wahrscheinlich, wie Sie Ihre Geräte mithilfe eines Adapters an eine Steckdose anschließen können. Mit dem Adapter können Sie ganz einfach eine Steckdose in eine andere umwandeln.

Das adapter-Muster in Software Engineering hilft einem Objekt bei der Arbeit mit einer anderen API. RecyclerView verwendet einen Adapter, um App-Daten in etwas umzuwandeln, das RecyclerView anzeigen kann, ohne dass die Daten in der App gespeichert und verarbeitet werden. Für die Schlaf-Tracker-App erstellst du einen Adapter, der Daten aus der Room-Datenbank in etwas anpasst, das RecyclerView anzeigen kann, ohne den ViewModel zu ändern.

RecyclerView implementieren

Um Ihre Daten in einer RecyclerView anzuzeigen, benötigen Sie die folgenden Teile:

  • Anzuzeigende Daten.
  • Eine in Ihrer Layoutdatei definierte RecyclerView-Instanz, die als Container für die Ansichten verwendet wird.
  • Ein Layout für ein Element mit Daten.
    Wenn alle Listenelemente gleich aussehen, können Sie für alle Elemente dasselbe Layout verwenden. Dies ist jedoch nicht zwingend erforderlich. Das Elementlayout muss getrennt vom Layout des Fragments erstellt werden, damit eine Elementansicht der einzelnen erstellt und mit Daten gefüllt werden kann.
  • Ein Layoutmanager.
    Der Layoutmanager verwaltet die Organisation (das Layout) der UI-Komponenten in einer Datenansicht.
  • Ein Aufrufer.
    Der Inhaber der Ansicht erweitert die Klasse ViewHolder. Die Datei enthält die Ansichtsinformationen für einen Element aus dem Layout des Elements. Ansichtsinhaber fügen auch Informationen hinzu, die RecyclerView zum effizienten Verschieben von Ansichten auf dem Bildschirm verwendet.
  • Ein Adapter.
    Der Adapter verbindet Ihre Daten mit dem RecyclerView. Die Daten werden so angepasst, dass sie in einem ViewHolder angezeigt werden können. RecyclerView nutzt den Adapter, um herauszufinden, wie die Daten auf dem Bildschirm dargestellt werden sollen.

In dieser Aufgabe fügst du deiner Layoutdatei ein RecyclerView hinzu und richte ein Adapter ein, um Schlafdaten für RecyclerView verfügbar zu machen.

Schritt 1: RecyclerView mit LayoutManager hinzufügen

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

  1. Laden Sie die App RecyclerViewFundamentals-Starter von GitHub herunter.
  2. Erstellen Sie die App und führen Sie sie aus. Die Daten werden in einem einfachen Text angezeigt.
  3. Öffne die Android-fragment_sleep_tracker.xmlLayoutdatei auf dem Tab Design.
  4. Löschen Sie im Bereich Baumbaum den ScrollView. Durch diese Aktion werden auch die TextView gelöscht, die sich innerhalb des ScrollView befinden.
  5. Scrollen Sie im Bereich Palette durch die Liste der Komponententypen auf der linken Seite, um nach Containern zu suchen und sie auszuwählen.
  6. Ziehen Sie eine RecyclerView aus dem Bereich Palette in die Baumstruktur. Platziere RecyclerView in der ConstraintLayout.

  1. Wenn Sie ein Dialogfeld öffnen, 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ügen kann. Das kann einige Sekunden dauern. Dann wird Ihre App synchronisiert.

  1. Öffnen Sie die Modul-build.gradle-Datei, scrollen Sie zum Ende und notieren Sie sich die neue Abhängigkeit, die dem folgenden Code ähnelt:
implementation 'androidx.recyclerview:recyclerview:1.0.0'
  1. Zurück zu fragment_sleep_tracker.xml.
  2. Suchen Sie auf dem Tab Text nach dem folgenden RecyclerView-Code:
<androidx.recyclerview.widget.RecyclerView
   android:layout_width="match_parent"
   android:layout_height="match_parent" />
  1. Gib der RecyclerView eine id von sleep_list.
android:id="@+id/sleep_list"
  1. Positioniere RecyclerView so, dass der verbleibende Teil des Bildschirms in ConstraintLayout enthalten ist. Sie können dazu den oberen Bereich von RecyclerView auf die Schaltfläche Starten, den unteren Rand auf die Schaltfläche Löschen beschränken und jede Seite zum übergeordneten Element hinzufügen. Legen Sie im Layouteditor oder in XML die Layoutbreite und -höhe auf 0 dp fest. Verwenden Sie dazu den folgenden Code:
    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 den Layout-Manager der RecyclerView-XML-Datei hinzu. Jeder RecyclerView benötigt einen Layoutmanager, der angibt, wie Elemente in der Liste positioniert werden sollen. Android stellt eine LinearLayoutManager zur Verfügung, die standardmäßig die Elemente in einer vertikalen Liste von Zeilen mit voller Breite darstellt.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
  1. Wechseln Sie zum Tab Design. Die zusätzlichen Einschränkungen haben dazu geführt, dass RecyclerView zu sehen ist, um den verfügbaren Platz zu füllen.

Schritt 2: Layout und Listenhalter erstellen

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 du so schnell wie möglich zu einer funktionierenden RecyclerView gelangt, verwendest du zuerst ein einfaches Listenelement, das nur die Schlafqualität als Zahl anzeigt. Dafür benötigen Sie den Datenansichtsinhaber TextItemViewHolder. Sie benötigen außerdem eine Ansicht, eine TextView für die Daten. In einem späteren Schritt erfahren Sie mehr über Aufrufer und wie Sie alle Schlafdaten herausstellen.

  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. In text_item_view.xml wird der gesamte Code gelöscht.
  3. Fügen Sie einen TextView mit 16dp-Text am Anfang und Ende und eine Textgröße von 24sp hinzu. Breite und Breite des Inhalts müssen von der Höhe umschlossen werden. Da diese Ansicht innerhalb von RecyclerView angezeigt wird, musst du sie in einem ViewGroup platzieren.
<?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 nach unten und fügen Sie die Definition unten hinzu, mit der die TextItemViewHolder-Klasse erstellt wird. Fügen Sie den Code am Ende der Datei nach der letzten schließenden geschweiften Klammer ein. Der Code wird in Util.kt eingefügt, weil dieser Inhaber der Ansicht vorübergehend ist und später ersetzt wird.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
  1. Importieren Sie android.widget.TextView und androidx.recyclerview.widget.RecyclerView, wenn Sie dazu aufgefordert werden.

Schritt 3: SleepNightAdapter erstellen

Die Hauptaufgabe beim Implementieren eines RecyclerView ist das Erstellen des Adapters. Sie haben einen einfachen Ansichtshalter für die Artikelansicht und ein Layout für jeden Artikel. Sie können jetzt einen Adapter erstellen. Der Adapter erstellt einen Aufrufhalter und füllt ihn mit Daten, die in RecyclerView angezeigt werden können.

  1. Erstellen Sie im Paket sleeptracker eine neue Kotlin-Klasse mit dem Namen SleepNightAdapter.
  2. Richten Sie die SleepNightAdapter-Klasse um RecyclerView.Adapter aus. Die Klasse heißt SleepNightAdapter, da sie ein SleepNight-Objekt in etwas anpasst, das RecyclerView verwenden kann. Der Adapter muss wissen, welcher Ansichtshalter verwendet werden soll. Übergeben Sie daher TextItemViewHolder. Importieren Sie die erforderlichen Komponenten, wenn Sie dazu aufgefordert werden. Im Anschluss wird eine Fehlermeldung angezeigt, weil die Implementierungsmethoden obligatorisch sind.
class SleepNightAdapter: RecyclerView.Adapter<TextItemViewHolder>() {}
  1. Erstellen Sie auf der obersten Ebene von SleepNightAdapter eine listOf SleepNight-Variable, in der die Daten gespeichert werden.
var data =  listOf<SleepNight>()
  1. Überschreibe in SleepNightAdapter den Wert „getItemCount()“ für die Größe der Liste der Schlafnächte in data. Der RecyclerView muss wissen, wie viele Elemente der Adapter anzeigen kann, indem er getItemCount() aufruft.
override fun getItemCount() = data.size
  1. Überschreibe in „SleepNightAdapter“ die Funktion „onBindViewHolder()“, wie unten gezeigt.

    Die Funktion onBindViewHolder() wird von RecyclerView aufgerufen, um die Daten eines Listenelements an der angegebenen Position anzuzeigen. Die Methode onBindViewHolder() verwendet daher zwei Argumente: einen Ansichtsinhaber und eine Position der zu bindenden Daten. Für diese App ist der Inhaber der TextItemViewHolder und die Position die Position in der Liste.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}
  1. Erstellen Sie innerhalb von onBindViewHolder() eine Variable für ein Element an einer bestimmten Position in den Daten.
 val item = data[position]
  1. Der von dir erstellte ViewHolder hat eine Property namens „textView“. Lege in onBindViewHolder() den text von textView auf die Schlafqualität fest. In diesem Code wird nur eine Liste mit Zahlen angezeigt. In diesem einfachen Beispiel können Sie jedoch sehen, wie der Adapter die Daten in den Eigentümer der Ansicht und auf den Bildschirm übernimmt.
holder.textView.text = item.sleepQuality.toString()
  1. Überschreiben und implementieren Sie in SleepNightAdapter onCreateViewHolder(), das aufgerufen wird, wenn RecyclerView einen Ansichtshalter zur Darstellung eines Elements benötigt.

    Diese Funktion verwendet zwei Parameter und gibt ein ViewHolder zurück. Der Parameter parent, also die Datenansichtsgruppe, die den Inhaber der Ansicht enthält, ist immer RecyclerView. Der Parameter viewType wird verwendet, wenn mehrere Ansichten in derselben RecyclerView vorhanden sind. Wenn du beispielsweise eine Liste mit Textaufrufen, einem Bild und einem Video in der gleichen RecyclerView hinzufügst, muss die Funktion onCreateViewHolder() wissen, welche Art von Ansicht sie verwenden möchten.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}
  1. Erstellen Sie in onCreateViewHolder() eine Instanz von LayoutInflater.

    Der Layout-Inflation kann Aufrufe aus XML-Layouts erstellen. Das context enthält Informationen zum korrekten Erhöhen der Ansicht. In einem Adapter für eine Recycler-Ansicht übergeben Sie immer den Kontext der parent-Datenansichtsgruppe, also der RecyclerView.
val layoutInflater = LayoutInflater.from(parent.context)
  1. Erstellen Sie in onCreateViewHolder() das view, indem Sie den layoutinflater bitten, den Wert zu erhöhen.

    Übergeben Sie das XML-Layout für die Ansicht und die Ansichtsgruppe parent für die Ansicht. Das dritte, boolesche Argument ist attachToRoot. Dieses Argument muss auf false gesetzt sein, da RecyclerView dieses Element der Ansichtshierarchie für Sie hinzufügt, wenn es Zeit ist.
val view = layoutInflater
       .inflate(R.layout.text_item_view, parent, false) as TextView
  1. Gibt in onCreateViewHolder() ein TextItemViewHolder mit view zurück.
return TextItemViewHolder(view)
  1. Der Adapter muss RecyclerView darüber informieren, wenn data geändert wurde, weil RecyclerView nichts über die Daten weiß. Es werden nur die Inhaber der Datenansicht berücksichtigt, die der Adapter an sie weitergibt.

    Um RecyclerView mitzuteilen, dass sich die angezeigten Daten geändert haben, fügen Sie oben in der Klasse SleepNightAdapter einen benutzerdefinierten Setter zur data-Variable hinzu. Geben Sie in der Setter data einen neuen Wert ein und rufen Sie dann notifyDataSetChanged() auf, um die Neuzeichnung der Liste mit den neuen Daten auszulösen.
var data =  listOf<SleepNight>()
   set(value) {
       field = value
       notifyDataSetChanged()
   }

Schritt 4: RecyclerView über den Adapter informieren

Das Gerät „RecyclerView“ benötigt Informationen zum Adapter, mit dem sich Halter für die Ansicht anzeigen lassen.

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

    Wenn immer noch Fehler in der Nähe von binding.sleepList oder binding.FragmentSleepTrackerBinding angezeigt werden, entwerten Sie die Caches und starten Sie das Gerät neu. Wählen Sie Datei > Caches wiederherstellen / Neu starten aus.

    Wenn Sie die App jetzt ausführen, sind keine Fehler aufgetreten, es werden jedoch keine Daten angezeigt, wenn Sie auf Starten und dann auf Beenden tippen.

Schritt 5: Daten in den Adapter laden

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

  1. Öffnen Sie SleepTrackerViewModel.
  2. Suchen Sie die Variable nights, in der alle Schlafnächte gespeichert werden. 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 Beobachter erstellen, der Zugriff auf diese Variable benötigt. Die Erklärung sollte so aussehen:
val nights = database.getAllNights()
  1. Öffnen Sie im Paket database das 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 LiveData enthält, die von Room aktualisiert wird, und du kannst dir nights ansehen, wenn es geändert wird.
  3. Öffnen Sie SleepTrackerFragment.
  4. Erstellen Sie in onCreateView() unter der Erstellung von adapter einen Beobachter für die nights-Variable.

    Wenn du das Fragment viewLifecycleOwner als Lebenszyklusinhaber angibst, kann dieser Beobachter nur aktiv sein, wenn sich der RecyclerView auf dem Bildschirm befindet.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   })
  1. Wenn Sie innerhalb des Observers einen Wert ermitteln, der nicht null ist (bei nights), weisen Sie den Wert dem Adapter data zu. Dies ist der abgeschlossene Code für den Observer und das Festlegen der Daten:
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   it?.let {
       adapter.data = it
   }
})
  1. Erstellen Sie Ihren Code und führen Sie ihn aus.

    Wenn dein Adapter funktioniert, werden dir die Zahlen für die Schlafqualität als Liste angezeigt. Im Screenshot links wird „-1“ angezeigt, nachdem Sie auf Starten getippt haben. Der Screenshot rechts zeigt die aktualisierte Zahl für die Schlafqualität, nachdem Sie auf Stopp getippt und eine Qualitätsbewertung ausgewählt haben.

Schritt 6: Verwendung von recycelten View-Inhabern

RecyclerView recycelt Aufrufhalter, d. h., sie werden wiederverwendet. Wenn eine Ansicht vom Bildschirm scrollt, wird sie von RecyclerView wiederverwendet, um durch die Ansicht zu scrollen.

Da diese Ansichtshalter recycelt werden, muss onBindViewHolder() alle Anpassungen, die vorherige Elemente einer Ansichtshalter vornehmen konnten, festlegen oder zurücksetzen.

So könnten Sie zum Beispiel die Textfarbe in Sichtbehältern auf Rot legen, wenn die Qualitätsfreigaben kleiner oder gleich 1 sind und schlechten Schlaf darstellen.

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

    Während RecyclerView die Aufrufer wiederverwendet, wird letztendlich einer der roten Halter für eine hohe Bewertung wiederverwendet. Die hohe Bewertung wird fälschlicherweise rot angezeigt.

  1. Fügen Sie eine else-Anweisung hinzu, um die Farbe auf „Schwarz“ zu setzen, wenn die Qualität mindestens so hoch ist wie der Wert in ein Schwarz.

    Wenn beide Bedingungen explizit sind, verwendet der Ansichtshalter die richtige Textfarbe für jedes Element.
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
} else {
   // reset
   holder.textView.setTextColor(Color.BLACK) // black
}
  1. Wenn du die App ausführst, sollten die Zahlen immer die richtige Farbe haben.

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

Für diese Aufgabe ersetzen Sie den einfachen Ansichtshalter durch einen, der mehr Daten für eine Nacht anzeigen kann.

Die einfache ViewHolder, die du Util.kt hinzugefügt hast, bricht einen TextView in einem TextItemViewHolder ein.

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

Warum verwendet RecyclerView nicht einfach direkt ein TextView-Objekt? Diese eine Zeile bietet zahlreiche Funktionen. Ein ViewHolder beschreibt eine Elementansicht und zugehörige Metadaten innerhalb des RecyclerView. RecyclerView stützt sich auf diese Funktion, um die Ansicht beim Scrollen der Liste korrekt zu platzieren, und um interessante Dinge zu erledigen, wie z. B. Animieren von Ansichten, wenn Elemente hinzugefügt oder entfernt werden Adapter.

Wenn RecyclerView auf die in ViewHolder gespeicherten Ansichten zugreifen muss, kann dies die Property itemView des Ansichtsinhabers sein. RecyclerView verwendet itemView, wenn ein Element zur Anzeige auf dem Bildschirm verknüpft, als Teil eines Rahmens wie ein Rahmen gezeichnet wird und die Barrierefreiheit implementiert wird.

Schritt 1: Elementlayout 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 und nennen Sie sie list_item_sleep_night.
  2. Ersetzen Sie den gesamten Code in der Datei durch den folgenden Code. Sehen Sie sich das soeben erstellte Layout an.
<?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 das Layout wie im Screenshot unten links aus. In der Entwurfsansicht sehen Sie den Screenshot rechts.

Schritt 2: ViewHolder erstellen

  1. Öffnen Sie SleepNightAdapter.kt.
  2. Erstellen Sie innerhalb der SleepNightAdapter eine Klasse namens ViewHolder und erweitern Sie sie RecyclerView.ViewHolder.
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}
  1. In ViewHolder erhältst du Verweise auf die Ansichten. Du benötigst einen Verweis auf die Ansichten, die von dieser ViewHolder aktualisiert werden. Jedes Mal, wenn Sie diesen ViewHolder binden, müssen Sie auf das Bild und beide Textansichten zugreifen. (Sie wandeln diesen Code um, um die Datenbindung später zu verwenden.)
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 soeben erstellte SleepNightAdapter.ViewHolder.
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {

Aktualisieren Sie onCreateViewHolder():

  1. Ändern Sie die Signatur von onCreateViewHolder(), um ViewHolder zurückzugeben.
  2. Ändere den Layout-Inflator, um die richtige Layout-Ressource zu verwenden, list_item_sleep_night.
  3. Entferne die Übertragung zu TextView.
  4. Statt ein TextItemViewHolder-Element zurückzugeben, wird ein ViewHolder-Element zurückgegeben.

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

Aktualisieren Sie onBindViewHolder():

  1. Ändern Sie die Signatur von onBindViewHolder() so, dass der Parameter holder ein ViewHolder statt TextItemViewHolder ist.
  2. Löschen Sie innerhalb der Datei onBindViewHolder() den gesamten Code, mit Ausnahme der Definition von item.
  3. Definieren Sie einen val-res, der einen Verweis auf den resources für diese Ansicht enthält.
val res = holder.itemView.context.resources
  1. Text für die Textansicht „sleepLength“ auf die Dauer festlegen. Kopieren Sie den Code unten. Dadurch wird eine Formatierungsfunktion aufgerufen, die mit dem Startcode bereitgestellt wird.
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
  1. Dabei wird ein Fehler ausgegeben, weil convertDurationToFormatted() definiert werden muss. Öffnen Sie Util.kt und entfernen Sie den Kommentar für den Code und die zugehörigen Importe. Wählen Sie Code > Kommentar mit Zeilenkommentaren aus.
  2. Gehe zurück zu onBindViewHolder() und verwende convertNumericQualityToString(), um die Qualität festzulegen.
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. Im Startcode finden Sie das neue ic_sleep_active-Symbol.
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. Dies ist die fertige aktualisierte onBindViewHolder()-Funktion, mit 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ühre deine App aus. Dein Display sollte wie der Screenshot unten aussehen, auf dem das Symbol für die Schlafqualität zusammen mit dem Text für die Schlafdauer und die Schlafqualität angezeigt wird.

Ihr RecyclerView ist jetzt vollständig. Sie haben nun eine Adapter und eine ViewHolder implementiert und sie zusammengefügt, um eine Liste mit einer RecyclerView Adapter aufzurufen.

Der bisherige Code für die Erstellung eines Adapters und eines Aufrufhalters wird in Ihrem Code angezeigt. Sie können diesen Code jedoch verbessern. Der anzuzeigende Code und der Code zum Verwalten von Ansichtsinhabern sind fehlerhaft und onBindViewHolder() weiß, wie er die ViewHolder aktualisieren muss.

In einer Produktions-App kann es vorkommen, dass mehrere Halter für die Ansicht, komplexere Adapter und mehrere Entwickler Änderungen vornehmen. Strukturieren Sie Ihren Code so, dass sich nur ein Aufrufer in der Nähe des Aufrufers befindet.

Schritt 1: onBindViewHolder() refaktorieren

In diesem Schritt refaktorieren Sie den Code und verschieben alle Funktionen des Aufrufers in ViewHolder. Ziel dieser Refaktorierung ist nicht, das Aussehen der App für die Nutzer zu verändern, sondern Entwicklern die Arbeit am Code zu erleichtern und sicherer zu machen. Glücklicherweise gibt es in Android Studio Tools, die Ihnen dabei helfen.

  1. Wählen Sie in SleepNightAdapter unter onBindViewHolder() alles außer der Anweisung aus, um die Variable item zu deklarieren.
  2. Klicken Sie mit der rechten Maustaste und wählen Sie Refaktorieren > Extrahieren > Funktion aus.
  3. Benennen Sie die Funktion mit bind und akzeptieren Sie die vorgeschlagenen Parameter. Klicken Sie auf OK.

    Die Funktion bind() befindet sich unter 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. Setzen Sie den Cursor auf das Wort holderdes Parameters holder von bind(). Drücken Sie Alt+Enter (Option+Enter auf einem Mac), um das Intent-Menü zu öffnen. Wählen Sie Parameter in Empfänger umwandeln aus, um dies in eine Erweiterungsfunktion mit der folgenden Signatur zu konvertieren:
private fun ViewHolder.bind(item: SleepNight) {...}
  1. Fügen Sie die bind()-Funktion aus und fügen Sie sie in ViewHolder ein.
  2. bind()“ öffentlich machen.
  3. Importieren Sie bind() in den Adapter, falls erforderlich.
  4. Da es sich jetzt in der ViewHolder befindet, kannst du 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: ReCreateonHolder-Refaktorierung durchführen

Die Methode onCreateViewHolder() im Adapter erhöht die Ansicht derzeit aus der Layoutressource für den ViewHolder. Allerdings hat die Inflation nichts mit dem Adapter und alles mit dem ViewHolder zu tun. Die Steigerung sollte in der ViewHolder erfolgen.

  1. Wählen Sie in onCreateViewHolder() den gesamten Code im Text der Funktion aus.
  2. Klicken Sie mit der rechten Maustaste und wählen Sie Refaktorieren > Extrahieren > Funktion aus.
  3. Benennen Sie die Funktion mit from und akzeptieren 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 Intent-Menü zu öffnen.
  5. Wählen Sie Zu Companion-Objekt verschieben aus. Die from()-Funktion muss sich in einem Companion-Objekt befinden, damit sie in der ViewHolder-Klasse aufgerufen werden kann, nicht bei einer ViewHolder-Instanz.
  6. Verschiebe das companion-Objekt in die ViewHolder-Klasse.
  7. from()“ öffentlich machen.
  8. Ändern Sie in onCreateViewHolder() die return-Anweisung, um from() nach dem Aufrufen von from() in der Klasse ViewHolder zurückzugeben.

    Ihre abgeschlossenen Methoden onCreateViewHolder() und from() sollten wie unten dargestellt aussehen und der Code sollte ohne Fehler erstellt 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. Ändere die Signatur der Klasse ViewHolder so, dass der Konstruktor privat ist. Da from() jetzt eine Methode ist, die eine neue ViewHolder-Instanz zurückgibt, kann jeder den Konstruktor von ViewHolder nicht mehr aufrufen.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
  1. Führen Sie die Anwendung aus. Ihre Anwendung sollte wie zuvor erstellt und ausgeführt werden, was nach dem Refaktorieren das gewünschte Ergebnis ist.

Android Studio-Projekt: RecyclerViewFundamentals

  • Das Anzeigen einer Liste oder ein Raster von Daten ist eine der häufigsten Aufgaben auf der Android-Benutzeroberfläche. RecyclerView ist darauf ausgelegt, auch bei extrem großen Listen effizient zu sein.
  • RecyclerView führt nur die Schritte aus, die erforderlich sind, um Elemente zu verarbeiten oder zu zeichnen, die auf dem Bildschirm sichtbar sind.
  • Wenn ein Element vom Bildschirm scrollt, werden die Aufrufe recycelt. Das heißt, das Element wird mit neuem Content gefüllt, der auf dem Bildschirm scrollt.
  • Das Adaptive-Muster in Software Engineering hilft einem Objekt dabei, mit einer anderen API zusammenzuarbeiten. Die App-Daten werden von RecyclerView mithilfe eines Adapters in eine Anzeige umgewandelt, ohne dass sich die Art und Weise ändert, wie Daten in der App gespeichert und verarbeitet werden.

Um Ihre Daten in einer RecyclerView anzuzeigen, benötigen Sie die folgenden Teile:

  • RecyclerView
    Definieren Sie zum Erstellen einer Instanz von RecyclerView in der Layoutdatei ein <RecyclerView>-Element.
  • LayoutManager
    Ein RecyclerView verwendet ein LayoutManager, um das Layout der Elemente in der RecyclerView zu organisieren, beispielsweise in einem Raster oder in einer linearen Liste.

    Legen Sie in der <RecyclerView>-Layoutdatei das <RecyclerView>-Attribut auf den Layoutmanager fest (z. B. LinearLayoutManager oder GridLayoutManager).

    Sie können LayoutManager auch für ein RecyclerView 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, um die Daten vorzubereiten und sie in einem ViewHolder anzuzeigen. Verbinde den Adapter mit dem RecyclerView.

    Wenn RecyclerView ausgeführt wird, ermittelt der Adapter, wie die Daten auf dem Bildschirm angezeigt werden.

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

  • ViewHolder
    Ein ViewHolder enthält die Ansichtsinformationen für ein Element aus dem Element.
  • Mit der Methode onBindViewHolder() im Adapter werden die Daten an die Ansichten angepasst. Sie überschreiben diese Methode immer. Normalerweise erhöht onBindViewHolder() das Layout eines Elements und fügt die Daten in die Ansichten im Layout ein.
  • Da die RecyclerView nichts über die Daten weiß, muss die Adapter die RecyclerView informieren, wenn sich diese Daten ändern. Mit notifyDataSetChanged() kannst du Adapter benachrichtigen, dass sich die Daten geändert haben.

Udacity-Kurs:

Android-Entwicklerdokumentation:

In diesem Abschnitt werden mögliche Hausaufgaben für Schüler oder Studenten aufgeführt, die an diesem von einem Kursleiter geleiteten Codelab arbeiten. Die Lehrkraft kann Folgendes tun:

  • Bei Bedarf können Sie die entsprechenden Aufgaben zuweisen.
  • Schülern mitteilen, wie sie Aufgaben für die Aufgabe abgeben
  • Benoten Sie die Hausaufgaben.

Lehrkräfte können diese Vorschläge so oft oder so oft verwenden, wie sie möchten. anderen Aufgaben können sie nach Belieben zugewiesen werden.

Wenn Sie alleine an diesem Codelab arbeiten, können Sie Ihr Wissen mit diesen Hausaufgaben testen.

Diese Fragen beantworten

Frage 1

Wie zeigt RecyclerView Elemente an? Wählen Sie alle zutreffenden Antworten aus.

▢ Zeigt Elemente in einer Liste oder einem Raster an.

▢ Scrollt vertikal oder horizontal.

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

▢: Benutzerdefinierte Layouts werden zugelassen, wenn eine Liste oder ein Raster für den Anwendungsfall nicht ausreicht.

Frage 2

Welche Vorteile bietet die Nutzung von RecyclerView? Wählen Sie alle zutreffenden Antworten aus.

▢ Zeigt große Listen effizient an.

▢ Aktualisiert die Daten automatisch.

▢. Ein Element wird nicht aktualisiert, wenn es aktualisiert, gelöscht oder der Liste hinzugefügt wird.

▢ Dadurch wird die Ansicht wiederverwendet, bei der vom Bildschirm gescrollt wird, um das nächste Element auf dem Bildschirm zu sehen.

Frage 3

Was sind die Gründe für die Verwendung von Adaptern? Wählen Sie alle zutreffenden Antworten aus.

▢ Dank der Trennung von Bedenken ist es einfacher, Code zu ändern und zu testen.

RecyclerView ist unabhängig von den angezeigten Daten.

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

▢ Die App wird schneller ausgeführt.

Frage 4

Welche der folgenden Aussagen trifft auf ViewHolder zu? Wählen Sie alle zutreffenden Antworten aus.

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

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

▢ In einem RecyclerView können mehrere ViewHolder ausgeführt werden.

▢ Mit dem Adapter werden Daten an die ViewHolder gebunden.

Starten Sie die nächste Lektion: 7.2: DiffUtil und Datenbindung mit RecyclerView