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

208 天前
 lixyz

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

2823 次点击
所在节点    Android
10 条回复
murmurkerman
208 天前
简单讲一下 RecyclerView 和 ItemView 结构,是某个 Item 会动态更新么,类似于 Ai 聊天应用么。
lixyz
208 天前
@murmurkerman 就是一个类似于 AI 聊天的应用,item 是 TextView ,然后 SSE 流式更新这个 TextView
想要实现当 TextView 高于屏幕高度时,随着文本的增加,自行向下滚动
现在通过 scrollToPositionWithOffset 可以实现滚动到底部,但是随着流式更新 TextView ,会导致上下一跳一跳的
大佬有没有什么解决方案啊
sankemao
208 天前
recyclerView 设置反向排列试过吗
murmurkerman
208 天前
用正序的 recycler view 比较合适。消息高度超过限制和不再自动滚动到底部。逆序的话要计算 scroll range 的变化,然后滚动变化的部分。
lixyz
208 天前
@sankemao @murmurkerman 因为还有其他类型的卡片以及交互,所以没有尝试改变 rv 的渲染方向,实在不行我试一下吧,多谢二位
qwwuyu
208 天前
试了一下,可以一直保持在底部,不会上下跳动,我这里没有去计算 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
208 天前
好久没写 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
207 天前
@qwwuyu 多谢大佬,昨天试了一下您的方案,闪动稍有缓解,但还存在
@murmurkerman 多谢大佬,我今天抽空按照您的方案改造一下

二位给我 V 还是啥?我给二位点奶茶喝!
qwwuyu
207 天前
@lixyz 还是 7L 方案靠谱,试了加 LastItem,效果很好,也不用写那么多额外代码了,奶茶还是让给 7L 吧~
murmurkerman
207 天前
@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