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 einemAdapter
und einemViewHolder
, 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, wirdRecyclerView
nur so viel Arbeit leisten, dass 10 Elemente auf dem Bildschirm dargestellt werden. Wenn der Nutzer scrollt, ermitteltRecyclerView
, 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 KlasseViewHolder
. Sie enthält die Ansichtsinformationen zum Anzeigen eines Elements aus dem Layout des Elements. View-Holder fügen auch Informationen hinzu, dieRecyclerView
verwendet, um Views effizient auf dem Bildschirm zu verschieben. - Ein Adapter
: Der Adapter verbindet Ihre Daten mit demRecyclerView
. Die Daten werden so angepasst, dass sie in einemViewHolder
dargestellt werden können. EinRecyclerView
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
.
- Laden Sie die App RecyclerViewFundamentals-Starter von GitHub herunter.
- Erstellen Sie die App und führen Sie sie aus. Die Daten werden als einfacher Text angezeigt.
- Öffnen Sie die Layoutdatei
fragment_sleep_tracker.xml
in Android Studio auf dem Tab Design. - Löschen Sie im Bereich Komponentenbaum die
ScrollView
. Mit dieser Aktion wird auch dieTextView
in derScrollView
gelöscht. - Scrollen Sie im Bereich Palette in der Liste der Komponententypen auf der linken Seite zu Container und wählen Sie diese Option aus.
- Ziehen Sie ein
RecyclerView
aus dem Bereich Palette in den Bereich Komponentenbaum. Legen Sie dasRecyclerView
in dasConstraintLayout
ein.
- 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.
- Ö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'
- Zurück zu
fragment_sleep_tracker.xml
wechseln - 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" />
- Geben Sie für
RecyclerView
denid
sleep_list
an.
android:id="@+id/sleep_list"
- Positionieren Sie die
RecyclerView
so, dass sie den verbleibenden Teil des Bildschirms innerhalb derConstraintLayout
einnimmt. Dazu müssen Sie die Oberkante vonRecyclerView
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"
- Fügen Sie dem
RecyclerView
-XML einen Layoutmanager hinzu. JedeRecyclerView
benötigt einen Layoutmanager, der angibt, wie Elemente in der Liste positioniert werden. Android bietet einLinearLayoutManager
, mit dem die Elemente standardmäßig in einer vertikalen Liste mit Zeilen in voller Breite angeordnet werden.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
- 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.
- 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. - Löschen Sie in
text_item_view.xml
den gesamten angegebenen Code. - Fügen Sie ein
TextView
mit einem16dp
-Abstand am Anfang und Ende sowie einer Textgröße von24sp
hinzu. Die Breite soll dem übergeordneten Element entsprechen und die Höhe soll sich an den Inhalt anpassen. Da diese Ansicht inRecyclerView
angezeigt wird, müssen Sie sie nicht in einViewGroup
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" />
- Öffnen Sie
Util.kt
. Scrollen Sie zum Ende und fügen Sie die unten gezeigte Definition hinzu, mit der die KlasseTextItemViewHolder
erstellt wird. Fügen Sie den Code am Ende der Datei nach der letzten schließenden geschweiften Klammer ein. Der Code gehört inUtil.kt
, da dieser View-Holder temporär ist und später ersetzt wird.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
- Importieren Sie bei Aufforderung
android.widget.TextView
undandroidx.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
.
- Erstellen Sie im Paket
sleeptracker
eine neue Kotlin-Klasse mit dem NamenSleepNightAdapter
. - Lassen Sie die Klasse
SleepNightAdapter
die KlasseRecyclerView.Adapter
erweitern. Die Klasse heißtSleepNightAdapter
, weil sie einSleepNight
-Objekt in etwas umwandelt, dasRecyclerView
verwenden kann. Der Adapter muss wissen, welcher View-Holder verwendet werden soll. Übergeben Sie daherTextItemViewHolder
. 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>() {}
- Erstellen Sie auf der obersten Ebene von
SleepNightAdapter
einelistOf
-SleepNight
-Variable, um die Daten zu speichern.
var data = listOf<SleepNight>()
- Überschreibe in
SleepNightAdapter
getItemCount()
, um die Größe der Liste der Schlafnächte indata
zurückzugeben. DerRecyclerView
muss wissen, wie viele Elemente der Adapter für die Anzeige hat. Dazu wirdgetItemCount()
aufgerufen.
override fun getItemCount() = data.size
- Überschreiben Sie in
SleepNightAdapter
die FunktiononBindViewHolder()
, wie unten gezeigt.
Die FunktiononBindViewHolder()
wird vonRecyclerView
aufgerufen, um die Daten für ein Listenelement an der angegebenen Position anzuzeigen. Die MethodeonBindViewHolder()
hat also zwei Argumente: einen View-Holder und eine Position der zu bindenden Daten. In dieser App ist der PlatzhalterTextItemViewHolder
und die Position die Position in der Liste.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}
- Erstellen Sie in
onBindViewHolder()
eine Variable für ein Element an einer bestimmten Position in den Daten.
val item = data[position]
- Die von Ihnen erstellte
ViewHolder
hat ein Attribut mit dem NamentextView
. Legen Sie inonBindViewHolder()
dietext
destextView
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()
- Überschreiben und implementieren Sie in
SleepNightAdapter
onCreateViewHolder()
. Diese Methode wird aufgerufen, wenn fürRecyclerView
ein View-Holder zur Darstellung eines Elements benötigt wird.
Diese Funktion verwendet zwei Parameter und gibt einenViewHolder
zurück. Der Parameterparent
, die Ansichtsgruppe, die den Ansichtshalter enthält, ist immerRecyclerView
. Der ParameterviewType
wird verwendet, wenn es mehrere Ansichten in derselbenRecyclerView
gibt. Wenn Sie beispielsweise eine Liste mit Textansichten, ein Bild und ein Video in dasselbeRecyclerView
einfügen, muss die FunktiononCreateViewHolder()
wissen, welcher Ansichtstyp verwendet werden soll.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}
- Erstelle in
onCreateViewHolder()
eine Instanz vonLayoutInflater
.
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 derparent
-Ansichtsgruppe, alsoRecyclerView
.
val layoutInflater = LayoutInflater.from(parent.context)
- Erstellen Sie in
onCreateViewHolder()
dasview
, indem Sielayoutinflater
bitten, es aufzublasen.
Übergeben Sie das XML-Layout für die Ansicht und dieparent
-Ansichtsgruppe für die Ansicht. Das dritte (boolesche) Argument istattachToRoot
. Dieses Argument mussfalse
sein, daRecyclerView
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
- Geben Sie in
onCreateViewHolder()
einTextItemViewHolder
zurück, das mitview
erstellt wurde.
return TextItemViewHolder(view)
- Der Adapter muss
RecyclerView
darüber informieren, wenn sichdata
ändert, daRecyclerView
nichts über die Daten weiß. Es kennt nur die View-Holder, die der Adapter ihm zur Verfügung stellt.
Damit dieRecyclerView
weiß, wann sich die angezeigten Daten geändert haben, fügen Sie derdata
-Variablen oben in derSleepNightAdapter
-Klasse einen benutzerdefinierten Setter hinzu. Weisen Sie im Setterdata
einen neuen Wert zu und rufen Sie dannnotifyDataSetChanged()
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.
- Öffnen Sie
SleepTrackerFragment.kt
. - Erstellen Sie in
onCreateview()
einen Adapter. Fügen Sie diesen Code nach der Erstellung desViewModel
-Modells und vor derreturn
-Anweisung ein.
val adapter = SleepNightAdapter()
- Verknüpfen Sie
adapter
mitRecyclerView
.
binding.sleepList.adapter = adapter
- Bereinigen Sie Ihr Projekt und erstellen Sie es neu, um das
binding
-Objekt zu aktualisieren.
Wenn weiterhin Fehler im Zusammenhang mitbinding.sleepList
oderbinding.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.
- Öffnen Sie
SleepTrackerViewModel
. - Suchen Sie nach der Variablen
nights
, in der alle Schlafnächte gespeichert sind. Das sind die Daten, die angezeigt werden sollen. Die Variablenights
wird durch Aufrufen vongetAllNights()
für die Datenbank festgelegt. - Entfernen Sie
private
ausnights
, da Sie einen Observer erstellen, der auf diese Variable zugreifen muss. Ihre Erklärung sollte so aussehen:
val nights = database.getAllNights()
- Öffnen Sie im Paket
database
die DateiSleepDatabaseDao
. - Suchen Sie die Funktion
getAllNights()
. Diese Funktion gibt eine Liste vonSleepNight
-Werten alsLiveData
zurück. Das bedeutet, dass die Variablenights
den WertLiveData
enthält, der vonRoom
aktualisiert wird. Sie könnennights
beobachten, um zu sehen, wann sich der Wert ändert. - Öffnen Sie
SleepTrackerFragment
. - Erstellen Sie in
onCreateView()
unter der Erstellung vonadapter
einen Observer für die Variablenights
.
Wenn SieviewLifecycleOwner
des Fragments als Lifecycle-Inhaber angeben, ist dieser Observer nur aktiv, wennRecyclerView
auf dem Bildschirm angezeigt wird.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
})
- Weisen Sie im Observer den Wert der
nights
des Adapters derdata
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
}
})
- 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.
- Fügen Sie in der Klasse
SleepNightAdapter
am Ende vononBindViewHolder()
den folgenden Code hinzu.
if (item.sleepQuality <= 1) {
holder.textView.setTextColor(Color.RED) // red
}
- Starten Sie die App.
- Wenn Sie Daten mit niedriger Schlafqualität hinzufügen, wird die Zahl rot dargestellt.
- Füge hohe Bewertungen für die Schlafqualität hinzu, bis auf dem Display eine rote hohe Zahl angezeigt wird.
DaRecyclerView
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.
- 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
}
- 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.
- Erstellen Sie eine neue Layoutressourcendatei mit dem Namen
list_item_sleep_night
. - 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>
- 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
- Öffnen Sie
SleepNightAdapter.kt
. - Erstellen Sie in
SleepNightAdapter
eine Klasse namensViewHolder
, dieRecyclerView.ViewHolder
erweitert.
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}
- Rufen Sie in
ViewHolder
Verweise auf die Ansichten ab. Sie benötigen einen Verweis auf die Ansichten, die durch dieseViewHolder
aktualisiert werden. Jedes Mal, wenn Sie dieseViewHolder
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
- Verwenden Sie in der
SleepNightAdapter
-Definition anstelle vonTextItemViewHolder
dieSleepNightAdapter.ViewHolder
, die Sie gerade erstellt haben.
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {
Aktualisieren Sie onCreateViewHolder()
:
- Ändern Sie die Signatur von
onCreateViewHolder()
, damitViewHolder
zurückgegeben wird. - Ändern Sie den Layout-Inflater, damit die richtige Layout-Ressource
list_item_sleep_night
verwendet wird. - Entferne die Übertragung auf
TextView
. - Geben Sie anstelle eines
TextItemViewHolder
einViewHolder
zurück.
Hier ist die aktualisierteonCreateViewHolder()
-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()
:
- Ändern Sie die Signatur von
onBindViewHolder()
so, dass der Parameterholder
einViewHolder
anstelle einesTextItemViewHolder
ist. - Löschen Sie in
onBindViewHolder()
den gesamten Code mit Ausnahme der Definition vonitem
. - Definieren Sie eine
val
res
, die eine Referenz auf dieresources
für diese Ansicht enthält.
val res = holder.itemView.context.resources
- 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)
- Das führt zu einem Fehler, da
convertDurationToFormatted()
definiert werden muss. Öffnen SieUtil.kt
und entfernen Sie die Kommentarzeichen für den Code und die zugehörigen Importe. Wählen Sie Code > Comment with Line comments aus. - Zurück in
onBindViewHolder()
können Sie mitconvertNumericQualityToString()
die Qualität festlegen.
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
- Möglicherweise müssen Sie diese Funktionen manuell importieren.
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString
- 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
})
- Hier ist die aktualisierte
onBindViewHolder()
-Funktion, in der alle Daten fürViewHolder
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
})
}
- 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.
- Wählen Sie in
SleepNightAdapter
inonBindViewHolder()
alles außer der Anweisung zum Deklarieren der Variablenitem
aus. - Klicken Sie mit der rechten Maustaste und wählen Sie Refactor > Extract > Function (Umgestalten > Extrahieren > Funktion) aus.
- Nennen Sie die Funktion
bind
und übernehmen Sie die vorgeschlagenen Parameter. Klicken Sie auf OK.
Die Funktionbind()
wird unteronBindViewHolder()
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
})
}
- Bewegen Sie den Cursor auf das Wort
holder
des Parametersholder
vonbind()
. Drücken SieAlt+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) {...}
- Schneiden Sie die
bind()
-Funktion aus und fügen Sie sie inViewHolder
ein. - Machen Sie
bind()
öffentlich. - Importieren Sie
bind()
bei Bedarf in den Adapter. - Da sie sich jetzt in der
ViewHolder
befindet, können Sie denViewHolder
-Teil der Signatur entfernen. Hier ist der endgültige Code für die Funktionbind()
in der KlasseViewHolder
.
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.
- Wählen Sie in
onCreateViewHolder()
den gesamten Code im Funktionskörper aus. - Klicken Sie mit der rechten Maustaste und wählen Sie Refactor > Extract > Function (Umgestalten > Extrahieren > Funktion) aus.
- Nennen Sie die Funktion
from
und übernehmen Sie die vorgeschlagenen Parameter. Klicken Sie auf OK. - Setzen Sie den Cursor auf den Funktionsnamen
from
. Drücken SieAlt+Enter
(Option+Enter
auf einem Mac), um das Intention-Menü zu öffnen. - Wählen Sie In Begleitobjekt verschieben aus. Die
from()
-Funktion muss sich in einem Companion-Objekt befinden, damit sie für dieViewHolder
-Klasse aufgerufen werden kann, nicht für eineViewHolder
-Instanz. - Verschieben Sie das
companion
-Objekt in dieViewHolder
-Klasse. - Machen Sie
from()
öffentlich. - Ändern Sie in
onCreateViewHolder()
diereturn
-Anweisung so, dass das Ergebnis des Aufrufs vonfrom()
in der KlasseViewHolder
zurückgegeben wird.
Die fertigen MethodenonCreateViewHolder()
undfrom()
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)
}
}
- Ändern Sie die Signatur der Klasse
ViewHolder
so, dass der Konstruktor privat ist. Dafrom()
jetzt eine Methode ist, die eine neueViewHolder
-Instanz zurückgibt, gibt es keinen Grund mehr, den Konstruktor vonViewHolder
aufzurufen.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
- 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 vonRecyclerView
erstellen möchten, definieren Sie ein<RecyclerView>
-Element in der Layoutdatei. - LayoutManager
: EinRecyclerView
verwendet einenLayoutManager
, um das Layout der Elemente imRecyclerView
zu organisieren, z. B. in einem Raster oder einer linearen Liste.
Legen Sie im<RecyclerView>
in der Layoutdatei das Attributapp:layoutManager
auf den Layoutmanager fest, z. B.LinearLayoutManager
oderGridLayoutManager
.
Sie können denLayoutManager
für einRecyclerView
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 einemViewHolder
dargestellt werden. Verknüpfen Sie den Adapter mit demRecyclerView
.
WennRecyclerView
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 dasViewHolder
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
EinViewHolder
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 durchonBindViewHolder()
aufgebläht und die Daten werden in die Ansichten im Layout eingefügt. - Da
RecyclerView
nichts über die Daten weiß, mussAdapter
RecyclerView
informieren, wenn sich die Daten ändern. Verwenden SienotifyDataSetChanged()
, um dieAdapter
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: