Selección de elementos de RecylerView usando dataBinding

Hola. El otro día me encontré con el problema de implementar una selección de varios elementos en RecyclerView usando dataBinding.





Empezar

Primero, escriba un adaptador básico que admita 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]
        }
    }
}
      
      



Además, para que DiffUtil funcione, hice una interfaz que muestra que el elemento tiene un campo único





/**
 *   ui ,   RecyclerViewAdapter.
 * @property id   
 */
interface IRecyclerViewItem {
    val id: Int
}
      
      



, . onClick onBind: (binding: ViewDataBinding) -> Unit, .





Selection helper

, 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





:





  1. viewModel/presenter ,









  2. 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&lt;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.





:





:





. , , , , , .





android:checked=@{selectionHelper.isSelected(item.id)}
      
      



.





, , , .












All Articles