基于DiffUtil优化RecyclerView选中项CardView变色方案求助
使用DiffUtil实现RecyclerView选中项UI切换(替代过时的notifyItemChanged)
核心思路
用ListAdapter(内置DiffUtil支持)替代传统RecyclerView.Adapter,结合DiffUtil的局部更新能力,只刷新选中状态变化的item,避免全量刷新。同时通过数据模型维护选中状态,让DiffUtil自动识别变化,实现高效更新。
1. 数据模型类(包含选中状态)
data class ItemModel( val id: String, // 唯一标识,用于DiffUtil判断是否为同一item val title: String, var isSelected: Boolean = false // 维护选中状态 )
2. 实现DiffUtil.ItemCallback
通过getChangePayload实现局部更新,只传递选中状态变化的信息,避免刷新整个item布局:
class ItemDiffCallback : DiffUtil.ItemCallback<ItemModel>() { override fun areItemsTheSame(oldItem: ItemModel, newItem: ItemModel): Boolean { // 用唯一id判断是否为同一item return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: ItemModel, newItem: ItemModel): Boolean { // 判断内容是否完全一致,选中状态变化时返回false return oldItem.isSelected == newItem.isSelected && oldItem.title == newItem.title } override fun getChangePayload(oldItem: ItemModel, newItem: ItemModel): Any? { // 仅当选中状态变化时,返回标记用于局部更新 if (oldItem.isSelected != newItem.isSelected) { return "SELECTED_STATE_CHANGED" } return super.getChangePayload(oldItem, newItem) } }
3. 编写ListAdapter实现
使用ListAdapter,处理点击事件时更新数据并提交,利用DiffUtil自动计算差异并高效刷新:
class ItemAdapter : ListAdapter<ItemModel, ItemAdapter.ItemViewHolder>(ItemDiffCallback()) { // 记录之前选中的item,实现单选逻辑 private var lastSelectedPosition: Int = -1 inner class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val cardView: CardView = itemView.findViewById(R.id.card_view) val titleTv: TextView = itemView.findViewById(R.id.title_tv) fun bind(model: ItemModel) { titleTv.text = model.title // 直接通过selector设置状态,无需手动判断颜色 cardView.isSelected = model.isSelected titleTv.isSelected = model.isSelected } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.item_layout, parent, false) val holder = ItemViewHolder(view) // 设置点击事件 holder.itemView.setOnClickListener { val currentPosition = holder.adapterPosition if (currentPosition == RecyclerView.NO_POSITION) return@setOnClickListener // 复制当前数据列表,避免修改原数据 val newList = currentList.toMutableList() // 取消之前选中项的状态 if (lastSelectedPosition != -1 && lastSelectedPosition != currentPosition) { newList[lastSelectedPosition] = newList[lastSelectedPosition].copy(isSelected = false) } // 更新当前选中项的状态 val currentItem = newList[currentPosition] newList[currentPosition] = currentItem.copy(isSelected = !currentItem.isSelected) // 更新选中位置记录 lastSelectedPosition = if (currentItem.isSelected) -1 else currentPosition // 提交新列表,DiffUtil自动处理刷新 submitList(newList) } return holder } override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { holder.bind(currentList[position]) } override fun onBindViewHolder(holder: ItemViewHolder, position: Int, payloads: MutableList<Any>) { // 处理局部更新,仅当选中状态变化时刷新 if (payloads.isNotEmpty()) { val model = currentList[position] holder.cardView.isSelected = model.isSelected holder.titleTv.isSelected = model.isSelected return } // 无payload时执行全量绑定 super.onBindViewHolder(holder, position, payloads) } }
4. 布局XML优化(使用Selector)
创建card_background_selector.xml(drawable目录下):
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@color/selected_card_bg" android:state_selected="true"/> <item android:drawable="@color/default_card_bg"/> </selector>
创建text_color_selector.xml(color目录下):
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="@color/selected_text_color" android:state_selected="true"/> <item android:color="@color/default_text_color"/> </selector>
然后在item布局item_layout.xml中引用:
<?xml version="1.0" encoding="utf-8"?> <androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/card_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="8dp" android:clickable="true" android:focusable="true" android:foreground="?attr/selectableItemBackground" app:cardBackgroundColor="@drawable/card_background_selector" app:cardCornerRadius="8dp" app:cardElevation="4dp"> <TextView android:id="@+id/title_tv" android:layout_width="match_parent" android:layout_height="60dp" android:gravity="center" android:textColor="@color/text_color_selector" android:textSize="16sp"/> </androidx.cardview.widget.CardView>
5. 使用Adapter
在Activity/Fragment中初始化RecyclerView:
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view) recyclerView.layoutManager = LinearLayoutManager(this) val adapter = ItemAdapter() recyclerView.adapter = adapter // 初始化数据 val initialList = listOf( ItemModel("1", "选项1"), ItemModel("2", "选项2"), ItemModel("3", "选项3") ) adapter.submitList(initialList)
为什么这是更高效的实现?
- ListAdapter内置DiffUtil:自动对比新旧数据差异,只刷新变化的item,避免
notifyDataSetChanged()的全量刷新。 - 局部更新(Payload):通过
getChangePayload只传递选中状态变化的标记,onBindViewHolder仅更新背景和文字颜色,无需重新绑定整个item的所有数据。 - 数据驱动UI:选中状态维护在数据模型中,符合组件化开发思想,UI自动跟随数据变化更新。
- 替代过时方法:完全避免使用
notifyItemChanged(),采用submitList()触发DiffUtil计算,符合Jetpack组件的最新规范。
内容的提问来源于stack exchange,提问作者Noah
相关产品推荐
相关产品推荐

