Salut. L'autre jour, j'ai rencontré le problème de l'implémentation d'une sélection de plusieurs éléments dans RecyclerView à l'aide de dataBinding.
Commencer
Tout d'abord, écrivons un adaptateur de base qui prend en charge dataBinding.
/**
* data binding'.
* @param layoutRes id layout',
* @param lifecycleOwner lifecycle owner , recycler view
* @param itemBindingId id layout',
* @param onClick ,
*/
class RecyclerViewAdapter<Item : IRecyclerViewItem>(
@LayoutRes private val layoutRes: Int,
private val lifecycleOwner: LifecycleOwner,
private val itemBindingId: Int? = null,
private val onClick: ((Item) -> Unit)? = null
) : RecyclerView.Adapter<RecyclerViewAdapter<Item>.ViewHolder>() {
private val items = mutableListOf<Item>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
// ViewDataBinding layoutRes
val binding = DataBindingUtil.inflate<ViewDataBinding>(inflater, layoutRes, parent, false)
return ViewHolder(binding)
}
override fun getItemCount(): Int = items.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// onBind
val item = items[position]
holder.onBind(item)
}
/**
*
*
* @param newItems
*/
fun setItems(newItems: List<Item>) {
val diffUtilCallback = DiffUtilCallback(newItems)
val diffResult = DiffUtil.calculateDiff(diffUtilCallback)
items.apply {
clear()
addAll(newItems)
}
diffResult.dispatchUpdatesTo(this)
}
// DataBinding'
inner class ViewHolder(
private val binding: ViewDataBinding
) : RecyclerView.ViewHolder(binding.root) {
fun onBind(item: Item) {
binding.apply {
//
setVariable(itemBindingId ?: BR.item, item)
root.setOnClickListener { onClick?.invoke(item) }
lifecycleOwner = this@RecyclerViewAdapter.lifecycleOwner
}
}
}
private inner class DiffUtilCallback(private val newItems: List<Item>) : DiffUtil.Callback() {
override fun getOldListSize(): Int = itemCount
override fun getNewListSize(): Int = newItems.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return newItems[newItemPosition].id == items[oldItemPosition].id
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return newItems[newItemPosition] == items[oldItemPosition]
}
}
}
Aussi, pour que DiffUtil fonctionne, j'ai créé une interface qui montre que l'élément a un champ unique
/**
* ui , RecyclerViewAdapter.
* @property id
*/
interface IRecyclerViewItem {
val id: Int
}
Jusqu'à récemment, cet adaptateur était capable de résoudre presque toutes les tâches avec des listes. Selon le projet, onClick peut être remplacé par onBind: (binding: ViewDataBinding) -> Unit, vous pouvez ainsi personnaliser les éléments individuels de l'élément.
Aide à la sélection
, SelectionHelper, dataBinding' .
, , , .
, , :
class SelectionHelper<T : IRecyclerViewItem> : ISelectionHelper<T>() {
//
private val selectedItems = mutableMapOf<Int, T>()
// , - , -
override fun handleItem(item: T) {
if (selectedItems[item.id] == null) {
selectedItems[item.id] = item
} else {
selectedItems.remove(item.id)
}
// dataBining, ui)
notifyChange()
}
override fun isSelected(id: Int): Boolean = selectedItems.containsKey(id)
override fun getSelectedItems(): List<T> = selectedItems.values.toList()
override fun getSelectedItemsSize(): Int = selectedItems.size
}
// BaseObservable, , dataBinding
//
abstract class ISelectionHelper<T : IRecyclerViewItem> : BaseObservable() {
abstract fun handleItem(item: T)
abstract fun isSelected(id: Int): Boolean
abstract fun getSelectedItems(): List<T>
abstract fun getSelectedItemsSize(): Int
}
:
viewModel selectionHelper.getSelectedItems, .
DataBinding, - adapter
, onBind
:
viewModel/presenter ,
xml
- ,
adadpter
class RecyclerViewAdapter<Item : IRecyclerViewItem>(
@LayoutRes private val layoutRes: Int,
private val lifecycleOwner: LifecycleOwner,
private val itemBindingId: Int? = null,
// , ,
private val selectionHelper: ISelectionHelper<Item>? = null,
private val onClick: ((Item) -> Unit)? = null
) : RecyclerView.Adapter<RecyclerViewAdapter<Item>.ViewHolder>() {
...
inner class ViewHolder(
private val binding: ViewDataBinding
) : RecyclerView.ViewHolder(binding.root) {
fun onBind(item: Item) {
binding.apply {
//
setVariable(itemBindingId ?: BR.item, item)
selectionHelper?.let { setVariable(BR.selectionHelper, it) }
root.setOnClickListener {
//
selectionHelper?.handleItem(item)
onClick?.invoke(item)
}
lifecycleOwner = this@RecyclerViewAdapter.lifecycleOwner
}
}
}
}
/ , , onBind, - .
class RecyclerViewAdapter<Item : IRecyclerViewItem>(
@LayoutRes private val layoutRes: Int,
private val lifecycleOwner: LifecycleOwner,
private val itemBindingId: Int? = null,
private val selectionHelper: ISelectionHelper<Item>? = null,
private val onClick: ((Item) -> Unit)? = null
) : RecyclerView.Adapter<RecyclerViewAdapter<Item>.ViewHolder>() {
private val items = mutableListOf<Item>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = DataBindingUtil.inflate<ViewDataBinding>(inflater, layoutRes, parent, false)
return ViewHolder(binding)
}
override fun getItemCount(): Int = items.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]
holder.onBind(item)
}
/**
*
*
* @param newItems
*/
fun setItems(newItems: List<Item>) {
val diffUtilCallback = DiffUtilCallback(newItems)
val diffResult = DiffUtil.calculateDiff(diffUtilCallback)
items.apply {
clear()
addAll(newItems)
}
diffResult.dispatchUpdatesTo(this)
}
inner class ViewHolder(
private val binding: ViewDataBinding
) : RecyclerView.ViewHolder(binding.root) {
fun onBind(item: Item) {
binding.apply {
//
setVariable(itemBindingId ?: BR.item, item)
selectionHelper?.let { setVariable(BR.selectionHelper, it) }
root.setOnClickListener {
//
selectionHelper?.handleItem(item)
onClick?.invoke(item)
}
lifecycleOwner = this@RecyclerViewAdapter.lifecycleOwner
}
}
}
private inner class DiffUtilCallback(private val newItems: List<Item>) : DiffUtil.Callback() {
override fun getOldListSize(): Int = itemCount
override fun getNewListSize(): Int = newItems.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return newItems[newItemPosition].id == items[oldItemPosition].id
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return newItems[newItemPosition] == items[oldItemPosition]
}
}
}
, xml , selectionHelper xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="item"
type="ImageItemUi" />
<!-- ) -->
<variable
name="selectionHelper"
type="dev.syncended.ctime.utils.ui.ISelectionHelper<ImageItemUi>" />
<import type="dev.syncended.ctime.models.ui.ImageItemUi" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_margin="@dimen/ui_spacing_normal"
android:padding="@dimen/1dp"
android:scaleType="centerCrop"
app:file="@{item.file}"
app:item_id="@{item.id}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintTop_toTopOf="parent"
app:selection_helper="@{selectionHelper}"
tools:ignore="ContentDescription" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
padding = 1dp, , , .
bindingAdapter selectionHelper
// selectionHelper', id
@BindingAdapter("selection_helper", "item_id", requireAll = true)
fun <T : IRecyclerViewItem> handleSelection(
view: View,
selectionHelper: ISelectionHelper<T>,
itemId: Int
) {
//
val isSelected = selectionHelper.isSelected(itemId)
//
val color = if (isSelected) {
R.color.color_primary
} else {
android.R.color.transparent
}
view.setBackgroundColor(ContextCompat.getColor(view.context, color))
}
, background.
:
:
En conséquence, nous avons un outil assez simple pour sélectionner les éléments de la liste. Si vous vous éloignez de la sélection habituelle avec un cadre, vous pouvez modifier l'état, par exemple, d'une case à cocher, selon qu'un élément est sélectionné ou non.
android:checked=@{selectionHelper.isSelected(item.id)}
Par analogie, vous pouvez faire un tas de différentes variantes d'utilisation de cet assistant.
Merci d'avoir lu, ceci est mon premier article, alors ne jugez pas strictement, et gardez également le chat.