RecyclerView 流式更新 item,当 item 高度高于屏幕时,如何保证一直保持滚动到底部并且不闪动?

2025 年 2 月 13 日
 lixyz

rt 现在更新到底部时,会出现闪动的情况,想请问大佬们有没有什么解决方案?成功了请喝咖啡

4717 次点击
所在节点    Android
10 条回复
murmurkerman
2025 年 2 月 13 日
简单讲一下 RecyclerView 和 ItemView 结构,是某个 Item 会动态更新么,类似于 Ai 聊天应用么。
lixyz
2025 年 2 月 13 日
@murmurkerman 就是一个类似于 AI 聊天的应用,item 是 TextView ,然后 SSE 流式更新这个 TextView
想要实现当 TextView 高于屏幕高度时,随着文本的增加,自行向下滚动
现在通过 scrollToPositionWithOffset 可以实现滚动到底部,但是随着流式更新 TextView ,会导致上下一跳一跳的
大佬有没有什么解决方案啊
sankemao
2025 年 2 月 13 日
recyclerView 设置反向排列试过吗
murmurkerman
2025 年 2 月 13 日
用正序的 recycler view 比较合适。消息高度超过限制和不再自动滚动到底部。逆序的话要计算 scroll range 的变化,然后滚动变化的部分。
lixyz
2025 年 2 月 13 日
@sankemao @murmurkerman 因为还有其他类型的卡片以及交互,所以没有尝试改变 rv 的渲染方向,实在不行我试一下吧,多谢二位
qwwuyu
2025 年 2 月 13 日
试了一下,可以一直保持在底部,不会上下跳动,我这里没有去计算 TextView 改变后的高度,有需要可以自己监听控件高度变化,再去 scrollToPositionWithOffset.
代码图片 https://imgur.com/a/HU30yph
```
new Thread(() -> {
while (!isFinishing() && run) {
try {
Thread.sleep(200);
} catch (InterruptedException ignored) {
}
runOnUiThread(() -> {
if (textTv != null) {
textTv.setText(textTv.getText() + "asdasdasdasdasdasdasd");
int targetPosition = adapter.getItemCount() - 1;
View targetView = linearLayoutManager.findViewByPosition(targetPosition);
int offset = recyclerView.getHeight() - targetView.getHeight();
linearLayoutManager.scrollToPositionWithOffset(targetPosition, offset);
}
});
}
}).start();
```
murmurkerman
2025 年 2 月 13 日
好久没写 android view 哈哈哈,我写出来了,花掉了所有的摸鱼时间,https://github.com/Murmurl912/android_recycler_view_chat.git
总结下:
1. 去掉 RecyclerView 的 ItemAnimator
2. 在更新 Item 的时候,判断是否在底部,已经是底部的时候滚动到底部。
3. 我之前的方案是会加一个 FirstItem 和 LastItem 的占位 Item 到 RecyclerView 中,方便实现滚动到底部和顶部。
Core Code:
```kotlin

class MainActivity : ComponentActivity() {
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
private val adapter by lazy {
ItemAdapter()
}
private var id = 1L
private val items = LinkedHashMap<Long, Item>()
private var job: Job? = null
private val layoutManager by lazy {
binding.recyclerView.layoutManager as LinearLayoutManager
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(binding.root)
binding.root.fitsSystemWindows = true
binding.recyclerView.adapter = adapter
binding.recyclerView.itemAnimator = null
binding.generate.setOnClickListener {
if (job?.isActive == true) {
return@setOnClickListener
}
var item = Item.MessageItem(id++, "")
items[item.id] = item
updateItems()
job = lifecycleScope.launch {
MessageApi.generate()
.collect { text ->
item = item.copy(text = item.text + text, isGenerating = true).also {
items[it.id] = it
updateItems()
}
// scroll to bottom here
val isAtBottom =
layoutManager.findLastCompletelyVisibleItemPosition() == adapter.itemCount - 1
if (isAtBottom) {
layoutManager.scrollToPosition(adapter.itemCount - 1)
}
}
item = item.copy(isGenerating = false).also {
items[it.id] = it
updateItems()
}
}
}
}


private fun updateItems() {
adapter.updateItems(items.values.toMutableList()
.apply {
add(0, Item.FirstItem)
add(Item.LastItem)
}
)
}


}
```
lixyz
2025 年 2 月 14 日
@qwwuyu 多谢大佬,昨天试了一下您的方案,闪动稍有缓解,但还存在
@murmurkerman 多谢大佬,我今天抽空按照您的方案改造一下

二位给我 V 还是啥?我给二位点奶茶喝!
qwwuyu
2025 年 2 月 14 日
@lixyz 还是 7L 方案靠谱,试了加 LastItem,效果很好,也不用写那么多额外代码了,奶茶还是让给 7L 吧~
murmurkerman
2025 年 2 月 14 日
@lixyz item 有没有加 id 和 type

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1111093

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX