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

RecyclerView结合ItemTouchHelper无法重复移动同一项的问题

问题分析与解决方案

核心问题

  1. 重复创建Adapter与ItemTouchHelper:每次viewModel.getHunts()的LiveData触发回调时,都重新创建了HuntAdapter和ItemTouchHelper实例。拖动完成更新数据库后,LiveData推送新数据会替换Adapter,导致之前的拖动状态丢失,后续拖动出现数据复位、无动画的问题。
  2. Adapter数据未复用:没有提供更新Adapter内部数据的方法,而是直接替换整个Adapter,破坏了RecyclerView的状态连续性。

修复步骤

1. 修改MainActivity:复用Adapter与ItemTouchHelper

将Adapter和ItemTouchHelper的初始化移出LiveData的Observer回调,仅在Activity创建时初始化一次,Observer回调只负责更新Adapter的数据:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = ActivityHuntsBinding.inflate(layoutInflater)
    setContentView(binding.root)

    auth = FirebaseAuth.getInstance()

    val viewModel: HuntViewModel by viewModels()
    // 初始化空Adapter
    val huntAdapter = HuntAdapter(this, mutableListOf(), this)
    binding.huntsRecyclerView.adapter = huntAdapter

    // 初始化ItemTouchHelper并绑定RecyclerView
    val itemTouchHelper = ItemTouchHelper(HuntItemTouchHelperCallback(huntAdapter))
    itemTouchHelper.attachToRecyclerView(binding.huntsRecyclerView)

    // 仅在数据变化时更新Adapter内容
    viewModel.getHunts().observe(this) { huntsList ->
        huntAdapter.setData(huntsList)
    }
}

2. 给HuntAdapter添加数据更新方法

在Adapter中新增setData方法,用来更新内部列表数据,避免重复创建Adapter实例:

class HuntAdapter(
    private val context: Context,
    private var hunts: MutableList<Hunt>,
    private val listener: ItemTouchHelperListener
) : RecyclerView.Adapter<HuntAdapter.HuntViewHolder>(), ItemTouchHelperListener {

    // 添加数据更新方法
    fun setData(newHunts: List<Hunt>) {
        this.hunts.clear()
        this.hunts.addAll(newHunts)
        notifyDataSetChanged() // 追求更优性能可改用DiffUtil
    }

    // 原有onItemMove方法保持不变
    override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
        Collections.swap(hunts, fromPosition, toPosition)
        for (i in hunts.indices) {
            hunts[i].sortOrder = i + 1
        }
        notifyItemMoved(fromPosition, toPosition)
        return true
    }

    // 原有onItemRelease方法保持不变
    override fun onItemRelease() {
        auth = FirebaseAuth.getInstance()
        val db = FirebaseFirestore.getInstance().collection(auth.currentUser.uid)
        val batch = db.firestore.batch()
        for (hunt in hunts) {
            batch.update(db.document(hunt.created!!), "sortOrder", hunt.sortOrder)
        }
        batch.commit()
            .addOnSuccessListener {
                Log.w("DB_Response", "Sort order updated successfully for all hunts.")
            }
            .addOnFailureListener { e ->
                Log.w("DB_Response", "Error updating sort order for all hunts.", e)
            }
    }

    // 其他ViewHolder相关代码...
}

3. 可选优化:onItemMove中的sortOrder更新逻辑

当前每次拖动都遍历所有项更新sortOrder,功能正常但可优化为仅更新受影响的项(列表项不多时当前逻辑也可接受):

override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
    val movedItem = hunts.removeAt(fromPosition)
    hunts.add(toPosition, movedItem)
    // 仅更新移动路径上的项的sortOrder
    val start = min(fromPosition, toPosition)
    val end = max(fromPosition, toPosition)
    for (i in start..end) {
        hunts[i].sortOrder = i + 1
    }
    notifyItemMoved(fromPosition, toPosition)
    return true
}

修复原理

  • 复用Adapter实例保证了RecyclerView的状态连续性,拖动后的状态不会因数据更新被重置。
  • ItemTouchHelper只绑定一次,避免重复绑定导致的拖拽逻辑混乱。
  • 通过setData方法更新数据,既保证UI刷新,又保留Adapter原有状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 20:23:11