Android RecyclerView: что такое, учитесь на простых примерах
Что такое RecyclerView Android?
Территория RecyclerView это виджет, который является более гибкой и продвинутой версией GridView и ListView. Это контейнер для отображения больших наборов данных, которые можно эффективно прокручивать, сохраняя ограниченное количество представлений. Вы можете использовать виджет RecyclerView, если у вас есть коллекции данных, элементы которых изменяются во время выполнения в зависимости от сетевых событий или действий пользователя.
Виды
Территория Android Платформа использует классы View и ViewGroup для рисования элементов на экране. Эти классы являются абстрактными и расширяются до различных реализаций в зависимости от варианта использования. TextView, например, имеет простую цель — отображать текстовое содержимое на экране. EditText расширяет тот же класс View и добавляет дополнительные функциональные возможности, позволяющие пользователю вводить данные.
Можно создавать собственные представления, чтобы добиться большей гибкости при разработке пользовательских интерфейсов. Класс View предоставляет методы, которые мы можем переопределить для рисования на экране, а также средства для передачи таких параметров, как ширина, высота и наши собственные атрибуты, которые мы хотели бы добавить в наше представление, чтобы оно вел себя так, как нам хотелось бы.
Группы просмотра
Класс ViewGroup — это разновидность View, но, в отличие от простого класса View, ответственность которого заключается в простом отображении, ViewGroup дает нам возможность объединять несколько представлений в одно представление, на которое мы можем ссылаться как единое целое. В этом случае представление, созданное на верхнем уровне, к которому мы добавляем другие простые представления (мы также можем добавлять группы представлений), называется «родительским», а представления, добавляемые внутрь, — «дочерними».
Мы можем представить представление как массив и группу представлений как массив массивов. Учитывая, что массив массивов сам по себе является массивом, мы можем увидеть, как ViewGroup можно рассматривать как представление.
var arr1 = [1,2,3] //imagine a simple View as an Array //we can imagine this as a NumberTextView which doesn't really exist //but we could imagine there's one that makes it easy to use numbers var arr2 = ["a","b","c"] // We can imagine this as another simple view var nestedArr = [arr1,arr2] //in our anology, we can now group views //together and the structure that would hold that would be what we call the ViewGroup
ViewGroup также позволяет нам определить, как дочерние элементы организованы внутри представления, например, расположены вертикально или горизонтально. Внутри представления у нас могут быть разные правила взаимодействия. Например, TextViews, следующие друг за другом, должны иметь расстояние 12dp, а ImageViews, за которыми следует TextView, должно иметь расстояние 5dp.
Это было бы так, если бы мы разрабатывали собственную ViewGroup с нуля. Чтобы упростить эти настройки, Android предоставляет класс LayoutParams, который мы можем использовать для ввода этих конфигураций.
Android документации предоставляет некоторые параметры по умолчанию, которые мы бы реализовали при настройке нашей собственной ViewGroup. Некоторые общие параметры относятся к ширине, высоте и полям. По умолчанию эти конфигурации имеют структуру android:layout_height для высоты, например android:layout_width. В этом отношении, когда вы создаете свою ViewGroup, вы можете дополнительно создать LayoutParams, специфичные для того, как вы хотите, чтобы ваша ViewGroup вела себя.
Android поставляется со стандартными представлениями и группами представлений, которые мы можем использовать для выполнения множества общих задач, которые нам нужны. Одним из примеров, который мы упомянули, является TextView. Это простое представление с настраиваемыми параметрами, такими как высота, ширина, размер текста и тому подобное. У нас есть ImageView для отображения изображений и EditText, как мы уже упоминали, среди многих других. Android также есть пользовательские группы представлений, в которые мы можем добавлять наши представления и получать ожидаемое поведение.
Линейный макет
LinearLayout позволяет нам добавлять в него элементы View. LinearLayout — это атрибут ориентации, который определяет, как он будет расположен на экране. Он также имеет LinearLayout.LayoutParams, которые определяют правила для представлений внутри, например, атрибут android:center_horizontal центрирует представления по горизонтальной оси, тогда как `android:center_vertical центрирует содержимое представления по вертикальной оси.
Вот несколько изображений, чтобы иметь представление о центрировании. Мы бы предположили, что это простой TextView внутри пространства размером 200 на 200 пикселей, атрибуты центрирования заставят его вести себя следующим образом.
Android:center_horizontal
Android:center_vertical
андроид: центр
Основные компоненты RecyclerView
Ниже приведены важные компоненты RecyclerView:
RecyclerView.Адаптер
Основа №1 – Шаблон адаптера
Адаптер — это устройство, которое преобразует атрибуты системы или устройства в атрибуты несовместимого в противном случае устройства или системы. Некоторые из них изменяют атрибуты сигнала или мощности, а другие просто адаптируют физическую форму одного разъема к другому.
Простой пример, который мы находим в реальной жизни для объяснения адаптера, — это когда нам нужно соединить устройства вместе, но у них есть порты подключения, которые не совпадают друг с другом. Это может быть тот случай, когда вы посещаете другую страну, где используются разные типы розеток. Если вы носите с собой зарядное устройство для телефона или ноутбука, подключить его к розеткам будет невозможно. Однако вы не сдадитесь, а просто купите адаптер, который будет вставляться между розеткой и зарядным устройством и обеспечивать зарядку.
Это тот случай в программировании, когда мы хотим соединить две структуры данных вместе, чтобы выполнить задачу, но их порты по умолчанию не имеют возможности взаимодействовать друг с другом.
Мы будем использовать простой пример устройства и зарядного устройства. У нас будет два экземпляра зарядных устройств. Американский и британский
class AmericanCharger() { var chargingPower = 10 } class BritishCharger(){ var charginPower = 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 } }
В этом случае, исходя из нашей аналогии, нам по той или иной причине понадобится использовать BritishCharger при использовании AmericanDevice или наоборот.
В мире программирования это обычно происходит при смешивании библиотек, предлагающих одинаковую функциональность (в нашем контексте наша общая функциональность является платной). Нам нужно будет найти способ реализовать это.
Если мы последуем аналогии, нам нужно будет пойти в магазин электроники и купить адаптер, который позволит нам заряжать американские устройства с помощью британских зарядных устройств. С точки зрения программирования мы будем производителями адаптера.
Для одного из них мы создадим адаптер, который будет точно соответствовать шаблону, который нам понадобится для создания другого. Мы реализуем его как класс следующим образом. Это не обязательно должен быть класс, это может быть функция, которая подчеркивает, что обычно делает шаблон адаптера. Мы будем использовать класс, поскольку он наиболее часто используется в Android.
class AmericanToBritishChargerAdapter(theAmericanCharger:AmericanCharger){ fun returnNewCharger(): BritishCharger{ //convert the American charger to a BritishCharger //we would change the American charging functionality //to British charging functionality to make sure the //adapter doesn't destroy the device. The adapter could //, for example, control the power output by dividing by 2 //our adapter could encompass this functionality in here var charingPower:Int = charger.chargingPower / 2 var newBritishCharger = new BritishCharger() newBritishCharger.chargingPower = theAmericanCharger.chargingPower/2 return newBritishCharger } }
В мире программирования разница в сокетах аналогична разнице в методах внутри, используемых для зарядки. Зарядные устройства, имеющие разные методы, сделают невозможным их использование.
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(theAmericanCharger) //calling returnNewCharger from myAdapter would return a BritishCharger var britishChargerFromAdapter = myAdapter.returnNewCharger() //and once we have the britishCharger we can now use it myBritishDevice.charge(britishChargerFromAdapter)
RecyclerView.LayoutManager
Когда мы имеем дело с ViewGroup, внутри нее размещаются представления. Задача LayoutManager — описать, как представления расположены внутри.
Для сравнения: при работе с Linearlayout ViewGroup нам нужен вариант использования — возможность размещать элементы вертикально или горизонтально. Это легко реализовать, добавив атрибут ориентации, который сообщает нам, как линейный макет будет расположен на экране. Мы можем сделать это, используя android:orientation=VERTICAL|HORIZONTAL
атрибутов.
У нас также есть еще одна группа представлений под названием GridLayout, ее можно использовать, когда мы хотим разместить представления в прямоугольной структуре сетки. Это может быть вызвано такими причинами, как упрощение использования данных, которые мы представляем пользователю приложения. По своей конструкции GridLayout позволяет использовать конфигурации, которые помогут вам в достижении этой цели, имея конфигурации, в которых мы можем определить размеры сетки, например, мы можем иметь сетку 4 × 4, сетку 3 x 2.
RecyclerView.ViewHolder
ViewHolder — это абстрактный класс, который мы также расширяем из RecyclerView. ViewHolder предоставляет нам общие методы, помогающие нам ссылаться на представление, которое мы поместили в RecyclerView, даже после того, как механизм переработки в RecyclerView изменил различные ссылки, о которых мы не знаем.
Большие списки
RecyclerViews используются, когда мы хотим представить пользователю действительно большой набор представлений, при этом не исчерпывая наши возможности. Оперативная память на нашем устройстве для каждого созданного экземпляра представления.
Если бы мы взяли список контактов, у нас было бы общее представление о том, как будет выглядеть один контакт в списке. Затем мы создадим макет шаблона, который на самом деле является представлением, со слотами, в которые будут заполняться различные данные из нашего списка контактов. Ниже приведен псевдокод, объясняющий всю цель:
//OneContactView <OneContact> <TextView>{{PlaceHolderForName}}</TextView> <TextView>{{PlaceHolderForAddress}}</TextView> <ImageView>{{PlaceHolderForProfilePicture}}</ImageView> <TextView>{{PlaceHolderForPhoneNumber}}</TextView> </OneContact>
Тогда у нас будет список контактов такого типа.
<ContactList> </ContactList>
Если бы это было так, мы бы жестко запрограммировали содержимое, и у нас не было бы программного способа добавления нового контента в список без переписывания приложения. К счастью для нас. Добавление представления в ViewGroup поддерживается addView(view:View)
метод.
Даже если это так, RecyclerView не добавляет к себе дочерние представления.
В нашем случае у нас будет длинный список контактов. Для каждого контакта в списке нам нужно будет создать OneContactView и заполнить данные внутри представления, чтобы они соответствовали полям в нашем классе контактов. Затем, когда у нас будет представление, нам нужно будет добавить его в 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 нам нужно добавить в него представления, чтобы он мог помочь нам с возможностью переработки.
RecyclerView на самом деле не позволяет нам добавлять представление, но позволяет нам добавлять ViewHolder. Итак, в этом сценарии у нас есть две вещи, которые мы хотим соединить, но не совпадают. Здесь на помощь приходит наш адаптер. RecyclerView предоставляет нам адаптер, очень похожий на наш AmericanToBritishChargerAdapter()
ранее это позволило нам преобразовать наше американское зарядное устройство, которое было непригодно для использования с нашим британским устройством, во что-то пригодное для использования, похожее на адаптер питания в реальной жизни.
В этом сценарии адаптер возьмет наш массив контактов и наше представление и оттуда сгенерирует ViewHolders, которые RecyclerView готов принять.
RecyclerView предоставляет интерфейс, который мы можем расширить для создания нашего адаптера через класс RecyclerView.Adapter. Внутри этого адаптера есть способ создать класс ViewHolder, с которым хочет работать RecyclerView. Итак, мы имеем ту же ситуацию, что и раньше, но с одной дополнительной вещью — адаптером.
У нас есть массив контактов, представление для отображения одного контакта OneContactView. RecyclerView — это список представлений, которые предоставляют услуги по переработке, но готовы принимать только ViewHolders.
Но в этом сценарии теперь у нас есть класс RecyclerView.Adapter, внутри которого есть метод для создания ViewHolders.
fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder
RecyclerView.ViewHolder — это абстрактный класс, который принимает наше представление в качестве аргумента и преобразует его в 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() } speak(){ println("Hello, my name is ${myAnimal.name}") } }
Теперь мы можем передать экземпляр животного в SpeechPoweredAnimalByWrapper. Вызов метода sound() вызовет переданный метод sound() животного. У нас также есть дополнительный метод talk(), который считается новой функциональностью, которую мы добавляем к переданным животным. Мы можем использовать его следующим образом:
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"
Используя этот шаблон, мы можем посещать занятия и добавлять функциональность. Все, что нам нужно, это передать экземпляр класса и новые методы, определенные нашим классом-оболочкой.
В нашем случае выше мы использовали конкретный класс. То же самое можно реализовать и в абстрактном классе. Нам нужно будет добавить изменение класса SpeechPoweredAnimalByWrapper на абстрактный, и все готово. Мы изменим имя класса на более короткое, чтобы сделать его более читабельным.
abstract class SpeechPowered(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } speak(){ println("Hello, my name is ${myAnimal.name}") } }
Это то же самое, что и раньше, но это будет означать что-то другое. В обычном классе мы можем иметь экземпляр класса так же, как мы создали cat1 и Dog1. Однако абстрактные классы не предназначены для создания экземпляров, а предназначены для расширения других классов. Итак, как бы мы использовали новый абстрактный класс SpeechPowered(var myAnimal:Animal). Мы можем использовать его, создавая новые классы, которые расширят его и, в свою очередь, приобретут его функциональность.
В нашем примере мы создадим класс 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 — это абстрактный класс, который добавляет функциональность представлению, подобно тому, как мы добавили метод talk к животным. Добавленная функциональность позволяет работать с 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 не перерисовывает ViewGroup автоматически, а следует определенному расписанию. На вашем устройстве может случиться так, что оно перерисовывается каждые 10 или 100 мс, или если мы выберем абсурдное число, скажем, 1 минуту, когда мы добавляем представление в ViewGroup, вы увидите изменения через 1 минуту, когда ViewGroup «обновится».
RecyclerView.Recycler
Основа №3. Кэширование
Один из лучших примеров того, где мы регулярно обновляем информацию, — это браузер. Предположим, например, что сайт, который мы посещаем, является статическим и не отправляет контент динамически, нам нужно будет постоянно обновлять его, чтобы увидеть изменения.
В этом примере давайте представим, что рассматриваемый сайт — Twitter. У нас будет список статических твитов, и единственный способ увидеть новые твиты — это нажать кнопку обновления, чтобы обновить контент.
Перерисовка всего экрана, очевидно, является дорогостоящим делом. Если представить, что это так, то у нашего телефонного провайдера была ограниченная пропускная способность. А в нашем списке твитов было много изображений и видео, поэтому повторно загружать все содержимое страницы при каждом обновлении было бы дорого.
Нам понадобится способ хранить уже загруженные твиты и гарантировать, что наш следующий запрос сможет произнести уже имеющиеся твиты. Таким образом, он не загружает все заново, а получает только новые твиты, которые у него есть, а также проверяет, нет ли какого-либо твита, который был сохранен локально, чтобы он мог удалить его локально. То, что мы описываем, называется кэшированием.
Информация, которую мы отправляем на веб-сайт о содержании, которое у нас есть, называется метаданными. Таким образом, в действительности мы не просто говорим «мы хотим загрузить ваш сайт», мы говорим «мы хотим загрузить ваш сайт, и вот часть содержимого, которое мы уже сохранили с момента последней загрузки, пожалуйста, используйте его, чтобы Отправляйте нам только то, чего там нет, чтобы мы не использовали большую пропускную способность, поскольку у нас не так много ресурсов».
Звонки по макету: список твитов, должно быть, сумасшедший
Примером вызова макета является ScrollToPosition.
Это распространенный пример, который присутствует в таких вещах, как приложения для чата. Если кто-то в ветке чата отвечает на всплывающее окно чата ранее, некоторые приложения для чата включают ответ и ссылку на всплывающее окно чата, при нажатии на которую вы перейдете туда, где было ваше исходное сообщение.
В этом случае мы вызываем этот метод до того, как к нашему RecyclerView будет добавлен LayoutManager, и до того, как у нас будет RecyclerView.Adapter, ScrollToPosition(n:Int) просто игнорируется.
Связь между компонентами RecyclerView
Основа №4. Обратные вызовы
RecyclerView при выполнении своей работы имеет множество движущихся частей. Он имеет дело с LayoutManager, который сообщает нам, как организовать представления: линейно или в виде сетки. Ему приходится иметь дело с адаптером, который выполняет работу по преобразованию наших элементов contactList в представления OneContactView, а затем в ViewHolders OneContactViewHolder, с которыми RecyclerView готов работать в рамках методов, которые он нам предоставляет.
Исходным материалом для RecyclerView являются наши представления, например OneContactView и источник данных.
contactList:Array<Contact>
Мы использовали простой сценарий в качестве отправной точки, чтобы понять, чего пытается достичь RecyclerView.
Базовый вариант, когда у нас будет статический массив из 1000 контактов, который мы хотим показать пользователю, легко понять.
Механизм RecyclerView действительно начинает оживать, когда список перестает быть статичным.
При использовании динамического списка нам приходится думать о том, что происходит с представлением на экране, когда мы добавляем элемент в список или удаляем его из списка.
RecyclerView.LayoutManager
Помимо решения о том, как наши представления должны быть расположены линейно или в сетке. LayoutManager выполняет большую скрытую работу, помогая переработчику узнать, когда следует выполнить переработку.
Он отвечает за отслеживание представлений, видимых в данный момент на экране, и передачу этой информации механизму переработки. Когда пользователь прокручивает вниз, менеджер макета отвечает за информирование системы переработки о представлениях, которые выходят из фокуса вверху, чтобы их можно было использовать повторно вместо того, чтобы оставаться там и потреблять память или вместо того, чтобы уничтожать их и создавать новые.
Это означает, что LayoutManager должен отслеживать, где находится пользователь, когда он прокручивает наш список. Это достигается за счет наличия списка позиций, которые являются индексной базой, т.е. первый элемент должен начинаться с 0 и увеличиваться, чтобы соответствовать количеству элементов в нашем списке.
Если мы можем просмотреть 10 элементов в нашем списке, скажем, 100, в начале LayoutManager знает, что в фокусе находится представление от 0 до самого представления-9. Когда мы прокручиваем, LayoutManager может вычислить представления, которые выходят. фокуса.
LayoutManager может передать эти представления механизму повторного использования, чтобы их можно было использовать повторно (к ним можно привязать новые данные, например, контактные данные представления могут быть удалены, а новые контактные данные из следующего сегмента могут заменить заполнители).
Это был бы счастливый случай, если бы список, который у нас есть, был статическим, но один из наиболее распространенных случаев использования RecyclerView — это динамические списки, где данные могут поступать из конечной точки в сети или даже, возможно, из датчика. Данные не только добавляются, но и данные в нашем Списке иногда удаляются или обновляются.
Динамическое состояние наших данных может сильно затруднить анализ LayoutManager. По этой причине LayoutManager поддерживает собственный список элементов и позиций, отдельный от списка, который использует компонент Recycling. Это гарантирует, что он правильно выполняет свою работу по макету.
В то же время LayoutManager RecyclerView не хочет искажать имеющиеся у него данные. Для правильной работы LayoutManager синхронизируется с RecyclerView.Adapter через заданные интервалы (60 мс), обмениваясь информацией об элементах нашего списка. т. е. элементы добавлены, обновлены, удалены, перемещены из одной позиции в другую). LayoutManager, получив эту информацию, реорганизует содержимое на экране, чтобы соответствовать изменениям, когда это необходимо.
Многие из основных операций, связанных с RecylerView, вращаются вокруг связи между RecyclerView.LayoutManager и RecyclerView.Adapter, которые хранят наши списки иногда статических, а иногда и динамических данных.
Более того, RecyclerView предоставляет нам методы, которые мы можем использовать для прослушивания таких событий, как onBindViewHolder, когда наш RecyclerView.Adapter связывает контент из нашего списка, например, контакт с ViewHolder, чтобы он теперь привыкал отображать информацию на экране.
Другой — onCreateViewHolder, который сообщает нам, когда адаптер RecyclerView.The принимает обычное представление, такое как OneContactView, и преобразует его в элемент ViewHolder, с которым может работать RecyclerView. Из нашего ViewHolder для использования RecyclerView. onViewDetached
Помимо основных механизмов, обеспечивающих переработку. RecyclerView предоставляет способы настройки поведения, не влияя на переработку.
Повторное использование представлений затрудняет выполнение обычных действий, которые мы привыкли делать со статическими представлениями, например реакцию на события onClick.
Поскольку мы знаем, что RecyclerView.LayoutManager
который представляет пользователю представления, может на мгновение иметь другой список элементов, чем RecyclerView.Adapter
у которого есть список, который мы сохранили в базе данных или получаем из источника. Размещение событий OnClick непосредственно в представлениях может привести к неожиданному поведению, например к удалению или изменению неправильного контакта.
Gradle
Если мы хотим использовать RecyclerView, нам нужно добавить его в качестве зависимости в наш файл сборки .gradle.
В следующем примере мы использовали реализацию «androidx.recyclerview:recyclerview:1.1.0», которая является самой последней версией согласно этой статье.
После добавления зависимости в наш Gradle файл, нам будет предложено Android Студия для Syncхронизировать изменения,
Вот как наш 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.
Для этого мы перейдем в каталог макетов, в котором хранятся наши макеты, и создадим представление, которое будет отвечать за отображение одного фрукта.
Макет, который будет использоваться для каждого элемента в нашем списке
<?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 выше мы добавили поле идентификатора, которое будет использоваться для идентификации представления.
По умолчанию он не генерируется. У нас есть TextView с идентификатором 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. Используя созданный нами идентификатор, мы сможем ссылаться на только что созданное представление.
Мы начнем со ссылки на 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, который отвечает за получение нашего представления и преобразование его в 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 и динамически привяжет содержимое к созданному нами представлению one_fruit_view.xml.
package com.guru99.learnrecycler import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.view.View import androidx.recyclerview.widget.RecyclerView class MainActivity : AppCompatActivity() { var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView) } class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView) class FruitArrayAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() }
Android Studio добавит красные волнистые линии на наш FruitArrayAdapter, сообщая нам, что нам нужно реализовать метод, который RecyclerView может использовать для подключения нашего массива к ViewHolder, который может использовать RecyclerView.
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.length.
class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder { } override fun getItemCount(): Int { return fruitArray.size } override fun onBindViewHolder(holder: FruitViewHolder, position: Int) { } }
Затем мы реализуем переопределение метода fun onCreateViewHolder.
Здесь RecyclerView просит нас помочь ему создать для него FruitHolder.
Если мы вспомним, так выглядел наш класс FruitViewHolder:
class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)
Для этого требуется наш FruitView, который мы создали в виде XML-файла one_fruit_view.xml.
Мы можем создать ссылку на этот XML и преобразовать его в представление следующим образом.
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) { } }
Оставшийся бит является переопределением
fun onBindViewHolder(holder: FruitViewHolder, position: Int)
RecyclerView.Adapter запрашивает целое число позиции, которое мы будем использовать для извлечения элемента из нашего списка. Он также предоставляет нам держатель, чтобы мы могли привязать элемент, полученный из FruitArray, к представлению, которое хранится внутри держателя представления.
Представление, хранящееся внутри ViewHoder, доступно через поле ViewHolder.itemView. Как только мы получим представление, мы можем использовать идентификатор 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) } } }