Виджет RecyclerView
является неотъемлемой частью большинства приложений Android сегодня. С тех пор как он был добавлен в библиотеку поддержки Android в конце 2014 года, он затмил виджет ListView
как наиболее предпочтительный виджет для отображения больших и сложных списков. Однако в нем отсутствует одна важная функция: поддержка выбора и отслеживания элементов списка. RecyclerView Selection , аддонная библиотека Google, выпущенная в марте этого года, пытается это исправить.
В этом уроке я покажу вам, как использовать новую библиотеку для создания приложения, которое предлагает интуитивно понятный интерфейс для выбора нескольких элементов в списке. Следуйте этому примеру Android RecyclerView множественного выбора, и вы узнаете некоторые навыки, которые можно применять в своих собственных приложениях.
Предпосылки
Чтобы следовать, вам нужно:
- последняя версия Android Studio
- устройство или эмулятор под управлением Android API уровня 23 или выше
1. Добавьте RecyclerView Android-зависимости
Чтобы добавить библиотеку RecyclerView Selection в проект Android Studio, укажите следующие зависимости implementation
в файле build.gradle модуля app
:
1
2
|
implementation ‘com.android.support:recyclerview-v7:28.0.0’
implementation ‘com.android.support:recyclerview-selection:28.0.0’
|
2. Создайте список
В этом уроке мы будем работать с небольшим списком предметов, каждый из которых содержит имя и номер телефона человека.
Чтобы сохранить данные каждого элемента списка, создайте класс данных Kotlin с именем Person
и добавьте к нему два свойства: name
и phone
.
1
2
|
data class Person(val name:String,
val phone: String)
|
Теперь вы можете продолжить и создать список объектов Person
в вашей основной деятельности.
1
2
3
4
5
6
7
8
|
val myList = listOf(
Person(«Alice», «555-0111»),
Person(«Bob», «555-0119»),
Person(«Carol», «555-0141»),
Person(«Dan», «555-0155»),
Person(«Eric», «555-0180»),
Person(«Craig», «555-0145»)
)
|
3. Добавьте Recycler View в макет
Мы, конечно, будем использовать виджет RecyclerView
для отображения списка. Поэтому добавьте <RecyclerView>
в XML-файл макета вашей основной деятельности.
1
2
3
4
5
6
|
<android.support.v7.widget.RecyclerView
android:layout_width=»match_parent»
android:layout_height=»match_parent»
android:id=»@+id/my_rv»>
</android.support.v7.widget.RecyclerView>
|
Чтобы указать расположение элементов списка, создайте новый файл XML и назовите его list_item.xml . Внутри него добавьте два виджета TextView
: один для отображения имени, а другой для отображения номера телефона. Если вы используете элемент LinearLayout
для позиционирования виджетов, содержимое файла XML должно выглядеть следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
<LinearLayout
xmlns:android=»https://schemas.android.com/apk/res/android»
android:orientation=»vertical» android:layout_width=»match_parent»
android:layout_height=»wrap_content»
android:padding=»16dp»>
<TextView
android:layout_width=»match_parent»
android:layout_height=»wrap_content»
android:id=»@+id/list_item_name»
style=»@style/TextAppearance.AppCompat.Large»/>
<TextView
android:layout_width=»match_parent»
android:layout_height=»wrap_content»
android:id=»@+id/list_item_phone»
style=»@style/TextAppearance.AppCompat.Small»/>
</LinearLayout>
|
4. Создайте Держатель Представления
Вы можете думать о держателе представления как об объекте, который содержит ссылки на представления, представленные в макете элементов списка. Без этого виджет RecyclerView
не сможет эффективно отображать элементы списка.
На данный момент вам нужен держатель представления, который содержит два виджета TextView
вы создали на предыдущем шаге. Поэтому создайте новый класс, который расширяет класс RecyclerView.ViewHolder
, и инициализируйте ссылки на оба виджета внутри него. Вот как:
1
2
3
4
5
6
7
8
9
|
class MyViewHolder(view: View)
: RecyclerView.ViewHolder(view) {
val name: TextView = view.list_item_name
val phone: TextView = view.list_item_phone
// More code here
}
|
Кроме того, для дополнения RecyclerView
Selection
требуется метод, который он может вызывать для уникальной идентификации выбранных элементов списка. Этот метод в идеале принадлежит самому владельцу вида. Кроме того, он должен возвращать экземпляр класса ItemDetailsLookup.ItemDetails
. Поэтому добавьте следующий код в держатель представления:
1
2
3
4
5
6
|
fun getItemDetails(): ItemDetailsLookup.ItemDetails<Long> =
object: ItemDetailsLookup.ItemDetails<Long>() {
// More code here
}
|
Теперь вы должны переопределить два абстрактных метода, присутствующих в классе ItemDetails
. Начните с переопределения getPosition()
и возврата свойства adapterPosition
держателя представления внутри него. Свойство adapterPosition
обычно представляет собой не что иное, как индекс элемента списка.
1
|
override fun getPosition(): Int = adapterPosition
|
Затем переопределите метод getSelectionKey()
. Этот метод должен возвращать ключ, который можно использовать для уникальной идентификации элемента списка. Для простоты давайте просто itemId
свойство itemId
держателя представления.
1
|
override fun getSelectionKey(): Long?
|
Вы можете использовать любой другой метод для генерации ключа выбора, если он генерирует уникальные значения.
5. Обрабатывать прикосновения пользователя
Чтобы дополнение RecyclerView
Selection
работало корректно, каждый раз, когда пользователь касается виджета RecyclerView
, необходимо преобразовать координаты касания в объект ItemDetails
.
Создайте новый класс, который расширяет класс ItemDetailsLookup
, и добавьте в него конструктор, который может принимать виджет RecyclerView
в качестве аргумента. Обратите внимание, что, поскольку класс является абстрактным, Android Studio автоматически сгенерирует заглушку для своего абстрактного метода.
1
2
3
4
5
6
7
8
9
|
class MyLookup(private val rv: RecyclerView)
: ItemDetailsLookup<String>() {
override fun getItemDetails(event: MotionEvent)
: ItemDetails<String>?
// More code here
}
}
|
Как видно из приведенного выше кода, метод getItemDetails()
получает объект MotionEvent
. findChildViewUnder()
координаты X и Y события в метод findChildViewUnder()
, вы можете определить представление, связанное с элементом списка, к findChildViewUnder()
прикоснулся пользователь. Чтобы преобразовать объект View
объект ItemDetails
, все, что вам нужно сделать, это вызвать метод getItemDetails()
. Вот как:
1
2
3
4
5
6
|
val view = rv.findChildViewUnder(event.x, event.y)
if(view != null) {
return (rv.getChildViewHolder(view) as MyViewHolder)
.getItemDetails()
}
return null
|
6. Создайте адаптер
Теперь вам понадобится адаптер, который может связать ваш список с вашим виджетом RecyclerView
. Чтобы создать его, создайте новый класс, который расширяет класс RecyclerView.Adapter
. Поскольку адаптеру необходим доступ к списку и контексту вашей деятельности, новый класс должен иметь конструктор, который может принимать оба аргумента в качестве аргументов.
1
2
3
4
5
|
class MyAdapter(private val listItems:List<Person>,
private val context: Context)
: RecyclerView.Adapter<MyViewHolder>() {
}
|
Важно, чтобы вы явно указали, что каждый элемент этого адаптера будет иметь уникальный стабильный идентификатор типа Long
. Лучшее место для этого — внутри блока init
.
1
2
3
|
init {
setHasStableIds(true)
}
|
Кроме того, чтобы иметь возможность использовать позицию элемента в качестве его уникального идентификатора, вам необходимо переопределить метод getItemId()
.
1
2
3
|
override fun getItemId(position: Int): Long {
return position.toLong()
}
|
Поскольку класс RecyclerView.Adapter
является абстрактным, теперь вам придется переопределить еще три метода, чтобы сделать ваш адаптер пригодным для использования.
Сначала переопределите метод getItemCount()
чтобы вернуть размер списка.
1
|
override fun getItemCount(): Int = listItems.size
|
Затем переопределите метод onCreateViewHolder()
. Этот метод должен возвращать экземпляр класса держателя представления, который вы создали ранее в этом руководстве. Чтобы создать такой экземпляр, вы должны вызвать конструктор класса и передать ему раздутый макет элементов списка. Чтобы надуть макет, используйте метод LayoutInflater
класса LayoutInflater
. Вот как:
1
2
3
4
5
6
|
override fun onCreateViewHolder(parent: ViewGroup,
viewType: Int): MyViewHolder =
MyViewHolder(
LayoutInflater.from(context)
.inflate(R.layout.list_item, parent, false)
)
|
Наконец, переопределите метод onBindViewHolder()
и соответствующим образом инициализируйте свойство text
обоих виджетов TextView
присутствующих в держателе представления.
1
2
3
4
|
override fun onBindViewHolder(vh: MyViewHolder, position: Int) {
vh.name.text = listItems[position].name
vh.phone.text = listItems[position].phone
}
|
7. Показать список
На данный момент, у вас есть почти все, что вам нужно, чтобы сделать свой список Однако вы все равно должны указать, как вы хотите, чтобы элементы списка были расположены. А пока давайте расположим их один под другим, используя экземпляр LinearLayoutManager
.
Для оптимальной производительности, я предлагаю вам также указать, что размер виджета RecyclerView
не изменится во время выполнения.
Добавьте следующий код к вашей основной деятельности:
1
2
|
my_rv.layoutManager = LinearLayoutManager(this)
my_rv.setHasFixedSize(true)
|
Наконец, назначьте новый экземпляр вашего adapter
свойству adapter
виджета RecyclerView
.
1
2
|
val adapter = MyAdapter(myList, this)
my_rv.adapter = adapter
|
Если вы запустите свое приложение сейчас, вы сможете увидеть список.
8. Создайте трекер выбора
Виджет RecyclerView
прежнему не позволяет выбирать какие-либо элементы. Чтобы включить выбор из нескольких элементов, вам понадобится объект SelectionTracker
в вашей деятельности.
1
|
private var tracker: SelectionTracker<Long>?
|
Вы можете инициализировать трекер, используя класс SelectionTracker.Builder
. Его конструктору необходимо передать идентификатор выбора, ваш виджет RecyclerView
, ключевой поставщик, класс поиска сведений о вашем элементе и стратегию хранения.
Вы можете использовать любую строку в качестве идентификатора выбора. В качестве ключевого поставщика вы можете использовать экземпляр класса StableIdKeyProvider
.
Библиотека RecyclerView Selection предлагает множество стратегий хранения, каждая из которых гарантирует, что выбранные элементы не отменяются при повороте устройства пользователя или когда система Android закрывает ваше приложение во время перерыва в ресурсах. На данный момент, поскольку тип ваших клавиш выбора — Long
, вы должны использовать объект StorageStrategy
типа Long
.
Когда Builder
будет готов, вы можете вызвать его withSelectionPredicate()
чтобы указать, сколько элементов вы хотите разрешить пользователю выбирать. Для поддержки выбора нескольких элементов в качестве аргумента метода необходимо передать объект SelectionPredicate
возвращаемый методом createSelectAnything()
.
Соответственно, добавьте следующий код в метод onCreate()
вашей деятельности:
1
2
3
4
5
6
7
8
9
|
tracker = SelectionTracker.Builder<Long>(
«selection-1»,
my_rv,
StableIdKeyProvider(my_rv),
MyLookup(my_rv),
StorageStrategy.createLongStorage()
).withSelectionPredicate(
SelectionPredicates.createSelectAnything()
).build()
|
Чтобы максимально использовать стратегию хранения, вы всегда должны пытаться восстановить состояние трекера внутри onCreate()
.
1
2
|
if(savedInstanceState != null)
tracker?.onRestoreInstanceState(savedInstanceState)
|
Точно так же вы должны убедиться, что вы сохранили состояние трекера в onSaveInstanceState()
вашей активности.
1
2
3
4
5
6
|
override fun onSaveInstanceState(outState: Bundle?) {
super.onSaveInstanceState(outState)
if(outState != null)
tracker?.onSaveInstanceState(outState)
}
|
Трекер выбора не очень полезен, если он не связан с вашим адаптером. Поэтому передайте его адаптеру, вызвав метод setTracker()
.
1
|
adapter.setTracker(tracker)
|
Метод setTracker()
еще не существует, поэтому добавьте следующий код в свой класс адаптера:
1
2
3
4
5
|
private var tracker: SelectionTracker<Long>?
fun setTracker(tracker: SelectionTracker<Long>?) {
this.tracker = tracker
}
|
Если вы попытаетесь запустить свое приложение на этом этапе, вы сможете выбрать элементы в списке. Когда вы входите в режим выбора из нескольких элементов при длительном нажатии на элемент списка, вы сможете почувствовать краткую вибрацию на большинстве устройств. Однако, поскольку выбранные элементы в настоящее время неотличимы от невыбранных, у вас не будет визуальной обратной связи. Чтобы это исправить, вам нужно внести несколько изменений в метод onBindViewHolder()
вашего адаптера.
Обычный способ выделить выбранные элементы — изменить цвет фона. Поэтому теперь вы должны изменить цвет LinearLayout
виджета LinearLayout
который присутствует в XML-файле макета ваших элементов. Чтобы получить ссылку на него, получите ссылку на родителя одного из виджетов TextView
доступных в держателе представления.
Добавьте следующий код непосредственно перед концом метода onBindViewHolder()
:
1
2
3
|
val parent = vh.name.parent as LinearLayout
// More code here
|
Затем вы можете вызвать метод isSelected()
объекта SelectionTracker
чтобы определить, выбран элемент или нет.
Следующий код показывает, как изменить цвет фона выделенных элементов на голубой:
1
2
3
4
5
6
7
8
|
if(tracker!!.isSelected(position.toLong())) {
parent.background = ColorDrawable(
Color.parseColor(«#80deea»)
)
} else {
// Reset color to white if not selected
parent.background = ColorDrawable(Color.WHITE)
}
|
Если вы запустите приложение сейчас, вы сможете увидеть выбранные вами элементы.
9. Создайте наблюдателя выбора
Обычно вы хотите показать пользователю, сколько элементов выбрано в данный момент. С библиотекой RecyclerView Selection это сделать очень просто.
addObserver()
объект SelectionObserver
с трекером выбора, вызвав метод addObserver()
. Внутри onSelectionChanged()
наблюдателя вы можете обнаружить изменения в количестве выбранных элементов.
1
2
3
4
5
6
7
8
|
tracker?.addObserver(
object: SelectionTracker.SelectionObserver<Long>() {
override fun onSelectionChanged() {
val nItems:Int?
// More code here
}
})
|
Как вы отображаете количество выбранных элементов, зависит от вас. На данный момент я предлагаю вам отобразить номер прямо на панели действий вашей деятельности. При желании вы также можете изменить цвет фона панели действий, чтобы пользователь знал, что в списке есть активный выбор. Следующий код показывает вам, как:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
if(nItems!=null && nItems > 0) {
// Change title and color of action bar
title = «$nItems items selected»
supportActionBar?.setBackgroundDrawable(
ColorDrawable(Color.parseColor(«#ef6c00»)))
} else {
// Reset color and title to default values
title = «RVSelection»
supportActionBar?.setBackgroundDrawable(
ColorDrawable(getColor(R.color.colorPrimary)))
}
|
Если вы снова запустите приложение, вы увидите изменение названия, чтобы отразить количество выбранных элементов списка.
Вывод
В этом руководстве вы узнали, как использовать библиотеку дополнений RecyclerView Selection, чтобы добавить поддержку простого выбора элементов в виджет RecyclerView
. Вы также узнали, как динамически изменять внешний вид выбранных элементов, чтобы пользователи могли отличить их от других.
Чтобы узнать больше о библиотеке, обратитесь к официальной документации .