Android RecyclerView: Was ist, lernen Sie anhand einfacher Beispiele

Was ist RecyclerView? Android?

Die RecyclerView ist ein Widget, das eine flexiblere und erweiterte Version von GridView und ListView darstellt. Es handelt sich um einen Container zur Anzeige großer Datensätze, der effizient gescrollt werden kann, indem eine begrenzte Anzahl von Ansichten beibehalten wird. Sie können das RecyclerView-Widget verwenden, wenn Sie Datensammlungen haben, deren Elemente sich zur Laufzeit abhängig von Netzwerkereignissen oder Benutzeraktionen ändern.

Aufrufe

Die Android Die Plattform verwendet die Klassen View und ViewGroup, um Elemente auf dem Bildschirm zu zeichnen. Diese Klassen sind abstrakt und werden auf verschiedene Implementierungen erweitert, um einem Anwendungsfall gerecht zu werden. TextView hat beispielsweise den einfachen Zweck, Textinhalte auf dem Bildschirm anzuzeigen. EditText basiert auf derselben View-Klasse und fügt weitere Funktionen hinzu, um dem Benutzer die Eingabe von Daten zu ermöglichen.

Es ist möglich, eigene benutzerdefinierte Ansichten zu erstellen, um mehr Flexibilität bei der Entwicklung von Benutzeroberflächen zu erreichen. Die View-Klasse bietet Methoden, die wir überschreiben können, um auf dem Bildschirm zu zeichnen, und eine Möglichkeit, Parameter wie Breite, Höhe und unsere eigenen benutzerdefinierten Attribute zu übergeben, die wir unserer Ansicht hinzufügen möchten, damit sie sich wie gewünscht verhält.

Ansichtsgruppen

Die ViewGroup-Klasse ist eine Art View, aber im Gegensatz zur einfachen View-Klasse, deren Aufgabe lediglich die Anzeige ist, gibt uns die ViewGroup die Möglichkeit, mehrere Ansichten in einer Ansicht zusammenzufassen, auf die wir als Ganzes verweisen können. In diesem Fall wird die auf der obersten Ebene erstellte Ansicht, zu der wir andere einfache Ansichten hinzufügen (wir können auch Ansichtsgruppen hinzufügen), als „übergeordnetes Element“ bezeichnet, und die darin hinzugefügten Ansichten sind „untergeordnete Elemente“.

Wir können eine Ansicht als Array und eine ViewGroup als Array von Arrays darstellen. Da ein Array von Arrays selbst ein Array ist, können wir sehen, wie eine ViewGroup wie eine Ansicht behandelt werden kann.

var arr1 = [1,2,3] //imagine a simple View as an Array
//we can imagine this as a NumberTextView which doesn't really exist
//but we could imagine there's one that makes it easy to use numbers
var arr2 = ["a","b","c"] // We can imagine this as another simple view

var nestedArr = [arr1,arr2] //in our anology, we can now group views 
//together and the structure that would hold that would be what we call the ViewGroup

Mit der ViewGroup können wir auch definieren, wie die untergeordneten Elemente innerhalb der Ansicht angeordnet sind, z. B. vertikal oder horizontal. Wir können verschiedene Regeln für die Interaktion innerhalb der Ansicht festlegen. Beispielsweise sollten aufeinanderfolgende TextViews einen Abstand von 12 dp haben, während ImageViews, denen ein TextView folgt, einen Abstand von 5 dp haben sollten.

Dies wäre der Fall, wenn wir unsere eigene ViewGroup von Grund auf entwickeln würden. Um diese Konfigurationen zu vereinfachen, Android stellt eine Klasse namens LayoutParams bereit, mit der wir diese Konfigurationen eingeben können.

Android Dokumentation stellt einige Standardparameter bereit, die wir implementieren würden, wenn wir unsere eigene ViewGroup konfigurieren. Einige allgemeine Parameter betreffen Breite, Höhe und Rand. Standardmäßig haben diese Konfigurationen die Struktur android:layout_height für die Höhe, z. B. android:layout_width. In dieser Hinsicht können Sie beim Erstellen Ihrer ViewGroup außerdem LayoutParams erstellen, die spezifisch auf das gewünschte Verhalten Ihrer ViewGroup abgestimmt sind.

Android wird mit Standardansichten und ViewGroups geliefert, mit denen wir viele der häufig benötigten Aufgaben erledigen können. Ein Beispiel, das wir erwähnt haben, ist eine TextView. Dabei handelt es sich um eine einfache Ansicht mit konfigurierbaren Aspekten wie Höhe, Breite, Textgröße und dergleichen. Wir haben eine ImageView zum Anzeigen von Bildern und EditText, wie wir neben vielen anderen bereits erwähnt haben. Android verfügt außerdem über benutzerdefinierte ViewGroups, zu denen wir unsere Ansichten hinzufügen und das erwartete Verhalten erzielen können.

LinearLayout

