You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Kotlin中按两个条件对RecyclerView排序的问题

RecyclerView按checked与order规则排序的实现方案

问题需求

需要实现RecyclerView元素排序逻辑:

  • 未勾选(checked=false)的元素排在上方,已勾选(checked=true)的排在下方
  • 同状态下的元素,按照order字段的整数升序排列
  • 勾选/取消勾选时,元素自动移动到对应排序位置,比如勾选order=2的元素时,要插入到已勾选区域order=1和3之间;取消勾选order=5的元素时,要回到未勾选区域的对应order位置

现有代码的问题

当前的unchecked()和checked()方法手动对集合进行增删移动元素,再调用sort()会导致集合操作冲突:

  • 手动增删元素后再排序,容易出现元素重复、位置错乱
  • notifyItemMoved和notifyDataSetChanged混用,刷新逻辑冲突,导致UI异常

解决步骤

  1. 移除手动移动元素的逻辑:先修改目标元素的checked状态,再统一排序
  2. 使用DiffUtil实现局部刷新:替代全量的notifyDataSetChanged,既保证UI正确性,又提升性能
  3. 统一排序规则:用compareBy({ it.checked }, { it.order })作为排序逻辑,注意checked是布尔值,false会排在true前面(符合未勾选在上的需求)

完整代码示例

1. 数据类Model

data class Model(
    val id: Int, // 必须要有唯一标识,用于DiffUtil判断元素唯一性
    var checked: Boolean,
    val order: Int
)

2. Adapter修改后的代码

class MyAdapter : RecyclerView.Adapter<MyAdapter.ViewHolder>() {
    private var list = mutableListOf<Model>()

    // 实现DiffUtil的ItemCallback,用于精准计算列表差异
    private val diffCallback = object : DiffUtil.ItemCallback<Model>() {
        override fun areItemsTheSame(oldItem: Model, newItem: Model): Boolean {
            return oldItem.id == newItem.id // 用唯一id判断是否为同一元素
        }

        override fun areContentsTheSame(oldItem: Model, newItem: Model): Boolean {
            return oldItem == newItem // 数据类直接用==判断内容是否一致
        }
    }

    // 刷新列表的核心方法:用DiffUtil处理局部刷新
    private fun updateList(newList: List<Model>) {
        val diffResult = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
            override fun getOldListSize() = list.size
            override fun getNewListSize() = newList.size
            override fun areItemsTheSame(oldPos: Int, newPos: Int): Boolean {
                return list[oldPos].id == newList[newPos].id
            }
            override fun areContentsTheSame(oldPos: Int, newPos: Int): Boolean {
                return list[oldPos] == newList[newPos]
            }
        })
        list.clear()
        list.addAll(newList)
        diffResult.dispatchUpdatesTo(this)
    }

    // 处理勾选状态切换的方法
    fun toggleChecked(position: Int) {
        // 1. 修改当前元素的checked状态
        val item = list[position]
        val updatedItem = item.copy(checked = !item.checked)
        list[position] = updatedItem

        // 2. 按照规则对列表重新排序
        val sortedList = list.sortedWith(
            compareBy<Model> { it.checked }.thenBy { it.order }
        ).toMutableList()

        // 3. 用DiffUtil更新列表
        updateList(sortedList)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_layout, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = list[position]
        holder.checkBox.isChecked = item.checked
        // 避免刷新视图时重复触发状态切换
        holder.checkBox.setOnCheckedChangeListener { _, isChecked ->
            if (isChecked != item.checked) {
                toggleChecked(position)
            }
        }
        // 绑定其他视图内容...
    }

    override fun getItemCount() = list.size

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val checkBox: CheckBox = itemView.findViewById(R.id.checkbox)
        // 其他视图组件...
    }
}

关键说明

  • DiffUtil的作用:通过对比新旧列表的差异,只刷新状态或位置变化的元素,避免全量刷新导致的UI闪烁和位置异常
  • 排序逻辑:compareBy({ it.checked }, { it.order })中,布尔值false(未勾选)排序优先级高于true(已勾选),所以未勾选元素自动排在上方;同状态下按order升序排列,完全匹配需求
  • 避免重复触发:在setOnCheckedChangeListener中增加状态判断,防止视图刷新重新绑定时重复调用toggleChecked

内容的提问来源于stack exchange,提问作者DJTalls

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 19:48:34