Android RecyclerView: co to jest, ucz się na prostych przykładach
⚡ Inteligentne podsumowanie
RecyclerView to zaawansowany Android Widżet, który efektywnie wyświetla duże, przewijalne zestawy danych, przetwarzając ograniczoną liczbę widoków. Opiera się na adapterze, menedżerze układu i posiadaczu widoku, aby powiązać zbiory danych, które zmieniają się w czasie wykonywania.

W czym jest RecyclerView Android?
RecyklerWidok to bardziej elastyczna i zaawansowana wersja GridView i ListView. Jest to kontener do wyświetlania dużych zbiorów danych, które można sprawnie przewijać, utrzymując ograniczoną liczbę widoków. Widżet RecyclerView można wykorzystać w przypadku kolekcji danych, których elementy zmieniają się w czasie wykonywania w zależności od zdarzenia sieciowego lub działania użytkownika.
odwiedzajacy
Android Platforma używa klas View i ViewGroup do rysowania elementów na ekranie. Klasy te to abstract i są rozszerzane do różnych implementacji, aby dopasować je do konkretnego przypadku użycia. Na przykład TextView ma proste zadanie wyświetlania tekstu na ekranie. EditText rozszerza tę samą klasę View i dodaje więcej funkcjonalności, umożliwiając użytkownikowi wprowadzanie danych.
Możliwe jest tworzenie własnych niestandardowych widoków, aby uzyskać większą elastyczność podczas rozwojuping Interfejsy użytkownika. Klasa View udostępnia metody, które możemy zastąpić, aby rysować na ekranie, oraz sposób przekazywania parametrów, takich jak szerokość, wysokość i nasze własne atrybuty niestandardowe, które chcielibyśmy dodać do naszego View, aby zachowywał się zgodnie z naszymi oczekiwaniami.
Wyświetl grupy
Klasa ViewGroup jest rodzajem widoku, ale w przeciwieństwie do prostej klasy View, której zadaniem jest jedynie wyświetlanie, ViewGroup daje nam możliwość umieszczenia wielu widoków w jednym, do którego możemy odwoływać się jako do całości. W tym przypadku widok utworzony na najwyższym poziomie, do którego dodajemy inne proste widoki, nazywany jest „widokiem nadrzędnym”, a widoki dodawane w jego obrębie – „widokami potomnymi”.
Możemy wyobrazić sobie widok jako tablicę, a grupę widoków jako tablicę tablic. Zakładając, że tablica tablic sama w sobie jest tablicą, możemy zrozumieć, jak grupę widoków można traktować jak widok.
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
Grupa Widoków pozwala nam również zdefiniować sposób organizacji elementów podrzędnych w widoku, na przykład, czy są one ułożone pionowo, czy poziomo. Możemy mieć różne reguły interakcji w widoku. Na przykład, obiekty TextView znajdujące się jeden po drugim powinny mieć odstęp 12 dp, podczas gdy obiekt ImageView, po którym następuje obiekt TextView, powinien mieć odstęp 5 dp.
Tak by było, gdybyśmy byli rozwijaniping od podstaw stworzyliśmy własną grupę ViewGroup. Aby ułatwić te konfiguracje, Android udostępnia klasę o nazwie LayoutParams, której możemy użyć do wprowadzenia tych konfiguracji.
Android dokumentacja Zawiera domyślne parametry, które zaimplementowalibyśmy podczas konfigurowania własnej grupy widoków. Niektóre typowe parametry dotyczą szerokości, wysokości i marginesu. Domyślnie te konfiguracje mają strukturę android:layout_height dla wysokości i android:layout_width dla szerokości. W związku z tym, podczas tworzenia grupy widoków, możesz dodatkowo utworzyć parametry układu (LayoutParams) specyficzne dla pożądanego sposobu działania grupy widoków.
Android Zawiera domyślne widoki i grupy widoków, których możemy używać do wykonywania wielu typowych zadań. Jednym z przykładów, o których wspomnieliśmy, jest TextView. To prosty widok z konfigurowalnymi parametrami, takimi jak wysokość, szerokość i rozmiar tekstu. Mamy również ImageView do wyświetlania obrazów i EditText, jak już wspomnieliśmy, i wiele innych. Android ma również niestandardowe grupy widoków, do których możemy dodać nasze widoki i uzyskać oczekiwane zachowanie.
Układ liniowy
LinearLayout pozwala nam dodawać do niego elementy widoku. LinearLayout ma atrybut orientacji, który określa sposób rozmieszczenia na ekranie. Posiada również LinearLayout.LayoutParams, który określa reguły dla widoków wewnątrz. Na przykład atrybut android:center_horizontal wyśrodkuje widoki wzdłuż osi poziomej, podczas gdy android:center_vertical wyśrodkuje zawartość widoku wzdłuż osi pionowej.
Oto kilka obrazów, które pomogą zrozumieć centrowanie. Zakładamy, że jest to prosty obiekt TextView w przestrzeni o wymiarach 200 x 200 pikseli; atrybuty centrowania sprawią, że będzie się on zachowywał w następujący sposób.
android:środek_poziomy
Treść wyśrodkowana poziomo
android:środek_pionowy
Zawartość wyśrodkowana pionowo
android:środek
Wyśrodkowana treść
Podstawowe składniki RecyclerView
Podstawowe składniki RecyclerView
Poniżej przedstawiono najważniejsze komponenty RecyclerView:
RecyclerView.Adapter
Prace przygotowawcze nr 1 – Wzór adaptera
Adapter to urządzenie, które przekształca atrybuty jednego systemu lub urządzenia w atrybuty innego, niekompatybilnego urządzenia lub systemu. Niektóre adaptery modyfikują atrybuty sygnału lub zasilania, podczas gdy inne po prostu dostosowują fizyczną formę jednego złącza do innego.
Prostym przykładem z życia wziętym, który wyjaśnia istotę adaptera, jest sytuacja, gdy musimy połączyć ze sobą urządzenia, ale ich porty nie pasują do siebie. Może się tak zdarzyć, gdy odwiedzasz inny kraj, w którym obowiązują różne rodzaje gniazdek. Jeśli nosisz ze sobą ładowarkę do telefonu lub laptopa, podłączenie jej do gniazdka elektrycznego byłoby niemożliwe. Nie warto jednak rezygnować z tego pomysłu i po prostu zaopatrzyć się w adapter, który podłącza się między gniazdko a ładowarkę i umożliwia ładowanie.
Dzieje się tak w programowaniu, gdy chcemy połączyć ze sobą dwie struktury danych, aby wykonać zadanie, ale ich domyślne porty nie mają możliwości komunikowania się ze sobą.
Posłużymy się prostym przykładem urządzenia i ładowarki. Będziemy mieli dwa przykłady ładowarek: amerykańską i brytyjską.
class AmericanCharger() { var chargingPower = 10 } class BritishCharger(){ var chargingPower = 5 }
Następnie utworzymy dwa urządzenia.
class AmericanDevice() class BritishDevice()
Możemy na przykład utworzyć kilka instancji urządzeń do wspólnej zabawy.
var myAmericanPhone = new AmericanDevice() var myBritishPhone = new BritishDevice()
Następnie wprowadzimy koncepcję ładowania obu urządzeń, dodając w nich metodę charge(). Metoda ta pobiera dane z odpowiedniej ładowarki i na ich podstawie przeprowadza ładowanie.
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 } }
W tym przypadku, opierając się na naszej analogii, z jakiegoś powodu musielibyśmy użyć brytyjskiej ładowarki, gdy korzystamy z amerykańskiego urządzenia, i odwrotnie.
W świecie programowania ma to zazwyczaj miejsce podczas mieszania bibliotek oferujących tę samą funkcjonalność (w naszym kontekście nasza wspólna funkcjonalność jest płatna). Musielibyśmy znaleźć sposób, aby to umożliwić.
Jeśli zastosujemy się do analogii, będziemy musieli udać się do sklepu z elektroniką i kupić adapter, który umożliwi nam ładowanie urządzeń amerykańskich za pomocą brytyjskich ładowarek. Z punktu widzenia programowania, będziemy producentem adaptera.
Stworzymy adapter, który dokładnie odpowiada wzorcowi potrzebnemu do utworzenia drugiego. Zaimplementujemy go jako klasę w następujący sposób. Nie musi to być koniecznie klasa, może to być funkcja, która podkreśla, co wzorzec adaptera zazwyczaj robi. Używamy klasy, ponieważ pasuje ona do większości zastosowań. 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 } }
W świecie programowania różnica między gniazdami jest analogiczna do różnicy w metodach ładowania. Ładowarki z różnymi metodami ładowania uniemożliwiłyby korzystanie z nich.
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger()
Próba wywołania metody charge() na urządzeniu myBritishDevice za pomocą americanChargerIFound nie zadziała, ponieważ urządzenie AmericanDevice akceptuje tylko ładowarkę AmericanCharger. Dlatego nie da się tego zrobić:
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger() myBritishDevice.charge(americanChargerIFound)
W tym scenariuszu utworzony przez nas adapter AmericanToBritishChargerAdapter może okazać się przydatny. Możemy użyć metody returnNewCharger(), aby utworzyć nowy adapter BritishCharger, którego będziemy mogli używać do ładowania. Wystarczy utworzyć instancję naszego adaptera i przekazać jej posiadany adapter AmericanCharger, a adapter utworzy adapter BritishCharger, którego będziemy mogli używać.
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
W przypadku grupy widoków (ViewGroup) umieszczamy w niej widoki. LayoutManager ma za zadanie opisanie, jak widoki są rozmieszczone w jej wnętrzu.
Dla porównania, pracując z grupą widoków LinearLayout, chcemy mieć możliwość rozmieszczania elementów w pionie lub poziomie. Można to łatwo zaimplementować, dodając atrybut orientacji, który określa, jak LinearLayout zostanie umieszczony na ekranie. Możemy to zrobić za pomocą android:orientation=VERTICAL|HORIZONTAL atrybutów.
Mamy również inną grupę widoków o nazwie GridLayout. Jej zastosowanie polega na umieszczaniu widoków w prostokątnej strukturze siatki. Może to wynikać z chęci ułatwienia użytkownikowi aplikacji korzystania z prezentowanych danych. Z założenia GridLayout umożliwia konfiguracje, które pomogą osiągnąć ten cel poprzez zdefiniowanie wymiarów siatki; na przykład możemy mieć siatkę 4×4 lub 3×2.
RecyclerView.ViewHolder
ViewHolder to abstracKlasa t, którą również rozszerzamy z RecyclerView. ViewHolder udostępnia nam typowe metody, które pomagają nam odwoływać się do widoku umieszczonego w RecyclerView, nawet po tym, jak maszyny recyklingowe w RecyclerView zmieniły różne odniesienia, o których nie wiemy.
Duże listy
RecyclerViews są używane, gdy chcemy przedstawić użytkownikowi naprawdę duży zestaw widoków, nie wyczerpując przy tym RAM na naszym urządzeniu dla każdego wystąpienia utworzonego widoku.
Jeśli weźmiemy pod uwagę listę kontaktów, mielibyśmy ogólne pojęcie, jak będzie wyglądał jeden kontakt na liście. Następnie stworzylibyśmy układ szablonu, który jest w rzeczywistości widokiem, z polami, w których będą umieszczane różne dane z naszej listy kontaktów. Poniżej znajduje się pseudokod, który wyjaśnia cały cel:
//OneContactView
<OneContact>
<TextView>{{PlaceHolderForName}}</TextView>
<TextView>{{PlaceHolderForAddress}}</TextView>
<ImageView>{{PlaceHolderForProfilePicture}}</ImageView>
<TextView>{{PlaceHolderForPhoneNumber}}</TextView>
</OneContact>
Mielibyśmy wtedy listę kontaktów tego typu:
<ContactList> </ContactList>
Gdybyśmy zakodowali zawartość na stałe, nie mielibyśmy programowego sposobu dodawania nowej zawartości do listy bez przepisywania aplikacji. Na szczęście dla nas dodawanie widoku do grupy widoków jest obsługiwane przez addView(view:View) Metoda ta nie jest jednak sposobem, w jaki RecyclerView dodaje do siebie widoki podrzędne.
W naszym przypadku mielibyśmy długą listę kontaktów. Dla każdego kontaktu na liście musielibyśmy utworzyć widok OneContactView i wypełnić go danymi, tak aby odpowiadały polom w naszej klasie Contact. Następnie, gdy już mamy widok, musielibyśmy dodać go do widoku RecyclerView, aby wyświetlić listę.
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)
Mamy tablicę kontaktów. OneContactView zawiera sloty do pobierania zawartości z klasy Contact i wyświetlania jej. W RecyclerView musimy dodać do niego widoki, aby mógł on usprawnić swoją funkcję recyklingu.
RecyclerView tak naprawdę nie pozwala nam na dodanie widoku, ale umożliwia dodanie ViewHoldera. W tym scenariuszu mamy dwa elementy, które chcemy połączyć, ale które do siebie nie pasują. Tu właśnie pojawia się nasz adapter. RecyclerView zapewnia nam adapter, podobnie jak nasz… AmericanToBritishChargerAdapter() dzięki temu mogliśmy przekształcić naszą ładowarkę AmericanCharger, która nie nadawała się do użytku z naszym urządzeniem BritishDevice, w coś użytecznego, coś na kształt prawdziwego zasilacza.
W tym scenariuszu adapter wziąłby naszą tablicę kontaktów i nasz widok, a następnie wygenerowałby na tej podstawie obiekty ViewHolder, które RecyclerView jest gotowy zaakceptować.
RecyclerView udostępnia interfejs, który możemy rozszerzyć, aby utworzyć nasz adapter za pomocą klasy RecyclerView.Adapter. Wewnątrz tego adaptera znajduje się sposób na utworzenie klasy ViewHolder, z którą RecyclerView chce współpracować. Mamy więc tę samą sytuację co poprzednio, ale z jednym dodatkowym elementem – adapterem.
Mamy tablicę kontaktów, widok wyświetlający jeden kontakt (OneContactView) oraz RecyclerView, czyli listę widoków, które oferują usługi recyklingu, ale akceptują tylko ViewHoldery. W tym scenariuszu mamy teraz klasę RecyclerView.Adapter, która zawiera metodę tworzenia ViewHolderów.
fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder
RecyclerView.ViewHolder to abstracKlasa t przyjmuje nasz widok jako argument i konwertuje go na ViewHolder. Wykorzystuje wzorzec wrappera, który służy do rozszerzania możliwości klas.
Prace przygotowawcze #2 – Wzór owijania
Pokażemy na prostym przykładzie, jak sprawić, by zwierzęta mówiły.
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
W powyższym przykładzie mamy dwa zwierzęta. Gdybyśmy chcieli dodać metodę, która sprawiłaby, że będą mówić, ale autor biblioteki nie byłby zbyt sympatyczny, nadal moglibyśmy znaleźć sposób. Potrzebowalibyśmy wrappera dla naszej klasy Animal. Zrobilibyśmy to, przyjmując Animal jako konstruktor dla naszej klasy.
class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } fun speak(){ println("Hello, my name is ${myAnimal.name}") } }
Teraz możemy przekazać instancję animal do obiektu SpeechPoweredAnimalByWrapper. Wywołanie metody sound() wywołałoby przekazaną metodę animal sound(). Mamy również dodatkową metodę speak(), która jest traktowana jako nowa funkcjonalność dodana do przekazanych zwierząt. Możemy jej użyć w następujący sposób:
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"
Korzystając z tego wzorca, możemy pobierać klasy i dodawać funkcjonalność. Wystarczy przekazać instancję klasy i nowe metody zdefiniowane przez nasz obiekt wrap.ping class.
W powyższym przypadku użyliśmy klasy konkretnej. Można również zaimplementować to samo w abs.tract. Musielibyśmy zmienić klasę SpeechPoweredAnimalByWrapper na abstract i gotowe. Zmienimy nazwę klasy na krótszą, żeby była bardziej czytelna.
abstract class SpeechPowered(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } fun speak(){ println("Hello, my name is ${myAnimal.name}") } }
To samo co poprzednio, ale oznaczałoby coś innego. W normalnej klasie możemy mieć instancję klasy w taki sam sposób, w jaki utworzyliśmy cat1 i dog1. AbstracKlasy t nie są jednak przeznaczone do tworzenia instancji, lecz do rozszerzania innych klas. Jak więc moglibyśmy wykorzystać nową funkcję SpeechPowered(var myAnimal:Animal)?tracKlasa t? Możemy z niej korzystać, tworząc nowe klasy, które ją rozszerzą i tym samym zyskają jej funkcjonalność.
W naszym przykładzie utworzymy klasę SpeechPoweredAnimal, która rozszerza klasę.
class SpeechPoweredAnimal(var myAnimal:Animal):SpeechPowered(myAnimal)
var cat1 = Cat("Tubby") var speakingKitty = SpeechPoweredAnimal(cat1) speakingKitty.speak() //"Hello, my name is Tubby"
To ten sam wzorzec, który zastosowano w ViewHolder. Klasa RecyclerView.ViewHolder jest abstracKlasa t, która dodaje funkcjonalność do widoku, podobnie jak dodaliśmy metodę speak do zwierząt. Dodatkowa funkcjonalność sprawia, że działa ona w przypadku RecyclerView.
Oto jak utworzylibyśmy OneContactViewHolder z 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 ma adapter umożliwiający nam połączenie tablicy kontaktów z ContactsView za pomocą RecyclerView.
Dodawanie widoku
Grupa Widoków nie odświeża się automatycznie, ale działa według określonego harmonogramu. Na Twoim urządzeniu może się zdarzyć, że odświeża się co 10 ms lub 100 ms; lub, jeśli wybierzemy absurdalną liczbę, na przykład 1 minutę, po dodaniu widoku do grupy widoków zmiany będą widoczne minutę później, gdy grupa widoków się odświeży.
RecyclerView.Recycler
Prace przygotowawcze nr 3 – Buforowanie
Jednym z najlepszych przykładów regularnego odświeżania jest przeglądarka. Wyobraźmy sobie na przykład, że odwiedzana przez nas strona jest statyczna i nie wysyła dynamicznie treści; musielibyśmy regularnie odświeżać stronę, aby zobaczyć zmiany.
W tym przykładzie wyobraźmy sobie, że chodzi o Twittera. Mielibyśmy serię statycznych tweetów, a jedynym sposobem na zobaczenie nowych tweetów byłoby kliknięcie przycisku „Odśwież”, aby ponownie pobrać treść.
Odświeżenie całego ekranu jest oczywiście kosztowne. Wyobraźmy sobie, że mamy ograniczoną przepustowość u naszego operatora, a nasza lista tweetów zawiera mnóstwo zdjęć i filmów; ponowne pobieranie całej zawartości strony przy każdym odświeżeniu byłoby kosztowne.
Potrzebowalibyśmy sposobu na przechowywanie już załadowanych tweetów i upewnienie się, że nasze kolejne żądanie będzie w stanie wskazać, które tweety już posiada. W związku z tym nie pobiera wszystkiego ponownie, a jedynie nowe tweety, a także sprawdza, czy któryś z tweetów zapisanych lokalnie już tam nie jest, aby móc go lokalnie usunąć. To, co opisujemy, nazywa się buforowaniem.
Informacje, które przesyłamy do witryny na temat posiadanych przez nas treści, nazywamy metadanymi. Tak więc w rzeczywistości nie mówimy po prostu „chcemy załadować Twoją witrynę”, ale „chcemy załadować Twoją witrynę, a oto część treści, którą już zapisaliśmy od ostatniego ładowania; proszę, użyj ich do przesłania nam tylko tego, czego tam nie ma, abyśmy nie zużywali dużo transferu danych”.
Połączenia dotyczące układu – lista tweetów musi być szalona
Przykładem wywołania układu jest scrollToPosition. Jest to powszechny przykład występujący na przykład w aplikacjach do czatów. Jeśli ktoś w wątku czatu odpowie na wcześniejszą wiadomość, niektóre aplikacje do czatów dołączają odpowiedź i link do dymka, który po kliknięciu przenosi do miejsca, w którym znajdowała się oryginalna wiadomość.
W przypadku, gdy wywołamy tę metodę przed dodaniem obiektu LayoutManager do obiektu RecyclerView i utworzeniem obiektu RecyclerView.Adapter, metoda scrollToPosition(n:Int) zostanie po prostu zignorowana.
Komunikacja pomiędzy komponentami RecyclerView
Prace przygotowawcze nr 4 – Wywołania zwrotne
RecyclerView, wykonując swoją pracę, składa się z wielu ruchomych części. Musi współpracować z LayoutManager, który podpowiada nam, jak organizować widoki, liniowo lub w siatce. Musi również współpracować z adapterem, który konwertuje nasze elementy (contactList) na widoki (OneContactView), a następnie na obiekty ViewHolder (OneContactViewHolder), z którymi RecyclerView jest gotowy współpracować za pomocą udostępnianych przez siebie metod.
Surowcem dla RecyclerView są nasze widoki, np. OneContactView i źródło danych.
contactList:Array<Contact>
Użyliśmy prostego scenariusza jako punktu wyjścia, aby zrozumieć, co RecyclerView próbuje osiągnąć. Przypadek bazowy, w którym mamy statyczną tablicę 1000 kontaktów, które chcemy pokazać użytkownikowi, jest łatwy do zrozumienia. Mechanizm RecyclerView zaczyna naprawdę działać, gdy lista przestaje być statyczna. W przypadku listy dynamicznej musimy zastanowić się, co dzieje się z widokiem na ekranie, gdy dodamy element do listy lub go z niej usuniemy.
RecyclerView.LayoutManager
Oprócz decydowania o tym, jak mają być rozłożone nasze widoki, liniowo lub w siatce, LayoutManager wykonuje wiele zadań pod maską.ping Recykler wie, kiedy dokonać recyklingu.
Jest odpowiedzialny za utrzymanieping track widoków aktualnie widocznych na ekranie i przekazuje te informacje do mechanizmu recyklingu. Gdy użytkownik przewija w dół, LayoutManager jest odpowiedzialny za informowanie systemu recyklingu o widokach, które tracą ostrość na górze, aby można je było ponownie wykorzystać, zamiast pozostawiać je tam i zużywać pamięć lub usuwać tylko w celu utworzenia nowych.
Oznacza to, że LayoutManager musi zachować track pozycji, w której znajduje się użytkownik przewijający naszą listę. Osiąga to poprzez listę pozycji opartą na indeksie, tj. pierwszy element zaczyna się od 0 i zwiększa się, dopasowując się do liczby elementów na liście.
Jeśli możemy wyświetlić 10 elementów na naszej liście, powiedzmy, 100, na początku LayoutManager jest świadomy, że fokus ma wszystkie widoki od View-0 do View-9. Podczas przewijania LayoutManager jest w stanie obliczyć, które widoki tracą fokus.
LayoutManager może zwolnić te widoki do mechanizmu recyklingu, aby mogły zostać ponownie wykorzystane (można powiązać z nimi nowe dane, np. dane kontaktowe widoku można usunąć, a nowe dane kontaktowe z następnego segmentu mogą zastąpić symbole zastępcze).
Byłoby to korzystne, gdyby lista była statyczna, ale jednym z najczęstszych zastosowań RecyclerView są listy dynamiczne, gdzie dane mogą pochodzić z punktu końcowego online, a nawet z czujnika. Dane nie tylko są dodawane, ale czasami są również usuwane lub aktualizowane na naszej liście.
Dynamiczny stan naszych danych może znacznie utrudniać wnioskowanie w Menedżerze Układów. Z tego powodu Menedżer Układów utrzymuje własną listę elementów i pozycji, niezależną od listy używanej przez komponent recyklingu. Dzięki temu zapewnia prawidłowe wykonywanie zadań układu.
Jednocześnie LayoutManager w RecyclerView nie chce przeinaczać posiadanych danych. Aby działać poprawnie, LayoutManager synchronizuje się z RecyclerView.Adapter w określonych odstępach czasu (60 ms), udostępniając informacje o elementach listy, tj. elementach dodanych, zaktualizowanych, usuniętych lub przeniesionych z jednej pozycji na drugą. LayoutManager, po otrzymaniu tych informacji, reorganizuje zawartość ekranu, dostosowując ją do zmian, gdy jest to konieczne.
Wiele podstawowych operacji związanych z RecyclerView opiera się na komunikacji między RecyclerView.LayoutManager i RecyclerView.Adapter, w których przechowywane są nasze listy danych, czasami statycznych, a czasami dynamicznych.
Ponadto RecyclerView udostępnia nam metody, których możemy używać do nasłuchiwania zdarzeń, takie jak onBindViewHolder, gdy nasz RecyclerView.Adapter wiąże zawartość z naszej listy (np. kontakt) z ViewHolder, aby ten mógł zostać użyty do wyświetlania informacji na ekranie.
Innym parametrem jest onCreateViewHolder, który informuje nas, kiedy RecyclerView.Adapter przyjmuje zwykły obiekt View, taki jak OneContactView, i konwertuje go na element ViewHolder, z którym RecyclerView może współpracować.
Oprócz podstawowych mechanizmów umożliwiających recykling, RecyclerView oferuje sposoby dostosowywania działania bez wpływu na recykling. Ponowne wykorzystanie widoków utrudnia wykonywanie typowych czynności, do których jesteśmy przyzwyczajeni w przypadku widoków statycznych, takich jak reagowanie na zdarzenia onClick.
Jak wiemy, RecyclerView.LayoutManager który prezentuje widoki użytkownikowi, może przez chwilę mieć inną listę elementów niż RecyclerView.Adapter którego lista jest przechowywana w bazie danych lub przesyłana strumieniowo ze źródła. Umieszczanie zdarzeń OnClick bezpośrednio w widokach może prowadzić do nieoczekiwanych zachowań, takich jak usunięcie niewłaściwego kontaktu.
Gradle
Jeśli chcemy używać RecyclerView, musimy dodać go jako zależność w pliku build.gradle. W poniższym przykładzie użyliśmy implementacji „androidx.recyclerview:recyclerview:1.1.0”, która jest najnowszą wersją zgodną z tym artykułem.
Po dodaniu zależności do naszego Gradle plik, zostaniemy poproszeni o Android Studio synchronizować zmiany. Tak właśnie działa nasz Gradle Plik będzie wyglądał po dodaniu RecyclerView do pustego projektu, zawierającego tylko ustawienia domyślne.
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" }
Na razie mamy tylko jeden plik układu. Zaczniemy od prostego przykładu, w którym użyjemy RecyclerView do wyświetlenia listy nazw owoców na ekranie.
Lista rzeczy
Przejdziemy do pliku MainActivity i utworzymy tablicę z nazwami owoców, tuż przed metodą onCreate(), która została wygenerowana podczas konfiguracji.
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) } }
Naszym kolejnym celem będzie zaprezentowanie tej listy na ekranie za pomocą RecyclerView. Aby to zrobić, przejdziemy do katalogu layout, w którym znajdują się nasze layouty, i utworzymy widok, który będzie odpowiadał za wyświetlanie jednego owocu.
Układ, który ma być używany dla każdego elementu na naszej liście
<?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>
W powyższym widoku TextView dodaliśmy pole identyfikatora, które będzie służyło do identyfikacji widoku. Nie jest ono generowane domyślnie. Nadaliśmy naszemu widokowi TextView identyfikator „fruitName”, aby dopasować go do danych, które zostaną z nim powiązane.
Dodanie RecyclerView do głównego układu
W tej samej aktywności znajduje się plik układu main_layout.xml, który został dla nas wygenerowany domyślnie. Jeśli wybierzemy pusty projekt, zostanie wygenerowany plik XML Zawierający ConstraintLayout, a w środku będzie TextView z tekstem „Hello”. Usuniemy całą zawartość i pozostawimy układ zawierający tylko RecyclerView, jak poniżej:
<?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" />
Dodaliśmy również atrybut id dla RecyclerView, którego będziemy używać do odwoływania się do niego w naszym kodzie.
android:id="@+id/fruitRecyclerView"
Następnie wrócimy do pliku MainActivity. Korzystając z utworzonych identyfikatorów, będziemy mogli odwoływać się do właśnie utworzonych widoków. Zaczniemy od odwołania się do RecyclerView za pomocą metody findViewById() dostarczonej przez AndroidZrobimy to w naszej metodzie onCreate(). Nasza metoda onCreate() będzie wyglądać następująco.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView) }
Utwórz ViewHolder
Następnie utworzymy obiekt RecyclerView.ViewHolder, który odpowiada za przechwycenie naszego widoku i przekonwertowanie go na obiekt ViewHolder, którego RecyclerView używa do wyświetlania naszych elementów. Zrobimy to zaraz po metodzie 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) }
Utwórz element RecyclerViewAdapter
Następnie utworzymy klasę FruitArrayAdapter, która rozszerza klasę RecyclerView.Adapter. Utworzony przez nas FruitArrayAdapter będzie odpowiedzialny za następujące zadania: pobierze nazwy owoców z tablicy owoców, utworzy ViewHolder za pomocą naszego widoku one_fruit_view.xml, a następnie powiąże owoc z ViewHolder i dynamicznie powiąże zawartość z utworzonym przez nas widokiem.
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 doda czerwone zawijasy do naszego FruitArrayAdapter, informując nas, że musimy zaimplementować metody, których RecyclerView może użyć do połączenia naszej tablicy z 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) { } }
Zaczniemy od najłatwiejszego fragmentu wygenerowanego kodu, metody getItemCount(). Wiemy, jak uzyskać liczbę elementów w naszej tablicy, wywołując właściwość rozmiaru tablicy.
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) { } }
Następnie zaimplementujemy metodę onCreateViewHolder. W tym miejscu RecyclerView prosi nas o pomoc w skonstruowaniu FruitViewHolder. Jeśli dobrze pamiętam, tak wyglądała nasza klasa FruitViewHolder:
class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)
Wymaga naszego widoku fruitView, który utworzyliśmy jako plik XML one_fruit_view.xml. Możemy utworzyć odwołanie do tego pliku XML i przekonwertować go na widok w następujący sposób.
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) { } }
Pozostały bit jest nadpisaniem onBindViewHolder.
fun onBindViewHolder(holder: FruitViewHolder, position: Int)
RecyclerView.Adapter odpytuje o liczbę całkowitą pozycji, której używamy do pobrania elementu z naszej listy. Zapewnia nam również uchwyt, dzięki któremu możemy powiązać element pobrany z tablicy fruitArray z widokiem przechowywanym w uchwycie widoku. Widok przechowywany w ViewHolder jest dostępny poprzez pole ViewHolder.itemView. Po pobraniu widoku możemy użyć identyfikatora fruitName utworzonego wcześniej, aby ustawić jego zawartość.
override fun onBindViewHolder(holder: FruitViewHolder, position: Int) { var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName) var aFruitName = fruitArray.get(position) ourFruitTextView.setText(aFruitName) }
Dzięki temu nasz FruitArrayAdapter jest kompletny i wygląda następująco.
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) } }
Na koniec jesteśmy gotowi połączyć pozostałe części RecyclerView, czyli utworzyć LayoutManager, który wskaże RecyclerView sposób wyświetlania zawartości listy, czy ma być ona wyświetlana liniowo za pomocą LinearLayoutManager, czy w siatce za pomocą GridLayoutManager lub StaggeredGridLayoutManager.
Utwórz Menedżera układu
Wrócimy do funkcji onCreate i dodamy 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 }
Podłącz nasz adapter do przedmiotów i ustaw go na 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 }
Stworzyliśmy również instancję fruitListAdapter i przekazaliśmy jej tablicę nazw owoców. I w zasadzie wszystko gotowe. Kompletny plik MainActivity.kt wygląda następująco.
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) } } }