LinearLayout ermöglicht es uns, Ansichtselemente hinzuzufügen. LinearLayout ist ein Orientierungsattribut, das vorgibt, wie es auf dem Bildschirm angeordnet wird. Es hat auch LinearLayout.LayoutParams, die die Regeln für die darin enthaltenen Ansichten vorgeben. Beispielsweise würde das Attribut android:center_horizontal die Ansichten entlang der horizontalen Achse zentrieren, während `android:center_vertical den Inhalt der Ansicht entlang der vertikalen Achse zentrieren würde.

Hier sind einige Bilder, um die Zentrierung zu verstehen. Wir würden dies als eine einfache Textansicht innerhalb eines 200 x 200 Pixel großen Raums annehmen. Durch die Zentrierung von Attributen würde es sich wie folgt verhalten.

android:center_horizontal

Horizontal zentrierter Inhalt
Horizontal zentrierter Inhalt

android:center_vertical

vertikal zentrierter Inhalt
vertikal zentrierter Inhalt

android:center

Zentrierter Inhalt
Zentrierter Inhalt

Kernkomponenten von RecyclerView

Kernkomponenten von The RecyclerView
Kernkomponenten von RecyclerView

Im Folgenden sind die wichtigen Komponenten von RecyclerView aufgeführt:

RecyclerView.Adapter

Grundlagen Nr. 1 – Adaptermuster

Ein Adapter ist ein Gerät, das die Eigenschaften eines Systems oder Geräts in die eines sonst inkompatiblen Geräts oder Systems umwandelt. Einige von ihnen modifizieren Signal- oder Stromeigenschaften, während andere einfach die physische Form eines Steckers an einen anderen anpassen.

Ein einfaches Beispiel, das wir im wirklichen Leben finden, um einen Adapter zu erklären, ist, wenn wir Geräte miteinander verbinden müssen, diese aber Anschlussports haben, die nicht zueinander passen. Dies kann der Fall sein, wenn Sie ein anderes Land besuchen, in dem andere Arten von Steckdosen verwendet werden. Wenn Sie Ihr Telefon- oder Laptop-Ladegerät dabei haben, ist es unmöglich, es an die Steckdose anzuschließen. Sie würden jedoch nicht aufgeben, sondern sich einfach einen Adapter besorgen, der zwischen die Steckdose und Ihr Ladegerät geschaltet wird und das Aufladen ermöglicht.

Dies ist in der Programmierung der Fall, wenn wir zur Erfüllung der Aufgabe zwei Datenstrukturen miteinander verbinden möchten, ihre Standardports jedoch keine Möglichkeit haben, miteinander zu kommunizieren.

Wir verwenden das einfache Beispiel eines Geräts und eines Ladegeräts. Wir werden zwei Instanzen von Ladegeräten haben. Ein amerikanisches und ein britisches

class AmericanCharger() {
    var chargingPower = 10
}
class BritishCharger(){
    var charginPower = 5
}

Anschließend erstellen wir zwei Geräte

class AmericanDevice() 
class BritishDevice()

Als Beispiel können wir dann einige Instanzen der Geräte zum Mitspielen erstellen.

var myAmericanPhone = new AmericanDevice()
var myBritishPhone = new BritishDevice()

Anschließend stellen wir das Konzept des Ladens für beide Geräte vor, indem wir den Geräten eine Methode namens „charge()“ hinzufügen.

Die Methode nimmt das jeweilige Ladegerät als Eingabe auf und führt den Ladevorgang auf dieser Grundlage durch.

sealed trait Device
class AmericanDevice : Device{
    fun charge(charger:AmericanCharger){
        //Do some American charging
    }
}
class BritishDevice: Device{
    fun charge(charger:BritishCharger){
        //Do some British charging
    }
}

In diesem Fall müssten wir, basierend auf unserer Analogie, aus dem einen oder anderen Grund ein britisches Ladegerät verwenden, wenn wir ein amerikanisches Gerät verwenden, oder umgekehrt.

In der Programmierwelt ist dies normalerweise der Fall, wenn Bibliotheken gemischt werden, die die gleiche Funktionalität bieten (in unserem Kontext ist unsere gemeinsame Funktionalität kostenpflichtig). Wir müssten einen Weg finden, dies zu ermöglichen.

Wenn wir der Analogie folgen, müssen wir in ein Elektronikgeschäft gehen und einen Adapter kaufen, mit dem wir amerikanische Geräte mit britischen Ladegeräten aufladen können. Aus Programmiersicht sind wir diejenigen, die den Adapter herstellen.

Für das eine erstellen wir einen Adapter, der genau dem Muster entspricht, das wir für die Erstellung des anderen benötigen würden. Wir werden es wie folgt als Klasse implementieren. Es muss nicht unbedingt eine Klasse sein und könnte eine Funktion sein, die hervorhebt, was das Adaptermuster im Allgemeinen tut. Wir verwenden eine Klasse, da sie am häufigsten verwendet wird Android.

class AmericanToBritishChargerAdapter(theAmericanCharger:AmericanCharger){
    fun returnNewCharger(): BritishCharger{
            //convert the American charger to a BritishCharger
            //we would change the American charging functionality
            //to British charging functionality to make sure the
            //adapter doesn't destroy the device. The adapter could 
            //, for example, control the power output by dividing by 2
            //our adapter could encompass this functionality in here
           
           var charingPower:Int = charger.chargingPower / 2
           var newBritishCharger = new BritishCharger()
           newBritishCharger.chargingPower = theAmericanCharger.chargingPower/2
        
        return newBritishCharger
    }
}

In der Programmierwelt ist der Unterschied bei den Steckdosen analog zu dem Unterschied bei den Methoden im Inneren, die zum Laden verwendet werden. Da die Ladegeräte unterschiedliche Methoden haben, wäre es unmöglich, die Ladegeräte zu verwenden.

 var myBritishDevice = new BritishDevice()
var americanChargerIFound = new AmericanCharger()

Der Versuch, die Methode „charge()“ in myBritishDevice mit americanChargerIFound aufzurufen, würde nicht funktionieren, da AmericanDevice nur einen AmericanCharger akzeptiert

Es ist also unmöglich, dies zu tun

 var myBritishDevice = new BritishDevice()
var americanChargerIFound = new AmericanCharger()
myBritishDevice.charge(americanChargerIFound)

In diesem Szenario der von uns erstellte Adapter

AmericanToBritishChargerAdapter kann sich jetzt als nützlich erweisen. Mit der returnNewCharger()-Methode können wir einen neuen BritishCharger erstellen, mit dem wir aufladen können. Alles, was wir brauchen, ist, eine Instanz unseres Adapters zu erstellen und ihn mit dem AmericanCharger zu versorgen, den wir haben, und schon wird ein BritishCharger erstellt, den wir verwenden können

var myBritishDevice = new BritishDevice()
var americanChargerIFound = new AmericanCharger()
//We create the adapter and feed it the americanCharger
var myAdapter =  AmericanToBritishChargerAdapter(theAmericanCharger)
//calling returnNewCharger from myAdapter would return a BritishCharger
var britishChargerFromAdapter = myAdapter.returnNewCharger()
//and once we have the britishCharger we can now use it
myBritishDevice.charge(britishChargerFromAdapter)

RecyclerView.LayoutManager

Wenn es sich um eine ViewGroup handelt, werden Ansichten darin platziert. Der LayoutManager hätte die Aufgabe zu beschreiben, wie die Ansichten im Inneren angeordnet sind.

Zu Vergleichszwecken: Bei der Arbeit mit Linearlayout ViewGroup ist der gewünschte Anwendungsfall die Möglichkeit, die Elemente entweder vertikal oder horizontal zu platzieren. Dies lässt sich leicht implementieren, indem man ein Orientierungsattribut hinzufügt, das uns sagt, wie das lineare Layout auf dem Bildschirm platziert wird. Wir können dies tun, indem wir verwenden android:orientation=VERTICAL|HORIZONTAL Attribut.

Wir haben auch eine andere ViewGroup namens GridLayout. Ihr Anwendungsfall ist, wenn wir Ansichten in einer rechteckigen Rasterstruktur platzieren möchten. Dies kann beispielsweise daran liegen, dass die Daten, die wir dem App-Benutzer präsentieren, einfacher zu nutzen sind. Das GridLayout ermöglicht konstruktionsbedingt Konfigurationen, die Ihnen dabei helfen, dieses Ziel zu erreichen, indem wir Konfigurationen haben, bei denen wir die Abmessungen des Rasters definieren können, zum Beispiel können wir ein 4×4-Raster, 3 x 2-Raster haben.

RecyclerView.ViewHolder

Der ViewHolder ist eine abstrakte Klasse, die wir auch von RecyclerView erweitern. Der ViewHolder stellt uns gängige Methoden zur Verfügung, die uns helfen, auf eine Ansicht zu verweisen, die wir auf der RecyclerView platziert haben, selbst nachdem die Recycling-Maschinerie in der RecyclerView verschiedene Referenzen geändert hat, von denen wir nichts wissen.

Große Listen

RecyclerViews werden verwendet, wenn wir dem Benutzer eine wirklich große Menge an Ansichten präsentieren möchten, ohne dabei unsere Möglichkeiten zu erschöpfen RAM auf unserem Gerät für jede einzelne Instanz der erstellten Ansicht.

Wenn wir den Fall einer Kontaktliste nehmen würden, hätten wir eine allgemeine Vorstellung davon, wie ein Kontakt in der Liste aussehen würde. Was wir dann tun würden, wäre, ein Vorlagenlayout – das eigentlich eine Ansicht ist – mit Feldern zu erstellen, in die verschiedene Daten aus unserer Kontaktliste eingefüllt werden. Es folgt ein Pseudocode, der den gesamten Zweck erklärt:

//OneContactView
<OneContact>
<TextView>{{PlaceHolderForName}}</TextView>
<TextView>{{PlaceHolderForAddress}}</TextView>
<ImageView>{{PlaceHolderForProfilePicture}}</ImageView>
<TextView>{{PlaceHolderForPhoneNumber}}</TextView>
</OneContact>

Wir hätten dann eine ContactList dieser Art

 <ContactList>
</ContactList>

Wenn dies der Fall ist und wir die Inhalte fest codiert haben, hätten wir keine programmatische Möglichkeit, der Liste neue Inhalte hinzuzufügen, ohne die App neu zu schreiben. Zum Glück für uns. Das Hinzufügen einer Ansicht zu einer ViewGroup wird von einem unterstützt addView(view:View) Methode.

Selbst wenn das der Fall ist, ist es nicht die Art und Weise, wie RecyclerView untergeordnete Ansichten hinzufügt.

In unserem Anwendungsfall hätten wir eine lange Liste von Kontakten. Für jeden Kontakt in der Liste müssten wir OneContactView erstellen und die Daten in der Ansicht so füllen, dass sie mit den Feldern in unserer Kontaktklasse übereinstimmen. Sobald wir die Ansicht haben, müssen wir sie zu RecyclerView hinzufügen, um die Liste anzuzeigen.

data  class Contact(var name:String, var address:String, var pic:String, var phoneNumber:Int)

var contact1 = Contact("Guru","Guru97", "SomePic1.jpg", 991)
var contact2 = Contact("Guru","Guru98", "SomePic2.jpg", 992)
var contact3 = Contact("Guru","Guru99", "SomePic3.jpg", 993)

var myContacts:ArrayList<Contact> = arrayListOf<Contact>(contact1,contact2,contact3)

Wir haben eine Reihe von Kontakten namens OneContactView. Es enthält Slots, um Inhalte aus der Contact-Klasse zu übernehmen und anzuzeigen. In RecyclerView müssen wir Ansichten hinzufügen, damit es uns bei der Recyclingfähigkeit helfen kann.

Mit RecyclerView können wir zwar keine Ansicht hinzufügen, aber einen ViewHolder hinzufügen. In diesem Szenario haben wir also zwei Dinge, die wir verbinden möchten, aber nicht zusammenpassen. Hier kommt unser Adapter ins Spiel. RecyclerView stellt uns einen Adapter zur Verfügung, der unserem sehr ähnlich ist AmericanToBritishChargerAdapter() von früher, das es uns ermöglichte, unser amerikanisches Ladegerät, das mit unserem britischen Gerät unbrauchbar war, in etwas Brauchbares umzuwandeln, das einem Netzteil im wirklichen Leben ähnelt.

In diesem Szenario würde der Adapter unser Array von Kontakten und unsere Ansicht übernehmen und von dort aus ViewHolder generieren, die die RecyclerView bereit ist, zu akzeptieren.

RecyclerView bietet eine Schnittstelle, die wir erweitern können, um unseren Adapter über die Klasse RecyclerView.Adapter zu erstellen. In diesem Adapter gibt es eine Möglichkeit, die ViewHolder-Klasse zu erstellen, mit der RecyclerView arbeiten möchte. Wir haben also die gleiche Situation wie zuvor, aber mit einer zusätzlichen Sache, nämlich dem Adapter.

Wir haben ein Array von Kontakten, eine Ansicht zur Anzeige eines Kontakts OneContactView. Eine RecyclerView ist eine Liste von Ansichten, die Recyclingdienste bereitstellen, aber nur bereit sind, ViewHolder zu übernehmen

Aber in diesem Szenario haben wir jetzt die Klasse RecyclerView.Adapter, die über eine Methode zum Erstellen von ViewHolders verfügt.

fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder

Der RecyclerView.ViewHolder ist eine abstrakte Klasse, die unsere View als Argument nimmt und sie in einen ViewHolder konvertiert.

Es verwendet das Wrapper-Muster, das zur Erweiterung der Fähigkeiten von Klassen verwendet wird.

Grundarbeit Nr. 2 – Wrapper-Muster

Anhand eines einfachen Beispiels zeigen wir, wie wir Tiere zum Sprechen bringen können.

sealed trait Animal{
    fun sound():String
}

data class Cat(name:String):Animal{
    fun sound(){
        "Meow"
        }
}
data class Dog(name:String):Animal{
    fun sound(){
        "Woof"
        }
}

var cat1 = Cat("Tubby")
var dog1 = Dog("Scooby")
cat1.sound() //meow
dog1.sound() //woof

Im obigen Beispiel haben wir zwei Tiere. Wenn wir zufällig eine Methode zum Sprechen hinzufügen wollten, der Autor der Bibliothek aber keinen Spaß hatte, konnten wir trotzdem einen Weg finden. Was wir brauchen, wäre ein Wrapper für unsere Animal-Klasse. Wir würden dies tun, indem wir das Tier als Konstruktor für unsere Klasse übernehmen

 class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

    speak(){
        println("Hello, my name is ${myAnimal.name}")
    }
}

Jetzt können wir eine Tierinstanz an SpeechPoweredAnimalByWrapper übergeben. Der Aufruf der Methode sound() würde die übergebene Methode animal sound() aufrufen. Wir haben auch eine zusätzliche speak()-Methode, die als neue Funktionalität gilt, die wir den übergebenen Tieren hinzufügen. Wir können sie wie folgt verwenden:

var cat1 =  Cat("Garfield")
cat1.sound()//"meow"
cat1.speak()// doesn't work as it isn't implemented
var talkingCat = new SpeechPoweredAnimalByWrapper(cat1)
talkingCat.sound() //"meow" the sound method calls the one defined for cat1
talkingCat.speak() //"Hello, my name is Garfield"

Mit diesem Muster können wir Kurse belegen und Funktionen hinzufügen. Wir müssten lediglich eine Klasseninstanz und neue Methoden übergeben, die von unserer Wrapping-Klasse definiert werden.

In unserem obigen Fall haben wir eine konkrete Klasse verwendet. Es ist auch möglich, dasselbe in einer abstrakten Klasse zu implementieren. Wir müssten nur die Klasse SpeechPoweredAnimalByWrapper in eine abstrakte Klasse ändern und fertig. Wir ändern den Klassennamen in einen kürzeren, um ihn lesbarer zu machen.

abstract class SpeechPowered(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

    speak(){
        println("Hello, my name is ${myAnimal.name}")
    }
}

Es ist dasselbe wie zuvor, aber es würde etwas anderes bedeuten. In einer normalen Klasse können wir eine Instanz einer Klasse auf die gleiche Weise haben, wie wir cat1 und dog1 erstellt haben. Abstrakte Klassen sind jedoch nicht dazu gedacht, instanziiert zu werden, sondern sollen andere Klassen erweitern. Wie würden wir also die neue abstrakte Klasse SpeechPowered(var myAnimal:Animal) verwenden? Wir können es nutzen, indem wir neue Klassen erstellen, die es erweitern und dadurch seine Funktionalität erweitern.

In unserem Beispiel erstellen wir eine Klasse SpeechPoweredAnimal, die die Klasse erweitert

class SpeechPoweredAnimal(var myAnimal:Animal):SpeechPowered(myAnimal)
var cat1 =  Cat("Tubby")
var speakingKitty = SpeechPoweredAnimal(cat1)
speakingKitty.speak() //"Hello, my name is Tubby"

Dies ist das gleiche Muster, das im ViewHolder verwendet wird. Die Klasse RecyclerView.ViewHolder ist eine abstrakte Klasse, die der Ansicht Funktionalität hinzufügt, ähnlich wie wir den Tieren die Speak-Methode hinzugefügt haben. Die zusätzliche Funktionalität sorgt dafür, dass es beim Umgang mit RecyclerView funktioniert.

So würden wir aus OneContactView einen OneContactViewHolder erstellen

//The View argument we pass is converted to a ViewHolder which uses the View to give it more abilities and in turn work with the RecyclerView
class OneContactViewHolder(ourContactView: View) : RecyclerView.ViewHolder(ourContactView)

RecyclerView verfügt über einen Adapter, der es uns ermöglicht, unser Kontaktarray mit dem ContactsView mit dem RecyclerView zu verbinden

Ansicht hinzufügen

Die ViewGroup zeichnet die ViewGroup nicht automatisch neu, sondern folgt einem bestimmten Zeitplan. Auf Ihrem Gerät kann es sein, dass es alle 10 ms oder 100 ms neu zeichnet, oder wenn wir eine absurde Zahl wählen, sagen wir 1 Minute, wenn wir einer ViewGroup eine Ansicht hinzufügen, werden Sie die Änderungen 1 Minute später sehen, wenn die ViewGroup „aktualisiert“ wird.

RecyclerView.Recycler

Grundlagen Nr. 3. Caching

Eines der besten Beispiele dafür, wo wir regelmäßig Aktualisierungen durchführen, ist der Browser. Stellen wir uns zum Beispiel vor, dass die Website, die wir besuchen, statisch ist und Inhalte nicht dynamisch sendet. Wir müssten sie ständig aktualisieren, um die Änderungen zu sehen.

Stellen wir uns für dieses Beispiel vor, dass es sich bei der betreffenden Website um Twitter handelt. Wir hätten eine Reihe statischer Tweets aufgelistet und die einzige Möglichkeit, neue Tweets zu sehen, bestünde darin, auf die Schaltfläche „Aktualisieren“ zu klicken, um den Inhalt erneut abzurufen.

Das Neulackieren des gesamten Bildschirms ist natürlich eine kostspielige Sache. Wenn wir uns vorstellen könnten, dass dies der Fall wäre, hätten wir bei unserem Telefonanbieter nur eine begrenzte Bandbreite. Da unsere Tweet-Liste viele Bilder und Videos enthielt, wäre es kostspielig, den gesamten Inhalt der Seite bei jeder Aktualisierung erneut herunterzuladen.

Wir bräuchten eine Möglichkeit, die bereits geladenen Tweets zu speichern und sicherzustellen, dass unsere nächste Anfrage die Möglichkeit hat, die bereits vorhandenen Tweets anzugeben. Deshalb lädt es nicht alles erneut herunter, sondern ruft nur die neuen Tweets ab, die es hat, und überprüft auch, ob einige Tweets, die lokal gespeichert wurden, nicht mehr vorhanden sind, damit es sie lokal löschen kann. Was wir beschreiben, nennt sich Caching.

Die Informationen, die wir an die Website über die von uns gespeicherten Inhalte senden, werden als Metadaten bezeichnet. Im eigentlichen Sinne sagen wir also nicht nur „wir möchten Ihre Website laden“, sondern wir sagen „wir möchten Ihre Website laden“ und hier sind einige der Inhalte, die wir bereits beim letzten Laden gespeichert haben. Bitte verwenden Sie sie dazu Senden Sie uns nur das, was nicht drin ist, sodass wir nicht viel Bandbreite verbrauchen, da wir nicht über viele Ressourcen verfügen.“

Layout-Aufrufe – Die Tweet-Liste muss verrückt sein

Ein Beispiel für einen Layoutaufruf ist scrollToPosition

Dies ist ein häufiges Beispiel, das beispielsweise bei Chat-Apps auftritt. Wenn jemand in einem Chat-Thread auf eine Chat-Blase von früher antwortet, enthalten einige Chat-Apps die Antwort und einen Link zur Chat-Blase, der Sie beim Klicken dorthin führt, wo Ihre ursprüngliche Nachricht war.

In diesem Fall rufen wir diese Methode auf, bevor wir einen LayoutManager zu unserem RecyclerView hinzugefügt haben und bevor wir einen RecyclerView.Adapter haben, wird scrollToPosition(n:Int) einfach ignoriert.

Kommunikation zwischen RecyclerView-Komponenten

Grundlagen Nr. 4. Rückrufe

Der RecyclerView verfügt bei seiner Arbeit über viele bewegliche Teile. Es muss sich um den LayoutManager kümmern, der uns sagt, wie die Ansichten entweder linear oder in einem Raster organisiert werden sollen. Es muss sich um einen Adapter handeln, der die Aufgabe übernimmt, unsere Elemente contactList in Views OneContactView und dann in ViewHolders OneContactViewHolder zu konvertieren, sodass RecyclerView bereit ist, innerhalb der von ihm bereitgestellten Methoden zu arbeiten.

Das Rohmaterial für die RecyclerView sind unsere Ansichten, z. B. OneContactView und die Datenquelle.

contactList:Array<Contact>

Wir haben ein einfaches Szenario als Ausgangspunkt verwendet, um ein Gefühl dafür zu bekommen, was RecyclerView erreichen möchte.

Ein Basisfall, bei dem wir ein statisches Array von 1000 Kontakten hätten, das wir dem Benutzer zeigen möchten, ist leicht zu verstehen.

Erst wenn die Liste nicht mehr statisch ist, fängt die RecyclerView-Maschinerie richtig an zu arbeiten.

Bei einer dynamischen Liste müssen wir darüber nachdenken, was mit der Ansicht auf dem Bildschirm passiert, wenn wir ein Element zur Liste hinzufügen oder ein Element aus der Liste entfernen.

RecyclerView.LayoutManager

Abgesehen davon, dass wir entscheiden, ob unsere Ansichten linear oder in einem Raster angeordnet werden sollen, leistet der LayoutManager im Hintergrund viel Arbeit, indem er dem Recycler hilft, zu erkennen, wann das Recycling durchgeführt werden muss.

Es ist dafür verantwortlich, die derzeit auf dem Bildschirm sichtbaren Ansichten zu verfolgen und diese Informationen an den Recycling-Mechanismus weiterzuleiten. Wenn ein Benutzer nach unten scrollt, ist der Layout-Manager dafür verantwortlich, das Recycling-System über die Ansichten zu informieren, die oben unscharf werden, damit sie wiederverwendet werden können, anstatt dort zu bleiben und Speicher zu verbrauchen oder anstatt sie zu zerstören und erstellen zu müssen neue.

Das bedeutet, dass der LayoutManager verfolgen muss, wo sich der Benutzer befindet, während er durch unsere Liste scrollt. Dies geschieht durch eine Liste von Positionen, die auf einem Index basieren, d. h. das erste Element beginnt bei 0 und wird erhöht, um der Anzahl der Elemente in unserer Liste zu entsprechen.

Wenn wir 10 Elemente auf unserer Liste von sagen wir 100 anzeigen können, erkennt der LayoutManager zu Beginn, dass er Ansicht-0 bis Ansicht-9 im Fokus hat. Während wir scrollen, kann der LayoutManager die angezeigten Ansichten berechnen des Fokus.

Der LayoutManager kann diese Ansichten für den Recycling-Mechanismus freigeben, sodass sie wiederverwendet werden können (neue Daten können an sie gebunden werden, z. B. können die Kontaktdaten einer Ansicht entfernt werden und neue Kontaktdaten aus dem nächsten Segment können die Platzhalter ersetzen).

Dies wäre ein guter Fall, wenn die Liste, die wir haben, statisch ist, aber einer der häufigsten Anwendungsfälle für die Verwendung von RecyclerView sind dynamische Listen, bei denen Daten von einem Online-Endpunkt oder vielleicht sogar von einem Sensor stammen können. Es werden nicht nur Daten hinzugefügt, sondern manchmal auch Daten aus unserer Liste entfernt oder aktualisiert.

Der dynamische Zustand unserer Daten kann es sehr schwierig machen, über den LayoutManager nachzudenken. Aus diesem Grund verwaltet der LayoutManager eine eigene Liste der Elemente und Positionen, die von der Liste, die die Recycling-Komponente verwendet, getrennt ist. Dadurch wird sichergestellt, dass die Layoutaufgabe korrekt ausgeführt wird.

Gleichzeitig möchte der LayoutManager von RecyclerView die vorhandenen Daten nicht falsch darstellen. Um ordnungsgemäß zu funktionieren, synchronisiert sich der LayoutManager in bestimmten Intervallen (60 ms) mit dem RecyclerView.Adapter und tauscht Informationen über unsere Listenelemente aus (d. h. hinzugefügte, aktualisierte, entfernte oder von einer Position an eine andere verschobene Elemente). Wenn der LayoutManager diese Informationen erhält, organisiert er den Inhalt auf dem Bildschirm neu, um ihn bei Bedarf an die Änderungen anzupassen.

Viele der Kernvorgänge, die mit RecylerView zu tun haben, drehen sich um die Kommunikation zwischen RecyclerView.LayoutManager und RecyclerView.Adapter, der unsere Listen mit manchmal statischen und manchmal dynamischen Daten speichert.

Darüber hinaus präsentiert uns RecyclerView Methoden, mit denen wir Ereignisse wie onBindViewHolder abhören können, wenn unser RecyclerView.Adapter Inhalte aus unserer Liste, z. B. einen Kontakt, an einen ViewHolder bindet, sodass er nun für die Anzeige der Informationen auf dem Bildschirm verwendet wird.

Ein anderer ist onCreateViewHolder, der uns mitteilt, wann der RecyclerView.The-Adapter eine reguläre Ansicht wie OneContactView annimmt und sie in ein ViewHolder-Element konvertiert, mit dem die RecyclerView arbeiten kann. Von unserem ViewHolder zur Verwendung durch den RecyclerView. onViewDetached

Abgesehen von den Kernmechanismen, die Recycling ermöglichen. Die RecyclerView bietet Möglichkeiten zum Anpassen des Verhaltens, ohne das Recycling zu beeinträchtigen.

Die Wiederverwendung von Ansichten macht es schwierig, häufige Dinge zu tun, die wir mit statischen Ansichten gewohnt sind, wie z. B. das Reagieren auf onClick-Ereignisse.

Da uns bewusst ist, dass die RecyclerView.LayoutManager die dem Benutzer die Ansichten präsentiert, könnte für einen Moment eine andere Liste von Elementen haben als die RecyclerView.Adapter das die Liste enthält, die wir in einer Datenbank gespeichert oder von einer Quelle gestreamt haben. Das direkte Einfügen von OnClick-Ereignissen in Ansichten kann zu unerwartetem Verhalten führen, z. B. zum Löschen oder Ändern des falschen Kontakts.

Gradle

Wenn wir RecyclerView verwenden möchten, müssen wir es als Abhängigkeit in unserer Build-.gradle-Datei hinzufügen.

Im folgenden Beispiel haben wir die Implementierung „androidx.recyclerview:recyclerview:1.1.0“ verwendet, die gemäß diesem Artikel die aktuellste Version ist.

Nach dem Hinzufügen der Abhängigkeit zu unserem Gradle Datei werden wir aufgefordert, Android Studio zu Syncdie Veränderungen chronisieren,

So ist unser Gradle So sieht die Datei aus, wenn in einem leeren Projekt eine RecyclerView nur mit den Standardeinstellungen hinzugefügt wird.

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "com.guru99.learnrecycler"
        minSdkVersion 17
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
             'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

    implementation "androidx.recyclerview:recyclerview:1.1.0"
}

Im Moment haben wir nur eine Layoutdatei. Wir beginnen mit einem einfachen Beispiel, in dem wir eine RecyclerView verwenden, um eine Liste mit Fruchtnamen auf dem Bildschirm anzuzeigen.

Liste von Gegenständen

Wir navigieren zu unserer MainActivity-Datei und erstellen direkt vor der onCreate()-Methode, die während der Einrichtung generiert wurde, ein Array mit darin enthaltenen Fruchtnamen.

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

Unser nächstes Ziel wird es sein, diese Liste mithilfe eines RecyclerView auf dem Bildschirm darzustellen.

Dazu navigieren wir zum Layoutverzeichnis, das unsere Layouts enthält, und erstellen eine Ansicht, die für die Anzeige einer Frucht zuständig ist.

Layout, das für jedes Element in unserer Liste verwendet werden soll

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/fruitName"
    />
</TextView>

In der Textansicht oben haben wir ein ID-Feld hinzugefügt, das zur Identifizierung einer Ansicht verwendet wird.

Es wird nicht standardmäßig generiert. Wir haben für unsere TextView die ID FruitName passend zu den Daten, die daran gebunden werden.

Hinzufügen der RecyclerView zum Hauptlayout

In derselben Aktivität gibt es die Layoutdatei main_layout.xml, die standardmäßig für uns generiert wurde.

Wenn wir ein leeres Projekt wählen. Es wird eine generiert haben XML enthält ein ConstraintLayout und darin befindet sich eine TextView mit „Hallo“-Text.

Wir löschen den gesamten Inhalt und sorgen dafür, dass das Layout nur die RecyclerView wie folgt enthält:

<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView 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:id="@+id/fruitRecyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" />

Wir haben auch ein ID-Attribut für RecyclerView hinzugefügt, das wir verwenden werden, um in unserem Code darauf zu verweisen.

 android:id="@+id/fruitRecyclerView"

Wir navigieren dann zurück zu unserer MainActivity-Datei. Mithilfe der von uns erstellten IDs können wir auf die soeben erstellten Ansichten verweisen.

Wir beginnen mit der Referenzierung von RecyclerView mithilfe der von bereitgestellten Methode findViewById() Android. Wir werden dies in unserer onCreate()-Methode tun.

Unsere onCreate()-Methode sieht wie folgt aus.

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
    }

Erstellen Sie einen ViewHolder

Als Nächstes erstellen wir einen RecyclerView.ViewHolder, der dafür verantwortlich ist, unsere Ansicht zu übernehmen und in einen ViewHolder umzuwandeln, den RecyclerView zum Anzeigen unserer Elemente verwendet.

Wir werden dies direkt nach unserer lustigen onCreate()-Methode tun.

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
    }
    
    class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)
    
}

Erstellen Sie einen RecyclerViewAdapter

Als Nächstes erstellen wir eine FruitArrayAdapter-Klasse, die die RecyclerView.Adapter-Klasse erweitert.

Der von uns erstellte FruitArrayAdapter ist für Folgendes verantwortlich.

Es werden Fruchtnamen aus der Fruchtreihe übernommen. Mithilfe unserer Ansicht one_fruit_view.xml wird ein ViewHolder erstellt. Anschließend wird die Frucht an einen ViewHolder gebunden und der Inhalt wird dynamisch an die von uns erstellte Ansicht one_fruit_view.xml gebunden.

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
    }
    
    class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)
    
    class FruitArrayAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>()
}

Android Studio fügt unserem FruitArrayAdapter rote Kringel hinzu und sagt uns, dass wir eine Methode implementieren müssen, die RecyclerView verwenden kann, um unser Array mit einem ViewHolder zu verbinden, den RecyclerView verwenden kann.

   class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {
            
        }

        override fun getItemCount(): Int {
         
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {
          
        }
    }

Wir beginnen mit dem einfachsten Teil des generierten Codes, der Methode getItemCount(). Wir wissen, wie wir die Anzahl der Elemente in unserem Array ermitteln, indem wir die Methode array.length aufrufen.

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {
            
        }

        override fun getItemCount(): Int {
         return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {
          
        }
    }

Dann implementieren wir die Methode override fun onCreateViewHolder.

An dieser Stelle bittet uns RecyclerView, ihm bei der Erstellung eines FruitHolder dafür zu helfen.

Wenn wir uns erinnern, sah unsere FruitViewHolder-Klasse so aus:

class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)

Es erfordert unser FruitView, das wir als XML-Datei one_fruit_view.xml erstellt haben

Wir können wie folgt einen Verweis auf diese XML-Datei erstellen und sie in eine Ansicht konvertieren.

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

            var fruitView = LayoutInflater.from(parent.context).inflate(R.layout.one_fruit_view, parent, false)

            var fruitViewHolder = FruitViewHolder(fruitView)

            return fruitViewHolder

        }

        override fun getItemCount(): Int {
            return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

        }
    }

Das verbleibende Bit ist der Override

fun onBindViewHolder(holder: FruitViewHolder, position: Int)

Der RecyclerView.Adapter fragt nach einer Positions-Ganzzahl, die wir verwenden, um ein Element aus unserer Liste abzurufen. Es stellt uns auch einen Halter zur Verfügung, sodass wir das Element, das wir aus dem FruitArray erhalten, an die Ansicht binden können, die im Ansichtshalter enthalten ist.

Auf die Ansicht, die in einem ViewHoder enthalten ist, kann über das Feld ViewHolder.itemView zugegriffen werden. Sobald wir die Ansicht erhalten haben, können wir die zuvor erstellte ID FruitName verwenden, um den Inhalt festzulegen.

  override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

            var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName)

            var aFruitName = fruitArray.get(position)

            ourFruitTextView.setText(aFruitName)
        }

Damit ist unser FruitArrayAdapter vollständig und sieht wie folgt aus.

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

            var fruitView = LayoutInflater.from(parent.context).inflate(R.layout.one_fruit_view, parent, false)

            var fruitViewHolder = FruitViewHolder(fruitView)

            return fruitViewHolder
        }

        override fun getItemCount(): Int {
            return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

            var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName)

            var aFruitName = fruitArray.get(position)

            ourFruitTextView.setText(aFruitName)
        }
    }

Schließlich sind wir bereit, die restlichen Teile unserer RecyclerView zu verkabeln. Diese erstellen einen LayoutManager, der RecyclerView mitteilt, wie der Inhalt der Liste angezeigt werden soll. Ob linear mit LinearLayoutManager oder in einem Raster mit GridLayoutManager oder StaggeredGridLayoutManager angezeigt werden soll.

Erstellen Sie einen Layout-Manager

Wir kehren zu unserer onCreate-Funktion zurück und fügen den LayoutManager hinzu

 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
        
        var fruitLinearLayout = LinearLayoutManager(this)
        
        myFruitRecyclerView.layoutManager =fruitLinearLayout
        
    }

Hängen Sie unseren Adapter an Elemente an und stellen Sie ihn auf RecyclerView ein

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
        
        var fruitLinearLayout = LinearLayoutManager(this)
        
        myFruitRecyclerView.layoutManager =fruitLinearLayout
        
        var fruitListAdapter = FruitListAdapter(fruitNames)
        
        myFruitRecyclerView.adapter =fruitListAdapter
    }

Wir haben auch eine FruitListAdapter-Instanz erstellt und ihr das Array mit Fruchtnamen zugeführt.

Und im Grunde sind wir alle fertig.

Die vollständige MainActivity.kt-Datei sieht wie folgt aus.

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)

        var fruitLinearLayout = LinearLayoutManager(this)

        myFruitRecyclerView.layoutManager =fruitLinearLayout

        var fruitListAdapter = FruitListAdapter(fruitNames)

        myFruitRecyclerView.adapter =fruitListAdapter
    }

    class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)

    class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

            var fruitView = LayoutInflater.from(parent.context).inflate(R.layout.one_fruit_view, parent, false)

            var fruitViewHolder = FruitViewHolder(fruitView)

            return fruitViewHolder
        }

        override fun getItemCount(): Int {
            return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

            var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName)

            var aFruitName = fruitArray.get(position)

            ourFruitTextView.setText(aFruitName)
        }
    }
}

Laden Sie das Projekt herunter