RecyclerView隐藏时ListAdapter数据不刷新致显示闪烁问题解决
你基于ListAdapter实现了标题-副标题列表组件,正常调用submitList()更新或清空列表都能正常生效,但当RecyclerView设为View.GONE时,submitList(null)的清空操作不会立即执行,重新显示RecyclerView时会先闪一下旧数据,再切换到新数据。伪代码如下:
suspend fun fetchItems() { adapter.submitList(null) showLoadingIndicator(); hideRecyclerView() // View.GONE val result = api.fetchItems() // coroutine adapter.submitList(result.items) hideLoadingIndicator(); showRecyclerView() }
原因分析
系统为优化性能,当View处于GONE状态时会跳过布局和绘制流程。ListAdapter的DiffUtil会在后台正常计算数据差异,但对应的UI更新(清空旧ViewHolder)会被推迟到View重新可见后执行。这就导致RecyclerView重新显示时,旧ViewHolder先被渲染,随后才更新为新数据,从而出现闪烁现象。
解决方案
方案1:利用submitList的回调确保更新完成后再切换可见性
ListAdapter的submitList()提供了带提交完成回调的重载方法,可借助该回调确保空列表或新数据的更新完成后,再切换RecyclerView的可见性:
suspend fun fetchItems() { // 提交空列表,等清空操作完成后再显示loading、隐藏列表 adapter.submitList(null) { showLoadingIndicator() hideRecyclerView() } val result = api.fetchItems() // 提交新数据,等更新完成后再隐藏loading、显示列表 adapter.submitList(result.items) { hideLoadingIndicator() showRecyclerView() } }
这种方式能保证RecyclerView隐藏时,空列表的更新已经生效;重新显示时,新数据的更新也已完成,不会出现旧数据残留。
方案2:移除提前清空步骤,直接提交新数据后再显示列表
如果不需要提前清空列表释放内存,可以简化流程,直接在请求完成后提交新数据,等待更新完成再显示RecyclerView:
suspend fun fetchItems() { showLoadingIndicator() hideRecyclerView() val result = api.fetchItems() // 提交新数据并等待更新完成,再显示列表 adapter.submitList(result.items) { hideLoadingIndicator() showRecyclerView() } }
ListAdapter会自动对比旧数据和新数据完成差异更新,更新完成后再显示列表,从根源避免旧数据闪烁。
方案3:强制触发RecyclerView的布局更新
如果必须保留原有流程,可以在显示RecyclerView后,强制触发布局更新,让ListAdapter的最新数据立即生效:
suspend fun fetchItems() { adapter.submitList(null) showLoadingIndicator() hideRecyclerView() val result = api.fetchItems() adapter.submitList(result.items) hideLoadingIndicator() showRecyclerView() // 强制触发布局计算,确保新数据立即渲染 recyclerView.requestLayout() }
requestLayout()会跳过系统的延迟优化,强制RecyclerView重新计算布局并绘制,直接应用最新数据。
方案4:替换View.GONE为View.INVISIBLE(备选)
如果布局允许,可将RecyclerView的隐藏方式从View.GONE改为View.INVISIBLE。INVISIBLE状态下View仍会占据布局空间,系统不会跳过其布局更新,submitList()的操作会立即生效,避免闪烁。不过这种方式可能影响loading的布局位置,需要根据实际UI调整。
内容的提问来源于stack exchange,提问作者rupinderjeet

