Android RecyclerView : Qu'est-ce que c'est, apprenez avec des exemples simples
Qu'est-ce que RecyclerView dans Android?
Votre RecycleurVoir est un widget qui est une version plus flexible et avancée de GridView et ListView. Il s'agit d'un conteneur permettant d'afficher de grands ensembles de données qui peuvent défiler efficacement en conservant un nombre limité de vues. Vous pouvez utiliser le widget RecyclerView lorsque vous disposez de collections de données dont les éléments changent au moment de l'exécution en fonction d'un événement réseau ou d'une action de l'utilisateur.
Vues
Votre Android La plateforme utilise les classes View et ViewGroup pour dessiner des éléments à l'écran. Ces classes sont abstraites et sont étendues à différentes implémentations pour répondre à un cas d'utilisation. TextView, par exemple, a pour objectif simple d'afficher du contenu textuel à l'écran. EditText s'étend de la même classe View et ajoute plus de fonctionnalités pour permettre à l'utilisateur de saisir des données.
Il est possible de créer nos propres vues personnalisées pour pouvoir obtenir plus de flexibilité lors du développement d'interfaces utilisateur. La classe View fournit des méthodes que nous pouvons remplacer pour dessiner à l'écran et un moyen de transmettre des paramètres tels que la largeur, la hauteur et nos propres attributs personnalisés que nous voudrions ajouter à notre vue pour qu'elle se comporte comme nous le souhaiterions.
Afficher les groupes
La classe ViewGroup est une sorte de View mais, contrairement à la simple classe View dont la responsabilité est simplement d'afficher, ViewGroup nous donne la possibilité de regrouper plusieurs vues dans une seule vue, que nous pouvons référencer dans son ensemble. Dans ce cas, la vue créée au niveau supérieur à laquelle nous ajoutons d'autres vues simples (nous pouvons également ajouter des viewGroups) est appelée le « parent » et les vues ajoutées à l'intérieur sont des « enfants ».
Nous pouvons imaginer une vue sous forme de tableau et un ViewGroup sous forme de tableau de tableaux. Étant donné qu'un tableau de tableaux est un tableau lui-même, nous pouvons voir comment un ViewGroup peut être traité comme une vue.
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
Le ViewGroup nous permet également de définir comment les enfants sont organisés à l'intérieur de la vue, par exemple, sont disposés verticalement ou horizontalement. Nous pouvons avoir différentes règles d'interaction à l'intérieur de la vue. Par exemple, les TextViews qui se suivent doivent avoir une distance de 12 dp tandis que les ImageViews suivis de TextView doivent avoir une distance de 5 dp.
Ce serait le cas si nous développions notre propre ViewGroup à partir de zéro. Pour faciliter ces configurations, Android fournit une classe appelée LayoutParams, que nous pouvons utiliser pour saisir ces configurations.
Android Documentation fournit certains paramètres par défaut que nous implémenterions lors de la configuration de notre propre ViewGroup. Certains paramètres courants concernent la largeur, la hauteur et la marge. Par défaut, ces configurations ont la structure android:layout_height pour la hauteur, par exemple, android:layout_width À cet égard, lorsque vous créez votre ViewGroup, vous pouvez en outre créer des LayoutParams spécifiques à la façon dont vous souhaitez que votre ViewGroup se comporte.
Android est livré avec des vues et des ViewGroups par défaut que nous pouvons utiliser pour effectuer de nombreuses tâches courantes dont nous avons besoin. Un exemple que nous avons mentionné est un TextView. Il s'agit d'une vue simple comportant des aspects configurables tels que la hauteur, la largeur, la taille du texte, etc. Nous avons un ImageView pour afficher les images et EditText comme nous l'avions mentionné, entre autres. Android dispose également de ViewGroups personnalisés auxquels nous pouvons ajouter nos vues et obtenir le comportement attendu.
Disposition linéaire
LinearLayout nous permet d'y ajouter des éléments View. LinearLayout est un attribut d'orientation qui dicte la manière dont il sera disposé à l'écran. Il a également LinearLayout.LayoutParams qui dicte les règles pour les vues à l'intérieur, par exemple, l'attribut android:center_horizontal centrerait les vues le long de l'axe horizontal tandis que `android:center_vertical centrerait le contenu de la vue le long de l'axe vertical.
Voici quelques images pour comprendre le centrage. Nous considérerions qu'il s'agit d'un simple TextView dans un espace de 200 px sur 200 px, les attributs de centrage le feraient se comporter comme suit.
Android : centre_horizontal
Android : centre_vertical
Android:centre
Composants principaux de RecyclerView
Voici les composants importants de RecyclerView :
RecyclerView.AdapterRecyclerView.Adapter
Base n°1 – Modèle d’adaptateur
Un adaptateur est un périphérique qui transforme les attributs d'un système ou d'un périphérique en ceux d'un périphérique ou d'un système autrement incompatible. Certains d'entre eux modifient les attributs du signal ou de la puissance, tandis que d'autres adaptent simplement la forme physique d'un connecteur à un autre.
Un exemple simple que nous trouvons dans la vie réelle pour expliquer un adaptateur est lorsque nous devons connecter des appareils ensemble, mais qu'ils ont des ports de connexion qui ne correspondent pas les uns aux autres. Cela peut être le cas lorsque vous visitez un autre pays où ils utilisent différents types de prises. Si vous transportez votre chargeur de téléphone ou d’ordinateur portable, il serait impossible de le connecter aux prises de courant. Cependant, vous n'abandonnerez pas mais vous obtiendrez simplement un adaptateur qui s'interposerait entre la prise de courant et votre chargeur et permettrait la recharge.
C'est le cas en programmation lorsque nous voulons connecter deux structures de données ensemble afin d'accomplir la tâche, mais que leurs ports par défaut n'ont aucun moyen de communiquer entre eux.
Nous utiliserons l'exemple simple d'un appareil et d'un chargeur. Nous aurons deux instances de chargeurs. Un américain et un britannique
class AmericanCharger() { var chargingPower = 10 } class BritishCharger(){ var charginPower = 5 }
Nous allons ensuite créer deux appareils
class AmericanDevice() class BritishDevice()
À titre d'exemple, nous pouvons ensuite créer des instances des appareils pour jouer.
var myAmericanPhone = new AmericanDevice() var myBritishPhone = new BritishDevice()
Nous présenterons ensuite le concept de recharge pour les deux appareils en ajoutant une méthode dans les appareils appelée charge() .
La méthode prend en entrée son chargeur respectif et effectue une charge en fonction de celui-ci.
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 } }
Dans ce cas, sur la base de notre analogie, nous aurions, pour une raison ou une autre, besoin d'utiliser un BritishCharger lors de l'utilisation d'un appareil américain ou vice versa.
Dans le monde de la programmation, cela se produit généralement lors du mélange de bibliothèques offrant les mêmes fonctionnalités (dans notre contexte, notre fonctionnalité partagée est payante). Nous aurions besoin de trouver un moyen de permettre cela.
Si nous suivons l'analogie, nous devrons nous rendre dans un magasin d'électronique et acheter un adaptateur qui nous permettra de recharger les appareils américains dotés de chargeurs britanniques. Du point de vue de la programmation, c'est nous qui serons le fabricant de l'adaptateur.
Nous allons créer un adaptateur pour l'un qui correspond au modèle exact dont nous aurions besoin pour créer l'autre. Nous allons l'implémenter en tant que classe comme suit. Il ne doit pas nécessairement s'agir d'une classe et peut être une fonction qui met en évidence ce que fait généralement le modèle d'adaptateur. Nous utiliserons une classe car elle correspond à la plupart des utilisations sur 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 } }
Dans le monde de la programmation, la différence entre les prises est analogue à la différence entre les méthodes utilisées à l’intérieur pour le chargement. Les chargeurs ayant des méthodes différentes rendraient impossible leur utilisation.
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger()
Essayer d'appeler la méthode charge() dans myBritishDevice avec americanChargerIFound ne fonctionnerait pas car AmericanDevice n'accepte qu'un AmericanCharger
Donc c'est impossible de faire ça
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger() myBritishDevice.charge(americanChargerIFound)
Dans ce scénario, l'adaptateur que nous avons créé
AmericanToBritishChargerAdapter peut désormais s’avérer utile. Nous pouvons utiliser la méthode returnNewCharger() pour créer un nouveau BritishCharger, que nous pouvons utiliser pour facturer. Tout ce dont nous avons besoin est de créer une instance de notre adaptateur et de l'alimenter avec l'AmericanCharger dont nous disposons, et cela créera un BritishCharger que nous pourrons utiliser.
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
Lorsqu'il s'agit d'un ViewGroup, nous aurions des vues placées à l'intérieur. C'est LayoutManager qui aurait pour tâche de décrire la manière dont les vues sont disposées à l'intérieur.
À des fins de comparaison, lorsque nous travaillons avec Linearlayout ViewGroup, le cas d'utilisation que nous souhaitons est la possibilité de placer les éléments verticalement ou horizontalement. Ceci est facilement implémenté en ajoutant un attribut d'orientation, qui nous indique comment la disposition linéaire sera placée sur l'écran. Nous pouvons le faire en utilisant android:orientation=VERTICAL|HORIZONTAL
attribuer.
Nous avons également un autre ViewGroup appelé GridLayout, son cas d'utilisation est lorsque nous souhaitons placer des vues dans une structure de grille rectangulaire. Cela pourrait être dû à des raisons telles que rendre les données que nous présentons à l'utilisateur de l'application faciles à consommer. De par sa conception, le GridLayout permet aux configurations de vous aider à atteindre cet objectif en ayant des configurations où nous pouvons définir les dimensions de la grille par exemple, nous pouvons avoir une grille 4×4, une grille 3 x 2.
RecyclerView.ViewHolderRecyclerView.ViewHolder
Le ViewHolder est une classe abstraite que nous étendons également à partir de RecyclerView. Le ViewHolder nous fournit des méthodes courantes pour nous aider à référencer une vue que nous avons placée sur le RecyclerView même après que les machines de recyclage du RecyclerView ont modifié diverses références dont nous ne connaissons pas l'existence.
Grandes listes
Les RecyclerViews sont utilisés lorsque nous souhaitons présenter un très grand ensemble de vues à l'utilisateur, sans épuiser notre RAM sur notre appareil pour chaque instance de la vue créée.
Si nous devions prendre le cas d'une liste de contacts, nous aurions une idée générale de l'apparence d'un contact dans la liste. Ce que nous ferions ensuite, c'est créer une mise en page de modèle – qui est en fait une vue – avec des emplacements où seront remplies diverses données de notre liste de contacts. Voici un pseudo-code qui explique tout le but :
//OneContactView <OneContact> <TextView>{{PlaceHolderForName}}</TextView> <TextView>{{PlaceHolderForAddress}}</TextView> <ImageView>{{PlaceHolderForProfilePicture}}</ImageView> <TextView>{{PlaceHolderForPhoneNumber}}</TextView> </OneContact>
Nous aurions alors une ContactList de cette nature
<ContactList> </ContactList>
Si c'est le cas, nous codions en dur le contenu, nous n'aurions pas de moyen programmatique d'ajouter du nouveau contenu à la liste sans réécrire l'application. Heureusement pour nous. L'ajout d'une vue à un ViewGroup est pris en charge par un addView(view:View)
méthode.
Même si tel est le cas, ce n'est pas ainsi que RecyclerView ajoute des vues enfants.
Dans notre cas d'utilisation, nous aurions une longue liste de contacts. Pour chaque contact de la liste, nous devrons créer OneContactView et remplir les données à l'intérieur de la vue pour qu'elles correspondent aux champs de notre classe Contact. Ensuite, une fois que nous aurons la vue, nous devrons l'ajouter à RecyclerView pour afficher la liste.
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)
Nous avons un ensemble de contacts appelé OneContactView. Il contient des emplacements pour récupérer le contenu de la classe Contact et l'afficher. Dans RecyclerView, nous devons y ajouter des vues afin que cela puisse nous aider avec sa capacité de recyclage.
Le RecyclerView ne nous permet pas vraiment d'ajouter une vue mais nous permet d'ajouter un ViewHolder. Donc, dans ce scénario, nous avons deux éléments que nous voulons connecter mais qui ne correspondent pas. C'est là qu'intervient notre adaptateur. RecyclerView nous fournit un adaptateur très similaire à notre AmericanToBritishChargerAdapter()
d'avant, cela nous a permis de convertir notre AmericanCharger qui était inutilisable avec notre BritishDevice en quelque chose d'utilisable, semblable à un adaptateur secteur dans la vraie vie.
Dans ce scénario, l'adaptateur prendrait notre tableau de contacts et notre vue, et à partir de là générerait des ViewHolders que RecyclerView est prêt à accepter.
Le RecyclerView fournit une interface que nous pouvons étendre pour créer notre adaptateur via la classe RecyclerView.Adapter. À l’intérieur de cet adaptateur se trouve un moyen de créer la classe ViewHolder avec laquelle RecyclerView souhaite travailler. Nous avons donc la même situation qu’avant, mais avec une chose en plus, c’est l’adaptateur.
Nous avons un tableau de contacts, une vue pour afficher un contact OneContactView. Un RecyclerView est une liste de vues qui fournissent des services de recyclage mais qui ne souhaitent accepter que des ViewHolders.
Mais dans ce scénario, nous avons maintenant la classe RecyclerView.Adapter, qui contient une méthode pour créer des ViewHolders.
fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder
Le RecyclerView.ViewHolder est une classe abstraite qui prend notre View comme argument et la convertit en ViewHolder.
Il utilise le modèle wrapper utilisé pour étendre les capacités des classes.
Base #2 – Modèle d’emballage
Nous utiliserons un exemple simple pour démontrer comment faire parler les animaux.
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
Dans l’exemple ci-dessus, nous avons deux animaux. Si par hasard, nous voulions ajouter une méthode pour faire parler, mais que l'auteur de la bibliothèque n'était pas amusant, nous pourrions quand même trouver un moyen. Ce dont nous avons besoin serait un wrapper pour notre classe Animal. Nous ferions cela en prenant Animal comme constructeur pour notre classe
class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } speak(){ println("Hello, my name is ${myAnimal.name}") } }
Nous pouvons maintenant transmettre une instance animale au SpeechPoweredAnimalByWrapper. Appeler la méthode sound() dessus appellerait la méthode animal sound() transmise. Nous avons également une méthode speak() supplémentaire, qui compte comme une nouvelle fonctionnalité que nous ajoutons aux animaux transmis. Nous pouvons l'utiliser comme suit :
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"
En utilisant ce modèle, nous pouvons suivre des cours et ajouter des fonctionnalités. Tout ce dont nous aurions besoin est de transmettre une instance de classe et de nouvelles méthodes définies par notre classe d'emballage.
Dans notre cas ci-dessus, nous avons utilisé une classe concrète. Il est également possible d'implémenter la même chose dans une classe Abstract. Nous aurions besoin de changer la classe SpeechPoweredAnimalByWrapper en abstract et nous avons terminé. Nous allons changer le nom de la classe en quelque chose de plus court pour le rendre plus lisible.
abstract class SpeechPowered(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } speak(){ println("Hello, my name is ${myAnimal.name}") } }
C'est la même chose qu'avant, mais cela signifierait autre chose. Dans une classe normale, nous pouvons avoir une instance d’une classe de la même manière que nous avons créé cat1 et dog1. Cependant, les classes abstraites ne sont pas destinées à être instanciées mais à étendre d'autres classes. Alors, comment utiliserions-nous la nouvelle classe abstraite SpeechPowered(var myAnimal:Animal). Nous pouvons l'utiliser en créant de nouvelles classes qui l'étendront et, à leur tour, gagneront en fonctionnalités.
Dans notre exemple, nous allons créer une classe SpeechPoweredAnimal qui étend la classe
class SpeechPoweredAnimal(var myAnimal:Animal):SpeechPowered(myAnimal)
var cat1 = Cat("Tubby") var speakingKitty = SpeechPoweredAnimal(cat1) speakingKitty.speak() //"Hello, my name is Tubby"
Il s’agit du même modèle utilisé dans ViewHolder. La classe RecyclerView.ViewHolder est une classe abstraite qui ajoute des fonctionnalités à la vue, un peu comme nous avons ajouté la méthode speak aux animaux. La fonctionnalité ajoutée est ce qui le fait fonctionner lorsqu’il s’agit de RecyclerView.
Voici comment nous créerions un OneContactViewHolder à partir de OneContactView
//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 dispose d'un adaptateur qui nous permet de connecter notre tableau de contacts au ContactsView avec le RecyclerView
Ajout d'une vue
Le ViewGroup ne redessine pas automatiquement le ViewGroup mais suit un calendrier particulier. Il se peut que votre appareil se redessine toutes les 10 ms ou 100 ms, ou si nous choisissons un nombre absurde, par exemple 1 minute, lorsque nous ajoutons une vue à un ViewGroup, vous verrez les changements 1 minute plus tard lorsque le ViewGroup « s'actualise ».
RecyclerView.Recycler
Base #3. Mise en cache
L’un des meilleurs exemples d’endroits où nous effectuons régulièrement des actualisations se trouve dans le navigateur. Imaginons, par exemple, que le site que nous visitons soit statique et n'envoie pas de contenu de manière dynamique, nous aurions besoin de continuer à l'actualiser pour voir les changements.
Pour cet exemple, imaginons que le site en question soit Twitter. Nous aurions une série de tweets statiques répertoriés et la seule façon de voir les nouveaux tweets serait de cliquer sur le bouton d'actualisation pour récupérer le contenu.
Repeindre tout l’écran est évidemment une chose coûteuse. Si nous devions imaginer que c'était le cas, nous avions une bande passante limitée avec notre opérateur téléphonique. Et notre liste de tweets contenait beaucoup d'images et de vidéos, il serait coûteux de retélécharger tout le contenu de la page à chaque actualisation.
Nous aurions besoin d'un moyen de stocker les Tweets déjà chargés et de garantir que notre prochaine requête ait la capacité d'indiquer les Tweets qu'elle possède déjà. Par conséquent, il ne télécharge pas tout à nouveau et récupère uniquement les nouveaux Tweets dont il dispose et vérifie également si certains Tweets enregistrés localement ne sont plus là afin de pouvoir les supprimer localement. Ce que nous décrivons s'appelle la mise en cache.
Les informations que nous envoyons au site Web sur les contenus dont nous disposons sont appelées métadonnées. Donc, dans le vrai sens, nous ne disons pas simplement « nous voulons charger votre site », nous disons « nous voulons charger votre site, et voici une partie du contenu que nous avions déjà enregistré lors du dernier chargement, veuillez l'utiliser pour envoyez-nous uniquement ce qui n'est pas là, nous n'utilisons donc pas beaucoup de bande passante car nous n'avons pas beaucoup de ressources.
Appels de mise en page – La liste de tweets doit être folle
Un exemple d'appel de mise en page est scrollToPosition
Il s'agit d'un exemple courant présent dans des éléments tels que les applications de chat. Si quelqu'un dans un fil de discussion répond à une bulle de discussion précédente, certaines applications de discussion incluent la réponse et un lien vers la bulle de discussion, qui, en cliquant sur vous, vous dirige vers l'endroit où se trouvait votre message d'origine.
Dans le cas, nous appelons cette méthode avant d'avoir un LayoutManager ajouté à notre RecyclerView et avant d'avoir un RecyclerView.Adapter, le scrollToPosition(n:Int) est simplement ignoré.
Communication entre les composants RecyclerView
Base #4. Rappels
Le RecyclerView, pour faire son travail, comporte de nombreuses pièces mobiles. Il doit gérer le LayoutManager qui nous indique comment organiser les vues, soit linéairement, soit dans une grille. Il doit faire face à un adaptateur qui fait le travail de convertir nos éléments contactList en Views OneContactView puis en ViewHolders OneContactViewHolder que le RecyclerView est prêt à travailler selon les méthodes qu'il nous propose.
La matière première du RecyclerView est nos vues, par exemple OneContactView et la source de données.
contactList:Array<Contact>
Nous avons utilisé un scénario simple comme point de départ pour avoir une idée de ce que RecyclerView tente d'accomplir.
Un cas de base dans lequel nous aurions un tableau statique de 1000 contacts que nous souhaitons montrer à l'utilisateur est facile à comprendre.
La machinerie RecyclerView commence vraiment à prendre vie lorsque la liste n'est plus statique.
Avec une liste dynamique, nous devons penser à ce qui arrive à la vue à l'écran lorsque nous ajoutons un élément à la liste ou supprimons un élément de la liste.
RecyclerView.LayoutManager
En plus de décider comment nos vues doivent être disposées de manière linéaire ou dans une grille. Le LayoutManager fait beaucoup de travail sous le capot pour aider le recycleur à savoir quand effectuer le recyclage.
Il est chargé de garder une trace des vues actuellement visibles à l'écran et de communiquer ces informations au mécanisme de recyclage. Lorsqu'un utilisateur fait défiler vers le bas, le gestionnaire de mise en page est chargé d'informer le système de recyclage des vues floues en haut afin qu'elles puissent être réutilisées au lieu d'y rester et de consommer de la mémoire ou au lieu de les détruire et de devoir créer les nouvelles.
Cela signifie que le LayoutManager doit garder une trace de l'endroit où se trouve l'utilisateur lorsqu'il fait défiler notre liste. Pour ce faire, il dispose d'une liste de positions qui sont une base d'index, c'est-à-dire que le premier élément doit commencer à 0 et augmenter pour correspondre au nombre d'éléments de notre liste.
Si nous pouvons afficher 10 éléments sur notre liste de 100, par exemple, au début, le LayoutManager est conscient qu'il a le focus sur la vue 0 jusqu'à la vue 9. Au fur et à mesure que nous faisons défiler, le LayoutManager est capable de calculer les vues qui sortent. de concentration.
Le LayoutManager est capable de transmettre ces vues au mécanisme de recyclage afin qu'elles puissent être réutilisées (de nouvelles données peuvent y être liées, par exemple les données de contact d'une vue peuvent être supprimées et les nouvelles données de contact du segment suivant peuvent remplacer les espaces réservés).
Ce serait un cas heureux si la liste que nous avons est statique, mais l'un des cas d'utilisation les plus courants d'utilisation d'un RecyclerView concerne les listes dynamiques où les données peuvent provenir d'un point de terminaison en ligne ou même peut-être d'un capteur. Non seulement les données sont ajoutées, mais les données de notre liste sont parfois supprimées ou mises à jour.
L'état dynamique de nos données peut rendre très difficile le raisonnement sur le LayoutManager. Pour cette raison, le LayoutManager gère sa propre liste d'éléments et de positions, distincte de la liste utilisée par le composant Recycling. Cela garantit qu'il effectue correctement son travail de mise en page.
Dans le même temps, le LayoutManager de RecyclerView ne veut pas déformer les données dont il dispose. Afin de fonctionner correctement, le LayoutManager se synchronise avec le RecyclerView.Adapter à intervalles donnés (60 ms) en partageant des informations sur nos éléments de liste. c'est-à-dire les éléments ajoutés, mis à jour, supprimés, déplacés d'une position à une autre). Le LayoutManager, dès réception de ces informations, réorganise le contenu à l'écran pour correspondre aux modifications lorsque cela est nécessaire.
La plupart des opérations de base liées à RecylerView tournent autour de la communication entre RecyclerView.LayoutManager et RecyclerView.Adapter qui stocke nos listes de données parfois statiques ou parfois dynamiques.
De plus, RecyclerView nous présente des méthodes que nous pouvons utiliser pour écouter des événements tels que onBindViewHolder lorsque notre RecyclerView.Adapter lie le contenu de notre liste, par exemple un contact à un ViewHolder afin qu'il soit désormais utilisé pour afficher les informations à l'écran.
Un autre est onCreateViewHolder, qui nous indique quand le RecyclerView. L'adaptateur prend une vue normale comme OneContactView et la convertit en un élément ViewHolder avec lequel le RecyclerView peut fonctionner. De notre ViewHolder pour une utilisation par RecyclerView. onViewDétaché
En dehors des mécanismes de base qui permettent le recyclage. Le RecyclerView fournit des moyens de personnaliser le comportement sans affecter le recyclage.
La réutilisation des vues rend difficile les tâches courantes que nous avons l'habitude de faire avec des vues statiques, telles que la réaction aux événements onClick.
Comme nous sommes conscients que le RecyclerView.LayoutManager
qui présente les vues à l'utilisateur peut avoir pendant un moment une liste d'éléments différente de celle de la RecyclerView.Adapter
qui contient la liste que nous avons stockée dans une base de données ou diffusée à partir d'une source. Placer des événements OnClick directement sur les vues peut entraîner un comportement inattendu, tel que la suppression ou la modification du mauvais contact.
Gradle
Si nous voulons utiliser RecyclerView, nous devons l'ajouter en tant que dépendance dans notre fichier de build .gradle.
Dans l'exemple suivant, nous avons utilisé l'implémentation « androidx.recyclerview:recyclerview:1.1.0 » qui est la version la plus récente selon cet article.
Après avoir ajouté la dépendance à notre Gradle fichier, nous serons invités par Android Atelier à Synchroniser les changements,
C'est ainsi que notre Gradle ressemblera au fichier après l’ajout d’un RecyclerView dans un projet vide avec uniquement les valeurs par défaut.
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" }
Nous n'avons qu'un seul fichier de mise en page pour le moment. Nous commencerons par un exemple simple dans lequel nous utiliserons un RecyclerView pour afficher une liste de noms de fruits à l'écran.
Liste d'objets
Nous allons accéder à notre fichier MainActivity et créer un tableau avec les noms de fruits à l'intérieur juste avant la méthode onCreate() qui a été générée lors de la configuration.
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) } }
Notre prochain objectif sera de présenter cette liste à l'écran à l'aide d'un RecyclerView.
Pour ce faire, nous allons naviguer vers le répertoire de mise en page qui contient nos mises en page et créer une vue qui sera chargée d'afficher un fruit.
Disposition à utiliser pour chaque élément de notre liste
<?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>
Dans le TextView ci-dessus, nous avons ajouté un champ d'identification qui sera utilisé pour identifier une vue.
Il n'est pas généré par défaut. Nous avons dans notre TextView l'identifiant fruitName pour correspondre aux données, qui y seront liées.
Ajout de RecyclerView à la mise en page principale
Dans la même activité, il y a le fichier de mise en page main_layout.xml qui a été généré pour nous par défaut.
Si nous choisissons un projet vide. Cela aura généré un XML contenant un ConstraintLayout et à l’intérieur se trouvera un TextView avec le texte « Bonjour ».
Nous supprimerons tout le contenu et ferons en sorte que la mise en page contienne uniquement le RecyclerView comme ci-dessous :
<?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" />
Nous avons également ajouté un attribut id pour RecyclerView que nous utiliserons pour le référencer dans notre code.
android:id="@+id/fruitRecyclerView"
Nous reviendrons ensuite à notre fichier MainActivity. En utilisant les identifiants que nous avons créés, nous pourrons référencer les vues que nous venons de créer.
Nous commencerons par référencer le RecyclerView à l’aide de la méthode findViewById() fournie par Android. Nous ferons cela dans notre méthode onCreate().
Notre méthode onCreate() ressemblera à ceci.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView) }
Créer un ViewHolder
Ensuite, nous allons créer un RecyclerView.ViewHolder qui est chargé de prendre notre vue et de la convertir en ViewHolder, que le RecyclerView utilise pour afficher nos éléments.
Nous le ferons juste après notre méthode amusante onCreate().
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) }
Créer un RecyclerViewAdapter
Ensuite, nous allons créer une classe FruitArrayAdapter qui étend la classe RecyclerView.Adapter.
Le FruitArrayAdapter que nous créons sera chargé d'effectuer les tâches suivantes.
Il prendra les noms de fruits de la gamme de fruits. Il créera un ViewHolder en utilisant notre vue one_fruit_view.xml. Il liera ensuite le fruit à un ViewHolder et liera dynamiquement le contenu à la vue que nous avons créée one_fruit_view.xml.
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 ajoutera des gribouillis rouges sur notre FruitArrayAdapter, nous indiquant que nous devons implémenter une méthode que RecyclerView peut utiliser pour connecter notre tableau à un ViewHolder que RecyclerView peut utiliser.
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) { } }
Nous commencerons par le bit le plus simple du code généré : la méthode getItemCount(). Nous savons comment obtenir le nombre d'éléments de notre tableau en appelant la méthode array.length.
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) { } }
Ensuite, nous implémenterons la méthode override fun onCreateViewHolder.
C'est là que RecyclerView nous demande de l'aider à construire un FruitHolder pour lui.
Si nous nous rappelons, voici à quoi ressemblait notre classe FruitViewHolder :
class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)
Cela nécessite notre fruitView que nous avons créé sous forme de fichier XML one_fruit_view.xml
Nous pouvons créer une référence à ce XML et la convertir en vue comme suit.
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) { } }
Le bit restant est le remplacement
fun onBindViewHolder(holder: FruitViewHolder, position: Int)
Le RecyclerView.Adapter demande avec un entier de position, que nous utiliserons pour récupérer un élément de notre liste. Il nous fournit également un support afin que nous puissions lier l'élément que nous obtenons du fruitArray à la vue conservée à l'intérieur du support de vue.
La vue conservée à l'intérieur d'un ViewHoder est accessible via le champ ViewHolder.itemView. Une fois que nous obtenons la vue, nous pouvons utiliser l'identifiant fruitName que nous avons créé précédemment pour définir le contenu.
override fun onBindViewHolder(holder: FruitViewHolder, position: Int) { var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName) var aFruitName = fruitArray.get(position) ourFruitTextView.setText(aFruitName) }
Avec cela, notre FruitArrayAdapter est complet et se présente comme suit.
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) } }
Enfin, nous sommes prêts à câbler les éléments restants de notre RecyclerView. Qui créent un LayoutManager, qui indiquera au RecyclerView comment afficher le contenu de la liste. Que ce soit pour afficher de manière linéaire à l'aide de LinearLayoutManager ou dans une grille à l'aide de GridLayoutManager ou StaggeredGridLayoutManager.
Créer un gestionnaire de mise en page
Nous allons revenir dans notre fonction onCreate et ajouter le LayoutManager
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 }
Accrochez notre adaptateur aux éléments et configurez-le sur RecyclerView
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 }
Nous avons également créé une instance fruitListAdapter et lui avons fourni le tableau de noms de fruits.
Et en gros, nous avons tous terminé.
Le fichier MainActivity.kt complet se présente comme suit.
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) } } }