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

删除RecyclerView条目时如何保留滚动位置,避免自动滚至底部?

问题:聊天RecyclerView删除条目时禁止自动滚动到底部,仅在Activity启动时平滑滚动

我做了一个聊天场景的RecyclerView,现在遇到个问题:删除消息条目时,RecyclerView会自动滚动到最后一项,我希望只有在Activity启动时才执行平滑滚动到最后,删除操作时保持当前滚动位置不变。以下是相关代码,求解决方法。


RecyclerView配置代码

private fun setRecyclerView() {
        val linearLayoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
        linearLayoutManager.stackFromEnd = true
        recyclerView.layoutManager = linearLayoutManager
        //recyclerView.setHasFixedSize(true)
        recyclerView.itemAnimator = DefaultItemAnimator()
        adapter = SingleChatAdapter(this, messages, senderRoom!!, receiverRoom!!, {num ->
            incDecNum(num)
        }) {show, hide ->
            showChosenToolbar(show, hide)
        }
        recyclerView.adapter = adapter
    }

消息删除流程代码

private fun delBtn() {
        val delS = containerView?.findViewById<View>(R.id.deleteSelections)
        delS?.setOnClickListener {
            val senderDel = LayoutInflater.from(this).inflate(R.layout.sender_deletes, null)
            val receiverDel = LayoutInflater.from(this).inflate(R.layout.receiver_deletes, null)
            for (msg: SingleChatData in messages) {
                if (msg.selected && msg.senderId == auth.uid) {
                    val deleteDialog = AlertDialog.Builder(this).setView(senderDel)
                        .setTitle("Delete This message")
                    val myDialog = deleteDialog?.show()
                    senderDel.deleteForMeS.setOnClickListener {
                        deleteForMe()
                        myDialog?.dismiss()
                        showChosenToolbar(false, true)
                        chosenNumTxt.text = "0"
                        adapter.notifyDataSetChanged()
                    }
                    senderDel.deleteForAllS.setOnClickListener {
                        deleteForAll()
                        myDialog?.dismiss()
                        showChosenToolbar(false, true)
                        chosenNumTxt.text = "0"
                        adapter.notifyDataSetChanged()
                    }
                    senderDel.cancelDeletionS.setOnClickListener { myDialog?.dismiss() }
                } else if (msg.selected && msg.senderId != auth.uid) {
                    val deleteDialog = AlertDialog.Builder(this).setView(receiverDel)
                        .setTitle("Delete This message")
                    val myDialog = deleteDialog?.show()
                    receiverDel.deleteForMeR.setOnClickListener {
                        deleteForMe()
                        myDialog?.dismiss()
                        showChosenToolbar(false, true)
                        chosenNumTxt.text = "0"
                        adapter.notifyDataSetChanged()
                    }
                    receiverDel.cancelDeletionR.setOnClickListener { myDialog?.dismiss() }
                }
            }
        }
    }

数据添加至适配器代码

private fun setDataToAdapter(){
        firestore.collection("Chats").document(senderRoom!!).collection("messages").orderBy("time")
            .addSnapshotListener { value, _ ->
                if (!value?.isEmpty!!) {
                    messages.clear()
                    for (document in value.documents) {
                        val coMsg = document.data?.get("message").toString()
                        val coId = document.data?.get("senderId").toString()
                        val msgId = document.id
                        //Using Date
                        // Get Calendar
                        val calendarMap = document.data?.get("date") as Map<*, *>
                        val curTimestamp = calendarMap["time"] as com.google.firebase.Timestamp
                        val millis = curTimestamp.seconds * 1000 + curTimestamp.nanoseconds / 1000000
                        val ss = SimpleDateFormat("hh:mm a", Locale.getDefault())
                        val netCal = Date(millis)
                        val msgTime = ss.format(netCal).toString()
                        // Get Day
                        val chatDay = document.data?.get("day").toString()
                        // Get Full Date
                        val fullDateStamp = calendarMap["time"] as com.google.firebase.Timestamp
                        val dateInMillis = fullDateStamp.seconds * 1000 + fullDateStamp.nanoseconds / 1000
                        val sdf = SimpleDateFormat("MM/dd/yyyy", Locale.getDefault())
                        val netAllDate = Date(dateInMillis)
                        val fullDate = sdf.format(netAllDate).toString()
                        val type = document.data?.get("type").toString()
                        val coMsgT = SingleChatData(coMsg, coId, msgId, msgTime, chatDay, fullDate, type)
                        messages.add(coMsgT)
                    }
                    adapter.notifyDataSetChanged()
                    recyclerView.post { // Call smooth scroll
                        recyclerView.smoothScrollToPosition(adapter.itemCount - 1)
                    }
                }
            }
    }

解决方案

核心问题分析

当前删除操作后调用adapter.notifyDataSetChanged()会触发RecyclerView全局重布局,加上linearLayoutManager.stackFromEnd = true的配置,导致布局完成后自动滚动到底部。另外,setDataToAdapter中的Firestore快照监听会在所有数据变更(包括删除)时执行滚动逻辑,这是另一个触发滚动的根源。

具体修复步骤

  1. 区分首次加载与后续数据变更,仅首次执行滚动
    添加一个成员变量标记是否为首次加载,只在Activity启动时触发滚动:

    // 定义成员变量标记首次加载
    private var isFirstLoad = true
    
    private fun setDataToAdapter(){
            firestore.collection("Chats").document(senderRoom!!).collection("messages").orderBy("time")
                .addSnapshotListener { value, _ ->
                    if (!value?.isEmpty!!) {
                        messages.clear()
                        // ... 原有数据解析代码保持不变 ...
                        messages.add(coMsgT)
                    }
                    adapter.notifyDataSetChanged()
                    // 仅首次加载时执行滚动
                    if (isFirstLoad) {
                        recyclerView.post {
                            recyclerView.smoothScrollToPosition(adapter.itemCount - 1)
                        }
                        isFirstLoad = false
                    }
                }
        }
    
  2. 替换全局刷新为精确条目通知,避免触发滚动
    删除操作时不要用notifyDataSetChanged(),而是找到被删除条目的索引,调用notifyItemRemoved实现局部更新,这样RecyclerView不会触发全局布局滚动:
    以deleteForMe()为例,修改删除逻辑:

    private fun deleteForMe() {
        // 倒序遍历收集要删除的索引,避免索引混乱
        val deleteIndices = mutableListOf<Int>()
        for (i in messages.indices.reversed()) {
            if (messages[i].selected) {
                deleteIndices.add(i)
                // 执行数据库删除逻辑(原有代码保持不变)
                // ...
                messages.removeAt(i)
            }
        }
        // 逐个通知条目移除
        deleteIndices.forEach { index ->
            adapter.notifyItemRemoved(index)
        }
        // 可选:更新选中状态相关的UI
        adapter.notifyItemRangeChanged(0, messages.size)
    }
    

    之后删除按钮的点击回调中,去掉adapter.notifyDataSetChanged(),因为已经在deleteForMe()中完成了精确通知。

  3. 可选:删除前后手动保存恢复滚动位置
    如果上述操作后仍有滚动问题,可以在删除前记录当前滚动位置,删除后恢复:

    // 删除前记录当前可见位置
    val layoutManager = recyclerView.layoutManager as LinearLayoutManager
    val firstVisiblePos = layoutManager.findFirstVisibleItemPosition()
    
    // 执行删除操作...
    
    // 删除后恢复滚动位置
    recyclerView.post {
        layoutManager.scrollToPosition(firstVisiblePos)
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 20:17:08