Android RecyclerView: Какво е, научете с прости примери
⚡ Умно обобщение
RecyclerView е усъвършенстван Android Уиджет, който показва ефективно големи, превъртаеми набори от данни, като рециклира ограничен брой изгледи. Той разчита на адаптер, мениджър на оформлението и притежател на изгледи, за да обвърже колекции от данни, които се променят по време на изпълнение.

В какво е RecyclerView Android?
- RecyclerView е по-гъвкава и усъвършенствана версия на GridView и ListView. Това е контейнер за показване на големи набори от данни, който може да се превърта ефективно, като се поддържа ограничен брой изгледи. Можете да използвате уиджета RecyclerView, когато имате колекции от данни, чиито елементи се променят по време на изпълнение в зависимост от мрежово събитие или действие на потребителя.
Прегледи
- Android Платформата използва класовете View и ViewGroup за рисуване на елементи на екрана. Тези класове са ABStract и са разширени до различни имплементации, за да отговарят на случая на употреба. TextView, например, има простата цел да показва текстово съдържание на екрана. EditText се разпростира от същия клас View и добавя повече функционалност, за да позволи на потребителя да въвежда данни.
Възможно е да създаваме собствени персонализирани изгледи, за да постигнем по-голяма гъвкавост при разработването.ping потребителски интерфейси. Класът View предоставя методи, които можем да презапишем, за да рисуваме на екрана, и начин за подаване на параметри като ширина, височина и наши собствени персонализирани атрибути, които бихме искали да добавим към нашия View, за да го накараме да се държи както желаем.
ViewGroups
Класът ViewGroup е вид View, но за разлика от простия клас View, чиято отговорност е просто да показва, ViewGroup ни дава възможност да поставяме множество изгледи в един изглед, към който можем да се позоваваме като цяло. В този случай, изгледът, създаден на най-горното ниво, към който добавяме други прости изгледи, се нарича „родител“, а изгледите, които се добавят вътре, са „деца“.
Можем да си представим View като масив и ViewGroup като масив от масиви. Като се има предвид, че масивът от масиви е сам по себе си масив, можем да видим как ViewGroup може да се третира като View.
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
ViewGroup ни позволява също да дефинираме как са организирани децата в изгледа, например дали са разположени вертикално или хоризонтално. Можем да имаме различни правила за взаимодействие вътре в изгледа. Например, TextView-ите, следващи един след друг, трябва да имат разстояние от 12dp, докато ImageView, следван от TextView, трябва да имат разстояние от 5dp.
Това би било така, ако бяхме развилиping наша собствена ViewGroup от нулата. За да улесним тези конфигурации, Android предоставя клас, наречен LayoutParams, който можем да използваме за въвеждане на тези конфигурации.
- Android документация предоставя някои параметри по подразбиране, които бихме имплементирали при конфигурирането на нашата собствена ViewGroup. Някои често срещани параметри се отнасят до ширината, височината и полето. По подразбиране тези конфигурации имат структурата android:layout_height за височина и android:layout_width за ширина. В тази връзка, когато създавате вашата ViewGroup, можете допълнително да създадете LayoutParams, специфични за начина, по който искате тя да се държи.
Android идва с подразбиращи се изгледи (Views) и групи изгледи (ViewGroups), които можем да използваме за изпълнение на много от често срещаните задачи, от които се нуждаем. Един пример, който споменахме, е TextView. Това е прост изглед, който се предлага с конфигурируеми аспекти като височина, ширина и размер на текста (textSize). Също така имаме ImageView за показване на изображения и EditText, както споменахме, наред с много други. Android също има персонализирани ViewGroups, към които можем да добавим нашите Views и да получим очакваното поведение.
LinearLayout
LinearLayout ни позволява да добавяме елементи на View в него. LinearLayout има атрибут за ориентация, който определя как ще бъде разположен на екрана. Също така има LinearLayout.LayoutParams, които диктуват правилата за изгледите вътре. Например, атрибутът android:center_horizontal ще центрира изгледите по хоризонталната ос, докато android:center_vertical ще центрира съдържанието на изгледа по вертикалната ос.
Ето няколко изображения, които ще ви помогнат да разберете центрирането. Приемаме това като обикновен TextView в пространство от 200px на 200px; атрибутите за центриране биха го накарали да се държи по следния начин.
android:center_horizontal
Хоризонтално центрирано съдържание
android:center_vertical
Вертикално центрирано съдържание
android: център
Центрирано съдържание
Основни компоненти на RecyclerView
Основни компоненти на RecyclerView
Следват важните компоненти на RecyclerView:
RecyclerView.Адаптер
Основа #1 – Модел на адаптер
Адаптерът е устройство, което трансформира атрибутите на една система или устройство в тези на иначе несъвместимо устройство или система. Някои адаптери променят атрибутите на сигнала или захранването, докато други просто адаптират физическата форма на един конектор към друг.
Един прост пример от реалния живот, който обяснява използването на адаптер, е когато трябва да свържем устройства, но те имат несъвместими портове за свързване. Това може да се случи, когато посетите друга държава, която използва различни видове контакти. Ако носите зарядно за телефон или лаптоп, би било невъзможно да го свържете към електрическите контакти. Въпреки това, не бихте се отказали, а просто бихте си взели адаптер, който се поставя между електрическия контакт и зарядното устройство и позволява зареждането.
Такъв е случаят в програмирането, когато искаме да свържем две структури от данни, за да изпълним задача, но техните портове по подразбиране нямат начин да комуникират помежду си.
Ще използваме простия пример с устройство и зарядно устройство. Ще имаме два примера на зарядни устройства – американско и британско.
class AmericanCharger() { var chargingPower = 10 } class BritishCharger(){ var chargingPower = 5 }
След това ще създадем две устройства.
class AmericanDevice() class BritishDevice()
Като пример, след това можем да създадем някои екземпляри на устройствата за игра.
var myAmericanPhone = new AmericanDevice() var myBritishPhone = new BritishDevice()
След това ще въведем концепцията за зареждане и за двете устройства, като добавим метод в устройствата, наречен charge(). Методът приема съответното зарядно устройство и извършва зареждане въз основа на него.
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 } }
В този случай, въз основа на нашата аналогия, по една или друга причина бихме се нуждаели от британско зарядно устройство, когато използваме американско устройство, или обратното.
В света на програмирането това обикновено е при смесване на библиотеки заедно, които предлагат една и съща функционалност (в нашия контекст нашата споделена функционалност се таксува). Ще трябва да намерим начин да активираме това.
Ако следваме аналогията, ще трябва да отидем в магазин за електроника и да купим адаптер, който ни позволява да зареждаме AmericanDevices, дадени от BritishChargers. От гледна точка на програмирането, ние ще бъдем производителят на адаптера.
Ще създадем адаптер, който съответства на точния шаблон, който ще ни е необходим за създаването на другия. Ще го имплементираме като клас, както следва. Не е задължително да е клас, а може да бъде функция, която подчертава какво прави шаблонът на адаптера. Използваме клас, тъй като той съответства на повечето приложения в... 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 } }
В света на програмирането, разликата в контактите е аналогична на разликата в методите, използвани за зареждане. Зарядните устройства с различни методи биха направили невъзможно използването им.
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger()
Опитът за извикване на метода charge() на myBritishDevice с americanChargerIFound няма да проработи, тъй като AmericanDevice приема само AmericanCharger. Така че е невъзможно да се направи това:
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger() myBritishDevice.charge(americanChargerIFound)
В този сценарий, адаптерът, който създадохме, AmericanToBritishChargerAdapter, вече може да ни бъде полезен. Можем да използваме метода returnNewCharger(), за да създадем нов BritishCharger, който да използваме за зареждане. Всичко, което трябва да направим, е да създадем екземпляр на нашия адаптер и да му подадем AmericanCharger, който имаме, и той ще създаде BritishCharger, който можем да използваме.
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
Когато работим с ViewGroup, имаме Views, разположени вътре в нея. LayoutManager има задачата да опише как Views са разположени вътре.
За сравнение, когато работим с LinearLayout ViewGroup, случаят на употреба, който искаме, е възможността да разполагаме елементите вертикално или хоризонтално. Това се реализира лесно чрез добавяне на атрибут за ориентация, който ни казва как LinearLayout ще бъде разположен на екрана. Можем да направим това, като използваме android:orientation=VERTICAL|HORIZONTAL атрибут.
Имаме и друга ViewGroup, наречена GridLayout. Нейният случай на употреба е, когато искаме да разположим Views в правоъгълна мрежова структура. Това може да се дължи на причини, като например улесняване на консумацията на данните, които представяме на потребителя на приложението. По дизайн GridLayout позволява конфигурации, които да ви помогнат да постигнете тази цел, като дефинирате размерите на мрежата; например, можем да имаме мрежа 4×4 или 3×2.
RecyclerView.ViewHolder
ViewHolder е коремна мускулатураtract клас, който също разширяваме от RecyclerView. ViewHolder ни предоставя общи методи, които ни помагат да се позоваваме на View, който сме поставили върху RecyclerView, дори след като машината за рециклиране в RecyclerView е променила различни препратки, за които не знаем.
Големи списъци
RecyclerViews се използват, когато искаме да представим на потребителя наистина голям набор от Views, без да изчерпваме RAM на нашето устройство за всяко създадено копие на View.
Ако вземем за пример списък с контакти, ще имаме обща представа как би изглеждал един контакт в списъка. След това ще създадем шаблон за оформление, който всъщност е изглед (View), със слотове, където ще се запълват различни данни от нашия списък с контакти. Следва псевдокод, който обяснява цялата цел:
//OneContactView
<OneContact>
<TextView>{{PlaceHolderForName}}</TextView>
<TextView>{{PlaceHolderForAddress}}</TextView>
<ImageView>{{PlaceHolderForProfilePicture}}</ImageView>
<TextView>{{PlaceHolderForPhoneNumber}}</TextView>
</OneContact>
Тогава ще имаме списък с контакти от този вид:
<ContactList> </ContactList>
Ако бяхме кодирали съдържанието твърдо, нямаше да имаме програмен начин за добавяне на ново съдържание към списъка, без да пренаписваме приложението. За щастие за нас, добавянето на View към ViewGroup се поддържа от addView(view:View) метод. Въпреки това, това не е начинът, по който RecyclerView добавя дъщерни изгледи към него.
В нашия случай ще имаме дълъг списък с контакти. За всеки контакт в списъка ще трябва да създадем OneContactView и да попълним данните във View, за да съответстват на полетата в нашия клас Contact. След като имаме изгледа, ще трябва да го добавим към RecyclerView, за да покажем списъка.
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)
Имаме масив от контакти. OneContactView съдържа слотове за вземане на съдържание от класа Contact и показването му. В RecyclerView трябва да добавим Views към него, за да ни помогне с възможността му за рециклиране.
RecyclerView всъщност не ни позволява да добавим изглед, но ни дава възможност да добавим ViewHolder. Така че, в този сценарий имаме две части, които искаме да свържем, но които не съвпадат. Тук се намесва нашият адаптер. RecyclerView ни предоставя адаптер, подобно на нашия AmericanToBritishChargerAdapter() от по-рано, което ни позволи да превърнем нашето AmericanCharger, което беше неизползваемо с нашето BritishDevice, в нещо използваемо, подобно на захранващ адаптер в реалния живот.
В този сценарий, адаптерът ще вземе нашия масив от контакти и нашия View, и оттам ще генерира ViewHolders, които RecyclerView е готов да приеме.
RecyclerView предоставя интерфейс, който можем да разширим, за да създадем нашия адаптер чрез класа RecyclerView.Adapter. Вътре в този адаптер има начин за създаване на класа ViewHolder, с който RecyclerView иска да работи. Така че, това, което имаме, е същата ситуация като преди, но с едно допълнително нещо - адаптера.
Имаме масив от контакти, изглед за показване на един контакт (OneContactView) и RecyclerView, който е списък от изгледи (Views), предоставящ услуги за рециклиране, но е готов да приема само ViewHolders. В този сценарий вече имаме клас RecyclerView.Adapter, който има метод за създаване на ViewHolders вътре.
fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder
RecyclerView.ViewHolder е ABStract клас, който приема нашия View като аргумент и го преобразува във ViewHolder. Той използва шаблона за обвивка, който се използва за разширяване на възможностите на класовете.
Основа #2 – Обвивка
Ще използваме един прост пример, за да покажем как можем да накараме животните да говорят.
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
В горния пример имаме две животни. Ако случайно поискаме да добавим метод, който да ги накара да говорят, но авторът на библиотеката не беше забавен, пак бихме могли да намерим начин. Това, от което ще се нуждаем, е обвивка за нашия клас Animal. Ще направим това, като приемем Animal като конструктор за нашия клас.
class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } fun speak(){ println("Hello, my name is ${myAnimal.name}") } }
Сега можем да предадем екземпляр на животно към SpeechPoweredAnimalByWrapper. Извикването на метода sound() върху него ще извика предадения метод animal sound(). Имаме и допълнителен метод speak(), който се брои като нова функционалност, която добавяме към предадените животни. Можем да го използваме, както следва:
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"
Използвайки този шаблон, можем да вземаме класове и да добавяме функционалност. Всичко, от което се нуждаем, е да предадем екземпляр на клас и нови методи, дефинирани от нашия wrap.ping клас.
В горния случай използвахме клас concrete. Възможно е също така да се имплементира същият в ABS.tract клас. Ще трябва да променим класа SpeechPoweredAnimalByWrapper на abstract и сме готови. Ще променим името на класа на нещо по-кратко, за да го направим по-четливо.
abstract class SpeechPowered(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } fun speak(){ println("Hello, my name is ${myAnimal.name}") } }
Същото е като преди, но би означавало нещо друго. В нормален клас можем да имаме екземпляр на клас по същия начин, по който създадохме cat1 и dog1. AbstracКласовете t обаче не са предназначени за създаване на инстанции, а за разширяване на други класове. И така, как бихме използвали новия SpeechPowered(var myAnimal:Animal) abstract клас? Можем да го използваме, като създадем нови класове, които ще го разширят и от своя страна ще получат неговата функционалност.
В нашия пример ще създадем клас SpeechPoweredAnimal, който разширява класа.
class SpeechPoweredAnimal(var myAnimal:Animal):SpeechPowered(myAnimal)
var cat1 = Cat("Tubby") var speakingKitty = SpeechPoweredAnimal(cat1) speakingKitty.speak() //"Hello, my name is Tubby"
Това е същият шаблон, използван във ViewHolder. Класът RecyclerView.ViewHolder е ABStract клас, който добавя функционалност към View, подобно на това, както добавихме метода speak към животните. Добавената функционалност е това, което го прави работещ при работа с RecyclerView.
Ето как бихме създали OneContactViewHolder от 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 има адаптер, който ни позволява да свържем нашия масив от контакти към ContactsView с RecyclerView.
Добавяне на изглед
ViewGroup не се прерисува автоматично, а следва определен график. Възможно е на вашето устройство да се прерисува на всеки 10ms или 100ms; или, ако изберем абсурдно число, да речем 1 минута, когато добавим View към ViewGroup, ще видите промените 1 минута по-късно, когато ViewGroup се „обнови“.
RecyclerView.Recycler
Основа #3 – Кеширане
Един от най-добрите примери за това къде редовно правим обновявания е в браузъра. Нека си представим например, че сайтът, който посещаваме, е статичен и не изпраща динамично съдържание; ще трябва да обновяваме страницата, за да видим промените.
За този пример, нека си представим, че въпросният сайт е Twitter. Ще имаме поредица от статични туитове, изброени в списъка, и единственият начин да видим нови туитове е чрез кликване върху бутона за обновяване, за да извлечем отново съдържанието.
Прерисуването на целия екран очевидно е скъпо нещо. Представете си, че имаме ограничена честотна лента с нашия телефонен оператор и списъкът ни с туитове съдържа много изображения и видеоклипове; би било скъпо да се изтегля цялото съдържание на страницата при всяко обновяване.
Ще ни е нужен начин да съхраняваме вече заредените туитове и да гарантираме, че следващата ни заявка ще може да каже кои туитове вече има. Следователно, системата не изтегля отново всичко, а получава само новите туитове, а също така проверява дали някой туит, който е бил запазен локално, вече не е налице, за да може да го изтрие локално. Това, което описваме, се нарича кеширане.
Информацията, която изпращаме до уебсайта за съдържанието, с което разполагаме, се нарича метаданни. Така че в истинския смисъл ние не просто казваме „искаме да заредим вашия сайт“, а казваме „искаме да заредим вашия сайт и ето част от съдържанието, което вече сме запазили от последното зареждане; моля, използвайте го, за да ни изпратите само това, което не е там, за да не използваме много трафик“.
Обаждания за оформление – Списъкът с туитове трябва да е луд
Пример за извикване на оформление е scrollToPosition. Това е често срещан пример, присъстващ в неща като приложения за чат. Ако някой в чат нишката отговори на балонче за чат от по-рано, някои приложения за чат включват отговора и връзка към балончето за чат, която при щракване ви навигира до мястото, където е било първоначалното ви съобщение.
В случай че извикаме този метод, преди да добавим LayoutManager към нашия RecyclerView и преди да имаме RecyclerView.Adapter, scrollToPosition(n:Int) просто се игнорира.
Комуникация между компонентите на RecyclerView
Основа #4 – Обратни повиквания
В работата си RecyclerView има много движещи се части. Той трябва да взаимодейства с LayoutManager, който ни казва как да организираме View-ите (Views), линейно или в мрежа. Той трябва да взаимодейства с адаптер, който извършва преобразуването на нашите елементи (contactList) в Views (OneContactView) и след това във ViewHolders (OneContactViewHolder), с които RecyclerView е готов да работи, използвайки методите, които предоставя.
Суровината за RecyclerView са нашите изгледи, например OneContactView, и източник на данни.
contactList:Array<Contact>
Използвахме един прост сценарий като отправна точка, за да добием представа какво се опитва да постигне RecyclerView. Лесен за разбиране е базовият случай, когато имаме статичен масив от 1000 контакта, които искаме да покажем на потребителя. Механизмът на RecyclerView наистина започва да оживява, когато списъкът вече не е статичен. При динамичен списък трябва да помислим какво се случва с View на екрана, когато добавим елемент към списъка или премахнем елемент от него.
RecyclerView.LayoutManager
Освен че решава как да бъдат разположени нашите изгледи, линейно или в мрежа, LayoutManager върши много работа „под капака“ в hel.ping Рециклиращият знае кога да извърши рециклирането.
Той е отговорен за поддържанетоping track от изгледите, видими в момента на екрана, и съобщава тази информация на механизма за рециклиране. Докато потребителят превърта надолу, LayoutManager е отговорен за информирането на системата за рециклиране за изгледите, които излизат от фокус в горната част, така че да могат да бъдат използвани повторно, вместо да останат там и да изразходват памет или да бъдат унищожени само за да се създадат нови.
Това означава, че LayoutManager трябва да поддържа track на мястото, където се намира потребителят, докато превърта нашия списък. Това се постига чрез списък с позиции, базиран на индекси, т.е. първият елемент започва от 0 и се увеличава, за да съответства на броя на елементите в нашия списък.
Ако можем да видим 10 елемента от нашия списък, да речем, със 100, в началото LayoutManager е наясно, че е във фокус от View-0 до View-9. Докато превъртаме, LayoutManager може да изчисли изгледите, които излизат от фокус.
LayoutManager може да освободи тези изгледи към механизма за рециклиране, така че да могат да бъдат използвани повторно (нови данни могат да бъдат свързани с тях; например данните за контакт на изглед могат да бъдат премахнати и нови данни за контакт от следващия сегмент могат да заменят заместителите).
Това би бил щастлив случай, ако списъкът, който имаме, е статичен, но един от най-често срещаните случаи на употреба на RecyclerView е с динамични списъци, където данните могат да идват от онлайн крайна точка или дори от сензор. Не само се добавят данни, но понякога данните в нашия списък се премахват или актуализират.
Динамичното състояние на нашите данни може да затрудни много разсъжденията относно LayoutManager. Поради тази причина LayoutManager поддържа собствен списък с елементите и позициите, отделен от списъка, използван от компонента за рециклиране. Това гарантира, че изпълнява правилно задачата си за оформление.
В същото време, LayoutManager на RecyclerView не иска да представя погрешно данните, с които разполага. За да работи правилно, LayoutManager се синхронизира с RecyclerView.Adapter на зададени интервали (60ms), споделяйки информация за елементите от нашия списък, т.е. елементи, добавени, актуализирани, премахнати или преместени от една позиция на друга. След получаване на тази информация, LayoutManager реорганизира съдържанието на екрана, за да съответства на промените, когато е необходимо.
Много от основните операции, които работят с RecyclerView, се въртят около комуникацията между RecyclerView.LayoutManager и RecyclerView.Adapter, които съхраняват нашите списъци с понякога статични, а понякога динамични данни.
В допълнение, RecyclerView ни предоставя методи, които можем да използваме за слушане на събития, като например onBindViewHolder, когато нашият RecyclerView.Adapter свързва съдържание от нашия списък (например контакт) с ViewHolder, така че той да се използва за показване на информацията на екрана.
Друг е onCreateViewHolder, който ни казва кога RecyclerView.Adapter приема обикновен View като OneContactView и го преобразува в елемент ViewHolder, с който RecyclerView може да работи.
Освен основните механизми, които позволяват рециклирането, RecyclerView предоставя начини за персонализиране на поведението, без това да засяга рециклирането. Повторното използване на изгледи затруднява извършването на обичайни неща, които сме свикнали да правим със статични изгледи, като например реагиране на събития onClick.
Както знаем, RecyclerView.LayoutManager който представя изгледите на потребителя може за момент да има различен списък от елементи от RecyclerView.Adapter който съхранява списъка в база данни или го предава поточно от източник. Поставянето на събития OnClick директно върху Views може да доведе до неочаквано поведение, като например изтриване на грешен контакт.
Gradle
Ако искаме да използваме RecyclerView, трябва да го добавим като зависимост в нашия build.gradle файл. В следващия пример сме използвали имплементация „androidx.recyclerview:recyclerview:1.1.0“, която е най-актуалната версия според тази статия.
След добавяне на зависимостта към нашия Gradle файл, ще бъдем подканени от Android Studio да синхронизира промените. Ето как нашите Gradle Файлът ще се погрижи за добавянето на RecyclerView в празен проект само с настройките по подразбиране.
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" }
В момента имаме само един файл с оформлението. Ще започнем с прост пример, в който използваме RecyclerView, за да покажем списък с имена на плодове на екрана.
Списък на елементите
Ще отидем до нашия файл MainActivity и ще създадем масив с имена на плодове вътре, точно преди метода onCreate(), който беше генериран по време на настройката.
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) } }
Следващата ни цел ще бъде да представим този списък на екрана, използвайки RecyclerView. За да направим това, ще отидем в директорията с оформленията, която съдържа нашите оформления, и ще създадем View, който ще отговаря за показването на един плод.
Оформление, което да се използва за всеки елемент в нашия списък
<?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>
В TextView по-горе добавихме поле за id, което ще се използва за идентифициране на View. То не се генерира по подразбиране. Дадохме на нашия TextView id fruitName, за да съответства на данните, които ще бъдат обвързани с него.
Добавяне на RecyclerView към основното оформление
В същата дейност има файл с оформление main_layout.xml, който е генериран за нас по подразбиране. Ако изберем празен проект, той ще е генерирал XML съдържащ ConstraintLayout и вътре ще бъде TextView с текст „Hello“. Ще изтрием цялото съдържание и ще оставим оформлението да съдържа само RecyclerView, както е показано по-долу:
<?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" />
Добавихме и атрибут id за RecyclerView, който ще използваме, за да го препращаме в нашия код.
android:id="@+id/fruitRecyclerView"
След това ще се върнем към нашия файл MainActivity. Използвайки създадените от нас идентификатори, ще можем да се позоваваме на View-ите, които току-що създадохме. Ще започнем с позоваване на RecyclerView, използвайки метода findViewById(), предоставен от AndroidЩе направим това в нашия метод onCreate(). Нашият метод onCreate() ще изглежда по следния начин.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView) }
Създайте ViewHolder
След това ще създадем RecyclerView.ViewHolder, който е отговорен за приемането на нашия View и преобразуването му във ViewHolder, който RecyclerView използва за показване на нашите елементи. Ще направим това веднага след нашия метод 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) }
Създайте RecyclerViewAdapter
След това ще създадем клас FruitArrayAdapter, който разширява класа RecyclerView.Adapter. FruitArrayAdapter, който създаваме, ще отговаря за следното: ще взема имената на плодовете от масива с плодове, ще създава ViewHolder, използвайки нашия изглед one_fruit_view.xml, след което ще обвърже плода с ViewHolder и динамично ще обвърже съдържанието със създадения от нас изглед.
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 ще добави червени вълнообразни линии към нашия FruitArrayAdapter, които ни казват, че трябва да имплементираме методи, които RecyclerView може да използва, за да свърже нашия масив с 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) { } }
Ще започнем с най-лесната част от генерирания код, методът getItemCount(). Знаем как да получим броя на елементите в нашия масив, като извикаме свойството array size.
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) { } }
След това ще имплементираме метода onCreateViewHolder. Тук RecyclerView ни моли да му помогнем да конструира FruitViewHolder. Ако си спомняме, ето как изглеждаше нашият клас FruitViewHolder:
class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)
Това изисква нашия fruitView, който създадохме като XML файл one_fruit_view.xml. Можем да създадем препратка към този XML и да я конвертираме в View, както следва.
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) { } }
Оставащият бит е презаписването на onBindViewHolder.
fun onBindViewHolder(holder: FruitViewHolder, position: Int)
RecyclerView.Adapter пита с цяло число за позиция, което използваме за извличане на елемент от нашия списък. Той също така ни предоставя holder, така че да можем да свържем елемента, който получаваме от fruitArray, с View, който се съхранява вътре в holder-а на view. View, съхраняван вътре в ViewHolder, е достъпен чрез полето ViewHolder.itemView. След като получим view-а, можем да използваме идентификатора fruitName, който създадохме по-рано, за да зададем съдържанието.
override fun onBindViewHolder(holder: FruitViewHolder, position: Int) { var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName) var aFruitName = fruitArray.get(position) ourFruitTextView.setText(aFruitName) }
С това нашият FruitArrayAdapter е завършен и изглежда по следния начин.
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) } }
Накрая сме готови да свържем останалите части на нашия RecyclerView, които създават LayoutManager, който ще каже на RecyclerView как да показва съдържанието на списъка, дали да го показва линейно, използвайки LinearLayoutManager, или в мрежа, използвайки GridLayoutManager или StaggeredGridLayoutManager.
Създаване на мениджър на оформление
Ще се върнем във функцията onCreate и ще добавим 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 }
Закачете нашия адаптер към елементи и го настройте на 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 }
Създадохме и екземпляр на fruitListAdapter и му зададохме масива с имена на плодове. И на практика сме готови. Пълният файл MainActivity.kt изглежда по следния начин.
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) } } }




