Android RecyclerView : Qu'est-ce que c'est, apprenez avec des exemples simples
โก Rรฉsumรฉ intelligent
RecyclerView est une fonctionnalitรฉ avancรฉe Android Ce widget permet d'afficher efficacement de grands ensembles de donnรฉes dรฉfilables en rรฉutilisant un nombre limitรฉ de vues. Il s'appuie sur un adaptateur, un gestionnaire de mise en page et un conteneur de vue pour lier les collections de donnรฉes qui รฉvoluent en cours d'exรฉcution.

Qu'est-ce que RecyclerView dans Android?
Le RecycleurVoir RecyclerView 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 et de les parcourir efficacement grรขce ร un nombre limitรฉ de vues. Vous pouvez utiliser le widget RecyclerView lorsque vos collections de donnรฉes sont modifiรฉes en temps rรฉel en fonction d'un รฉvรฉnement rรฉseau ou d'une action de l'utilisateur.
Vues
Le Android La plateforme utilise les classes View et ViewGroup pour dessiner des รฉlรฉments ร l'รฉcran. Ces classes sont absolumenttracLes classes TextView et EditText sont รฉtendues ร diffรฉrentes implรฉmentations pour s'adapter ร un cas d'utilisation. TextView, par exemple, a pour simple fonction d'afficher du texte ร l'รฉcran. EditText hรฉrite de la mรชme classe View et ajoute des fonctionnalitรฉs permettant ร l'utilisateur de saisir des donnรฉes.
Il est possible de crรฉer nos propres vues personnalisรฉes pour obtenir plus de flexibilitรฉ lors du dรฉveloppement.ping Interfaces utilisateur. La classe View fournit des mรฉthodes que nous pouvons redรฉfinir 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 souhaitons ajouter ร notre vue pour qu'elle se comporte comme nous le souhaitons.
Afficher les groupes
La classe ViewGroup est un type de vue, mais contrairement ร la classe View simple dont le rรดle se limite ร l'affichage, ViewGroup permet d'intรฉgrer plusieurs vues dans une seule vue, que l'on peut rรฉfรฉrencer comme un tout. Dans ce cas, la vue crรฉรฉe au niveau supรฉrieur, ร laquelle on ajoute d'autres vues simples, est appelรฉe ยซ parent ยป, et les vues ajoutรฉes ร l'intรฉrieur sont appelรฉes ยซ enfants ยป.
On peut se reprรฉsenter une vue comme un tableau et un ViewGroup comme un tableau de tableaux. Puisqu'un tableau de tableaux est lui-mรชme un tableau, on comprend qu'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 analogy, 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 l'organisation des รฉlรฉments enfants au sein de la vue, par exemple leur disposition verticale ou horizontale. Nous pouvons dรฉfinir diffรฉrentes rรจgles d'interaction au sein de la vue. Par exemple, deux TextView consรฉcutifs doivent รชtre espacรฉs de 12 dp, tandis qu'une ImageView suivie d'un TextView doit รชtre espacรฉe de 5 dp.
Ce serait le cas si nous รฉtions dรฉveloppรฉsping Nous avons crรฉรฉ notre propre ViewGroup ร partir de zรฉro. Pour simplifier ces configurations, Android fournit une classe appelรฉe LayoutParams, que nous pouvons utiliser pour saisir ces configurations.
Le Android Documentation Cette classe fournit des paramรจtres par dรฉfaut que nous utiliserons lors de la configuration de notre propre ViewGroup. Parmi les paramรจtres courants, on trouve la largeur, la hauteur et les marges. Par dรฉfaut, ces configurations utilisent la structure `android:layout_height` pour la hauteur et `android:layout_width` pour la largeur. Ainsi, lors de la crรฉation de votre ViewGroup, vous pouvez dรฉfinir des LayoutParams spรฉcifiques pour personnaliser son comportement.
Android Il est fourni avec des vues et des groupes de vues par dรฉfaut permettant d'effectuer la plupart des tรขches courantes. Par exemple, nous avons mentionnรฉ la TextView. Il s'agit d'une vue simple dont les paramรจtres sont configurables, tels que la hauteur, la largeur et la taille du texte. Nous proposons รฉgalement une ImageView pour afficher des images et un EditText, comme indiquรฉ prรฉcรฉdemment, parmi bien d'autres. Android dispose รฉgalement de ViewGroups personnalisรฉs auxquels nous pouvons ajouter nos vues et obtenir le comportement attendu.
Disposition linรฉaire
LinearLayout permet d'y ajouter des รฉlรฉments View. Il possรจde un attribut d'orientation qui dรฉtermine sa disposition ร l'รฉcran. Il possรจde รฉgalement LinearLayout.LayoutParams qui dรฉfinit les rรจgles d'affichage des vues. Par exemple, l'attribut android:center_horizontal centre les vues horizontalement, tandis que android:center_vertical centre le contenu de la vue verticalement.
Voici quelques images pour mieux comprendre le centrage. Prenons l'exemple d'un simple TextView dans un espace de 200 px par 200 px ; les attributs de centrage lui permettraient de se comporter comme suit.
Android : centre_horizontal
Contenu centrรฉ horizontalement
Android : centre_vertical
Contenu centrรฉ verticalement
Android:centre
Contenu centrรฉ
Composants principaux de RecyclerView
Composants principaux de RecyclerView
Voici les composants importants de RecyclerView :
RecyclerView.AdapterRecyclerView.Adapter
Base nยฐ1 โ Modรจle dโadaptateur
Un adaptateur est un dispositif qui transforme les attributs d'un systรจme ou d'un appareil pour les rendre compatibles avec un autre systรจme ou appareil incompatible. Certains adaptateurs modifient les attributs du signal ou de l'alimentation, tandis que d'autres se contentent d'adapter la forme physique d'un connecteur ร un autre.
Un exemple concret et simple illustrant l'utilitรฉ d'un adaptateur est celui oรน l'on souhaite connecter des appareils dont les ports de connexion ne sont pas compatibles. Cela peut arriver lors d'un voyage dans un pays utilisant des prises รฉlectriques diffรฉrentes. Si vous emportez votre chargeur de tรฉlรฉphone ou d'ordinateur portable, il vous sera impossible de le brancher directement sur les prises de courant. Heureusement, il existe une solution : un adaptateur qui se place entre la prise de courant et votre chargeur, permettant ainsi la recharge.
Cโest le cas en programmation lorsque lโon souhaite connecter deux structures de donnรฉes pour accomplir une tรขche, mais que leurs ports par dรฉfaut ne permettent pas de communiquer entre eux.
Nous utiliserons l'exemple simple d'un appareil et d'un chargeur. Nous aurons deux chargeurs : un amรฉricain et un britannique.
class AmericanCharger() { var chargingPower = 10 } class BritishCharger(){ var chargingPower = 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 allons ensuite introduire le concept de charge pour les deux appareils en ajoutant une mรฉthode appelรฉe `charge()`. Cette mรฉthode prendra en paramรจtre le chargeur correspondant et effectuera la charge en consรฉquence.
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, selon notre analogie, nous aurions besoin, pour une raison ou une autre, d'utiliser un chargeur britannique avec un appareil amรฉricain, ou inversement.
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 l'on suit l'analogie, il nous faudra aller dans un magasin d'รฉlectronique et acheter un adaptateur permettant de recharger des appareils amรฉricains avec des chargeurs britanniques. Du point de vue de la programmation, nous serons le fabricant de cet adaptateur.
Nous allons crรฉer un adaptateur qui correspond exactement au modรจle nรฉcessaire ร la crรฉation de l'autre. Nous l'implรฉmenterons sous forme de classe, comme suit. Il ne s'agit pas nรฉcessairement d'une classe ; une fonction illustrant le fonctionnement gรฉnรฉral du modรจle d'adaptateur pourrait suffire. Nous utilisons une classe car elle correspond ร la plupart des usages. 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 chargingPower:Int = theAmericanCharger.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 de charge. Des chargeurs utilisant des mรฉthodes diffรฉrentes seraient inutilisables.
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger()
Tenter d'appeler la mรฉthode charge() sur myBritishDevice avec americanChargerIFound ne fonctionnerait pas, car AmericanDevice n'accepte que des chargeurs amรฉricains. Il est donc impossible de procรฉder ainsi :
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger() myBritishDevice.charge(americanChargerIFound)
Dans ce cas prรฉcis, l'adaptateur que nous avons crรฉรฉ, AmericanToBritishChargerAdapter, s'avรจre trรจs utile. Nous pouvons utiliser la mรฉthode returnNewCharger() pour crรฉer un nouveau chargeur britannique, que nous pourrons ensuite utiliser pour recharger notre appareil. Il suffit de crรฉer une instance de notre adaptateur et de lui fournir le chargeur amรฉricain existant ; l'adaptateur crรฉera alors un chargeur britannique fonctionnel.
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger() //We create the adapter and feed it the americanCharger var myAdapter = AmericanToBritishChargerAdapter(americanChargerIFound) //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'on travaille avec un ViewGroup, on y place des vues. Le LayoutManager a pour rรดle de dรฉfinir la disposition de ces vues.
ร titre de comparaison, lorsqu'on travaille avec un LinearLayout ViewGroup, l'objectif est de pouvoir placer les รฉlรฉments verticalement ou horizontalement. Cela se fait facilement en ajoutant un attribut d'orientation, qui indique comment le LinearLayout sera positionnรฉ ร l'รฉcran. On peut le faire en utilisantโฆ android:orientation=VERTICAL|HORIZONTAL attribuer.
Nous disposons รฉgalement d'un autre ViewGroup appelรฉ GridLayout. Il est utilisรฉ pour disposer les vues dans une structure de grille rectangulaire, notamment pour faciliter la consultation des donnรฉes prรฉsentรฉes ร l'utilisateur. GridLayout offre des options de configuration permettant de dรฉfinir les dimensions de la grille ; par exemple, une grille 4ร4 ou 3ร2.
RecyclerView.ViewHolderRecyclerView.ViewHolder
Le ViewHolder est un ABStracLa classe `t`, que nous hรฉritons รฉgalement de `RecyclerView`, nous fournit des mรฉthodes communes permettant de rรฉfรฉrencer une vue placรฉe dans le `RecyclerView`, mรชme aprรจs que le mรฉcanisme de recyclage de ce dernier a modifiรฉ diverses rรฉfรฉrences dont nous n'avons pas connaissance.
Grandes listes
On utilise les RecyclerViews lorsqu'on souhaite prรฉsenter un trรจs grand nombre de vues ร l'utilisateur, sans pour autant รฉpuiser la mรฉmoire. RAM sur notre appareil pour chaque instance de la vue crรฉรฉe.
Prenons l'exemple d'une liste de contacts : nous avons une idรฉe gรฉnรฉrale de l'apparence d'un contact dans cette liste. Nous crรฉons ensuite un modรจle de mise en page, qui est en rรฉalitรฉ une vue, avec des emplacements pour insรฉrer diffรฉrentes donnรฉes de notre liste de contacts. Voici un pseudo-code qui explique le principe :
//OneContactView
<OneContact>
<TextView>{{PlaceHolderForName}}</TextView>
<TextView>{{PlaceHolderForAddress}}</TextView>
<ImageView>{{PlaceHolderForProfilePicture}}</ImageView>
<TextView>{{PlaceHolderForPhoneNumber}}</TextView>
</OneContact>
Nous aurions alors une liste de contacts de ce type :
<ContactList> </ContactList>
Si nous devions coder le contenu en dur, nous n'aurions aucun moyen d'ajouter du nouveau contenu ร la liste par programmation sans rรฉรฉcrire l'application. Heureusement pour nous, l'ajout d'une vue ร un ViewGroup est pris en charge par un addView(view:View) Cette mรฉthode n'est toutefois pas celle qui permet d'ajouter des vues enfants au RecyclerView.
Dans notre cas d'utilisation, nous aurions une longue liste de contacts. Pour chaque contact, nous devrions crรฉer une vue OneContactView et y renseigner les donnรฉes correspondant aux champs de notre classe Contact. Une fois la vue crรฉรฉe, nous devrions l'ajouter au 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 disposons d'un tableau de contacts. La vue OneContactView contient des emplacements pour rรฉcupรฉrer les donnรฉes de la classe Contact et les afficher. Dans RecyclerView, nous devons ajouter des vues pour qu'elle puisse gรฉrer le recyclage des contacts.
Le RecyclerView ne nous permet pas d'ajouter directement une vue, mais nous permet d'ajouter un ViewHolder. Dans ce cas prรฉcis, nous avons deux รฉlรฉments que nous souhaitons connecter, mais qui ne correspondent pas. C'est lร qu'intervient notre adaptateur. Le RecyclerView nous fournit un adaptateur, tout comme notreโฆ AmericanToBritishChargerAdapter() Cela nous a permis, comme prรฉcรฉdemment, de convertir notre chargeur amรฉricain, inutilisable avec notre appareil britannique, en quelque chose d'utilisable, semblable ร un adaptateur secteur dans la vie rรฉelle.
Dans ce scรฉnario, l'adaptateur prendrait notre tableau de contacts et notre vue, et gรฉnรฉrerait ร partir de lร des ViewHolders que le RecyclerView est prรชt ร accepter.
Le RecyclerView fournit une interface que nous pouvons รฉtendre pour crรฉer notre adaptateur via la classe RecyclerView.Adapter. Cet adaptateur contient un moyen de crรฉer la classe ViewHolder avec laquelle le RecyclerView doit interagir. Nous nous retrouvons donc dans la mรชme situation qu'auparavant, ร un รฉlรฉment supplรฉmentaire prรจs : l'adaptateur.
Nous disposons d'un tableau de contacts, d'une vue affichant un contact (OneContactView) et d'un RecyclerView, une liste de vues permettant le recyclage des รฉlรฉments, mais qui n'accepte que des ViewHolders. Dans ce cas, nous avons maintenant une classe RecyclerView.Adapter, qui possรจde une mรฉthode permettant de crรฉer des ViewHolders.
fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder
Le RecyclerView.ViewHolder est un absolutracCette classe prend notre vue comme argument et la convertit en ViewHolder. Elle utilise le modรจle de conception Wrapper, qui permet d'รฉtendre les fonctionnalitรฉ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 les faire parler, mais que l'auteur de la bibliothรจque n'รฉtait pas coopรฉratif, nous pourrions tout de mรชme trouver une solution. Il nous faudrait un wrapper pour notre classe Animal. Pour ce faire, nous prendrions l'objet Animal comme constructeur de notre classe.
class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } fun speak(){ println("Hello, my name is ${myAnimal.name}") } }
Nous pouvons maintenant passer une instance d'animal ร SpeechPoweredAnimalByWrapper. Appeler la mรฉthode sound() sur cette instance appellera la mรฉthode sound() de l'animal passรฉ en paramรจtre. Nous disposons รฉgalement d'une mรฉthode speak() supplรฉmentaire, qui constitue une nouvelle fonctionnalitรฉ ajoutรฉe aux animaux passรฉs en paramรจtre. Voici comment l'utiliser :
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"
Grรขce ร ce modรจle, nous pouvons prendre des classes et y ajouter des fonctionnalitรฉs. Il suffit de leur fournir une instance de classe et les nouvelles mรฉthodes dรฉfinies par notre wrapper.ping classe.
Dans l'exemple ci-dessus, nous avons utilisรฉ une classe concrรจte. Il est รฉgalement possible d'implรฉmenter la mรชme chose dans une classe abstraite.tracclasse t. Nous devrions modifier la classe SpeechPoweredAnimalByWrapper en abstracVoilร , c'est terminรฉ. Nous allons raccourcir le nom de la classe pour le rendre plus lisible.
abstract class SpeechPowered(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } fun 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. AbstracLes classes t, cependant, ne sont pas destinรฉes ร รชtre instanciรฉes mais ร รฉtendre d'autres classes. Alors, comment utiliserions-nous la nouvelle mรฉthode SpeechPowered(var myAnimal:Animal) abs ?tracLa classe t ? Nous pouvons lโutiliser en crรฉant de nouvelles classes qui lโรฉtendront et qui, par consรฉquent, acquerront ses 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 le ViewHolder. La classe RecyclerView.ViewHolder est une classe absolue.tracCette classe ajoute des fonctionnalitรฉs ร la vue, un peu comme nous avons ajoutรฉ la mรฉthode ยซ parler ยป aux animaux. Cโest cette fonctionnalitรฉ supplรฉmentaire qui permet son bon fonctionnement avec le RecyclerView.
Voici comment crรฉer un OneContactViewHolder ร partir d'un 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 possรจde un adaptateur qui nous permet de connecter notre tableau de contacts ร ContactsView via RecyclerView.
Ajout d'une vue
Le ViewGroup ne se redessine pas automatiquement, mais selon une frรฉquence prรฉcise. Sur votre appareil, il se peut qu'il se redessine toutes les 10 ms ou 100 ms ; ou, si l'on prend un intervalle absurde, par exemple 1 minute, lorsque vous ajoutez une vue ร un ViewGroup, les modifications seront visibles 1 minute plus tard, lorsque le ViewGroup se ยซ rafraรฎchit ยป.
RecyclerView.Recycler
Travaux prรฉparatoires nยฐ 3 โ La mise en cache
L'un des meilleurs exemples de situations oรน nous actualisons rรฉguliรจrement la page est le navigateur. Imaginons, par exemple, que le site que nous visitons soit statique et ne mette pas ร jour son contenu dynamiquement ; nous devrions alors actualiser la page rรฉguliรจrement pour voir les changements.
Prenons l'exemple de Twitter. On y trouverait une liste de tweets statiques, et le seul moyen de voir les nouveaux tweets serait de cliquer sur le bouton d'actualisation pour mettre ร jour le contenu.
Redessiner l'รฉcran entier est รฉvidemment une opรฉration coรปteuse. Imaginez si notre forfait tรฉlรฉphonique รฉtait limitรฉ et que 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 rafraรฎchissement.
Il nous faudrait un moyen de stocker les tweets dรฉjร chargรฉs et de faire en sorte que notre prochaine requรชte puisse identifier les tweets dรฉjร prรฉsents. Ainsi, au lieu de tout retรฉlรฉcharger, seuls les nouveaux tweets sont rรฉcupรฉrรฉs. De plus, le systรจme vรฉrifie si un tweet enregistrรฉ localement a disparu afin de le supprimer, le cas รฉchรฉant. Ce mรฉcanisme s'appelle la mise en cache.
Les informations que nous envoyons au site web concernant son contenu sont appelรฉes mรฉtadonnรฉes. Concrรจtement, nous ne disons pas simplement ยซ nous voulons charger votre site ยป, mais plutรดt ยซ nous voulons charger votre site et voici une partie du contenu que nous avons dรฉjร enregistrรฉ lors de notre dernier chargement ; veuillez l'utiliser pour ne nous envoyer que le contenu manquant, afin de ne pas consommer trop de bande passante. ยป
Appels de mise en page โ La liste de tweets doit รชtre folle
Un exemple d'appel de mise en page est scrollToPosition. C'est un exemple courant, notamment dans les applications de messagerie instantanรฉe. Si une personne rรฉpond ร une bulle de discussion prรฉcรฉdente, certaines applications incluent la rรฉponse et un lien vers la bulle ; en cliquant dessus, vous รชtes redirigรฉ vers votre message initial.
Dans le cas oรน nous appelons cette mรฉthode avant d'avoir ajoutรฉ un LayoutManager ร notre RecyclerView et avant d'avoir un RecyclerView.Adapter, scrollToPosition(n:Int) est simplement ignorรฉ.
Communication entre les composants RecyclerView
Travaux prรฉparatoires nยฐ 4 โ Rappels
Le RecyclerView, pour fonctionner, est un composant complexe. Il interagit avec le LayoutManager, qui dรฉtermine l'organisation des vues, linรฉairement ou sous forme de grille. Il gรจre รฉgalement un adaptateur qui convertit les รฉlรฉments (contactList) en vues (OneContactView), puis en ViewHolders (OneContactViewHolder), compatibles avec les mรฉthodes du RecyclerView.
La matiรจre premiรจre du RecyclerView est constituรฉe de nos vues, par exemple OneContactView, et d'une source de donnรฉes.
contactList:Array<Contact>
Nous avons utilisรฉ un scรฉnario simple comme point de dรฉpart pour comprendre le fonctionnement du RecyclerView. Le cas de base, ร savoir un tableau statique de 1 000 contacts ร afficher ร l'utilisateur, est facile ร apprรฉhender. Le fonctionnement du RecyclerView prend tout son sens lorsque la liste n'est plus statique. Avec une liste dynamique, il faut anticiper l'รฉvolution de l'affichage ร l'รฉcran lors de l'ajout ou de la suppression d'un รฉlรฉment.
RecyclerView.LayoutManager
Outre la dรฉcision de la disposition de nos vues, linรฉaire ou en grille, le LayoutManager effectue de nombreux travaux en arriรจre-plan.ping Les recycleurs savent quand effectuer le recyclage.
Il est responsable de garderping tracLe gestionnaire de mise en page (LayoutManager) rรฉcupรจre le nombre de vues actuellement affichรฉes ร l'รฉcran et communique cette information au mรฉcanisme de recyclage. Lorsque l'utilisateur fait dรฉfiler la page vers le bas, le LayoutManager informe le systรจme de recyclage des vues qui perdent le focus en haut de la page afin qu'elles puissent รชtre rรฉutilisรฉes, au lieu de rester affichรฉes et de consommer de la mรฉmoire ou d'รชtre dรฉtruites uniquement pour en crรฉer de nouvelles.
Cela signifie que le LayoutManager doit conserver tracLe systรจme dรฉtermine la position de l'utilisateur lorsqu'il fait dรฉfiler la liste. Pour ce faire, il utilise une liste de positions indexรฉes : le premier รฉlรฉment commence ร 0 et les numรฉros augmentent jusqu'ร correspondre au nombre d'รฉlรฉments dans la liste.
Si nous pouvons visualiser 10 รฉlรฉments sur une liste de, disons, 100, au dรฉbut, le LayoutManager sait qu'il a le focus sur les vues 0 ร 9. Au fur et ร mesure que nous faisons dรฉfiler, le LayoutManager est capable de calculer les vues qui perdent le focus.
Le LayoutManager peut libรฉrer ces vues dans le mรฉcanisme de recyclage afin qu'elles puissent รชtre rรฉutilisรฉes (de nouvelles donnรฉes peuvent leur รชtre liรฉes ; par exemple, les donnรฉes de contact d'une vue peuvent รชtre supprimรฉes et de nouvelles donnรฉes de contact du segment suivant peuvent remplacer les espaces rรฉservรฉs).
Ce serait idรฉal si notre liste รฉtait statique, mais l'un des cas d'utilisation les plus courants d'un RecyclerView concerne les listes dynamiques, oรน les donnรฉes peuvent provenir d'un point de terminaison en ligne ou mรชme d'un capteur. Non seulement des donnรฉes sont ajoutรฉes, mais elles peuvent รฉgalement รชtre supprimรฉes ou mises ร jour.
L'รฉtat dynamique de nos donnรฉes peut rendre l'analyse du LayoutManager trรจs complexe. C'est pourquoi le LayoutManager conserve sa propre liste des รฉlรฉments et de leurs positions, distincte de celle utilisรฉe par le composant de recyclage. Ceci garantit un affichage correct.
Parallรจlement, le LayoutManager du RecyclerView veille ร afficher correctement les donnรฉes. Pour ce faire, il se synchronise avec le RecyclerView.Adapter ร intervalles rรฉguliers (60 ms), partageant ainsi les informations relatives aux รฉlรฉments de la liste (ajoutรฉs, mis ร jour, supprimรฉs ou dรฉplacรฉs). Dรจs rรฉception de ces informations, le LayoutManager rรฉorganise l'affichage en consรฉquence.
La plupart des opรฉrations essentielles liรฉes ร RecyclerView s'articulent autour de la communication entre RecyclerView.LayoutManager et RecyclerView.Adapter qui stocke nos listes de donnรฉes, parfois statiques, parfois dynamiques.
De plus, RecyclerView nous offre des mรฉthodes permettant d'รฉcouter des รฉvรฉnements, comme onBindViewHolder lorsque notre RecyclerView.Adapter lie le contenu de notre liste (par exemple un contact) ร un ViewHolder afin qu'il soit utilisรฉ pour afficher les informations ร l'รฉcran.
Une autre est onCreateViewHolder, qui nous indique quand RecyclerView.Adapter prend une vue rรฉguliรจre comme OneContactView et la convertit en un รฉlรฉment ViewHolder avec lequel RecyclerView peut fonctionner.
Outre les mรฉcanismes de base permettant le recyclage, RecyclerView offre des possibilitรฉs de personnalisation du comportement sans incidence sur le recyclage. La rรฉutilisation des vues complique les opรฉrations courantes effectuรฉes avec des vues statiques, comme la rรฉaction aux รฉvรฉnements onClick.
Comme nous le savons, 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 Cette liste est stockรฉe dans une base de donnรฉes ou provient d'une source externe. L'intรฉgration directe d'รฉvรฉnements OnClick dans les vues peut entraรฎner des comportements inattendus, comme la suppression du mauvais contact.
Gradle
Pour utiliser RecyclerView, il faut l'ajouter comme dรฉpendance dans le fichier 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 Studio pour synchroniser les modifications. Voici comment notre Gradle Voici ร quoi ressemblera le fichier aprรจs l'ajout d'un RecyclerView dans un projet vide avec uniquement les paramรจtres 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 pour l'instant qu'un seul fichier de mise en page. Nous allons commencer par un exemple simple oรน nous utilisons 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 contenant des noms de fruits, juste avant la mรฉthode onCreate() 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 d'afficher cette liste ร l'รฉcran ร l'aide d'un RecyclerView. Pour ce faire, nous accรฉderons au rรฉpertoire de mise en page contenant nos mises en page et crรฉerons une vue qui affichera 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 id qui servira ร identifier la vue. Ce champ n'est pas gรฉnรฉrรฉ par dรฉfaut. Nous avons attribuรฉ ร notre TextView l'identifiant fruitName afin qu'il corresponde aux donnรฉes qui lui seront liรฉes.
Ajout de RecyclerView ร la mise en page principale
Dans la mรชme activitรฉ, un fichier de mise en page main_layout.xml a รฉtรฉ gรฉnรฉrรฉ par dรฉfaut. Si nous avions choisi un projet vide, il aurait gรฉnรฉrรฉ un fichier de mise en page. XML Ce RecyclerView contient un ConstraintLayout avec un TextView affichant le texte ยซ Bonjour ยป. Nous allons supprimer tout son contenu pour ne conserver que 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 au RecyclerView, que nous utiliserons pour le rรฉfรฉrencer dans notre code.
android:id="@+id/fruitRecyclerView"
Nous retournerons ensuite ร notre fichier MainActivity. Grรขce aux 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 AndroidNous allons procรฉder ainsi dans notre mรฉthode onCreate(). Voici ร quoi ressemblera notre mรฉthode onCreate().
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 se chargera de convertir notre vue en un ViewHolder, que le RecyclerView utilisera pour afficher nos รฉlรฉments. Nous effectuerons cette opรฉration juste aprรจs l'exรฉcution de notre mรฉthode 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. Cette classe FruitArrayAdapter sera chargรฉe des opรฉrations suivantes : elle extraira les noms des fruits du tableau de fruits, crรฉera un ViewHolder ร partir de notre vue one_fruit_view.xml, puis liera les fruits ร ce ViewHolder et enfin, liera dynamiquement le contenu ร la vue crรฉรฉe.
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 traits rouges ondulรฉs sur notre FruitArrayAdapter, nous indiquant que nous devons implรฉmenter des mรฉthodes que le RecyclerView peut utiliser pour connecter notre tableau ร un ViewHolder.
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 allons commencer par la partie la plus simple du code gรฉnรฉrรฉ : la mรฉthode `getItemCount()`. Nous savons comment obtenir le nombre d'รฉlรฉments dans notre tableau en appelant la propriรฉtรฉ `size` du tableau.
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) { } }
Nous allons ensuite implรฉmenter la mรฉthode `onCreateViewHolder`. C'est ร ce moment que le RecyclerView nous demande de l'aider ร construire un `FruitViewHolder`. Pour rappel, voici ร quoi ressemblait notre classe `FruitViewHolder` :
class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)
Cela nรฉcessite notre vue fruitView, que nous avons crรฉรฉe sous forme de fichier XML one_fruit_view.xml. Nous pouvons crรฉer une rรฉfรฉrence ร ce fichier 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) { } }
La partie restante concerne la surcharge de la mรฉthode onBindViewHolder.
fun onBindViewHolder(holder: FruitViewHolder, position: Int)
L'adaptateur RecyclerView demande un entier de position, que nous utilisons pour rรฉcupรฉrer un รฉlรฉment de notre liste. Il nous fournit รฉgalement un conteneur permettant de lier l'รฉlรฉment rรฉcupรฉrรฉ de fruitArray ร la vue contenue dans ce conteneur. Cette vue est accessible via le champ ViewHolder.itemView. Une fois la vue rรฉcupรฉrรฉe, nous pouvons utiliser l'identifiant fruitName crรฉรฉ prรฉcรฉdemment pour dรฉfinir son 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) }
Notre FruitArrayAdapter est ainsi terminรฉ et ressemble ร ceci.
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 ร connecter les รฉlรฉments restants de notre RecyclerView, ร savoir la crรฉation d'un LayoutManager qui indiquera au RecyclerView comment afficher le contenu de la liste, soit de maniรจre linรฉaire ร l'aide de LinearLayoutManager, soit sous forme de grille ร l'aide de GridLayoutManager ou de StaggeredGridLayoutManager.
Crรฉer un gestionnaire de mise en page
Nous allons retourner 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 de fruitListAdapter et l'avons alimentรฉe avec le tableau de noms de fruits. Et voilร , c'est terminรฉ ! Voici ร quoi ressemble le fichier MainActivity.kt complet.
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) } } }




