删除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快照监听会在所有数据变更(包括删除)时执行滚动逻辑,这是另一个触发滚动的根源。
具体修复步骤
区分首次加载与后续数据变更,仅首次执行滚动
添加一个成员变量标记是否为首次加载,只在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 } } }替换全局刷新为精确条目通知,避免触发滚动
删除操作时不要用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()中完成了精确通知。可选:删除前后手动保存恢复滚动位置
如果上述操作后仍有滚动问题,可以在删除前记录当前滚动位置,删除后恢复:// 删除前记录当前可见位置 val layoutManager = recyclerView.layoutManager as LinearLayoutManager val firstVisiblePos = layoutManager.findFirstVisibleItemPosition() // 执行删除操作... // 删除后恢复滚动位置 recyclerView.post { layoutManager.scrollToPosition(firstVisiblePos) }
内容的提问来源于stack exchange,提问作者Mahmoud Nabil
相关产品推荐
相关产品推荐

