如何在ConcatAdapter中恢复嵌套RecyclerView的滚动状态
解决ConcatAdapter+嵌套RecyclerView的滚动状态恢复问题
我来帮你梳理下当前遇到的问题,以及对应的解决方案——核心问题在于滚动状态的保存没有持久化到系统的状态容器中,再加上ConcatAdapter的初始化方式和数据更新时机干扰了状态恢复流程,咱们一步步来调整:
一、先明确几个关键问题的根源
- 你之前在适配器里保存的
scrollStates是内存级别的,屏幕旋转时Fragment和适配器都会被重建,这部分状态直接丢失了,所以无法恢复。 stateRestorationPolicy = PREVENT_WHEN_EMPTY需要配合正确的状态保存时机,而且外层的RecyclerView和ConcatAdapter也需要同步配置,否则系统默认的状态恢复会覆盖你的自定义逻辑。- 频繁创建或替换ConcatAdapter/嵌套适配器,会导致RecyclerView无法追踪到之前的状态关联。
二、分步解决方案
1. 用Fragment的SavedStateHandle持久化滚动状态
我们把嵌套列表的滚动状态存在Fragment的SavedStateHandle里(或者onSaveInstanceState的Bundle),这样屏幕旋转、页面导航时状态不会丢失。
2. 优化ConcatAdapter的初始化逻辑
避免动态添加/替换适配器,提前初始化好ConcatAdapter和嵌套适配器,只更新数据,这样RecyclerView能一直保持状态关联。
3. 正确配置StateRestorationPolicy
给外层RecyclerView、ConcatAdapter、嵌套的所有适配器都设置PREVENT_WHEN_EMPTY,阻止系统在数据为空时自动恢复状态,让我们自己控制恢复时机。
三、修改后的代码示例
1. Fragment实现
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 初始化嵌套适配器和ConcatAdapter,提前绑定 val nestedListAdapter = NestedListAdapter(savedStateHandle) val concatAdapter = ConcatAdapter(nestedListAdapter) binding.recyclerView.apply { adapter = concatAdapter layoutManager = LinearLayoutManager(requireContext()) // 外层RecyclerView也禁用自动状态恢复,交给我们自己处理 stateRestorationPolicy = RecyclerView.StateRestorationPolicy.PREVENT_WHEN_EMPTY } // 统一配置所有适配器的状态恢复策略 concatAdapter.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY nestedListAdapter.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY viewModel.data.observe(viewLifecycleOwner, { listItems -> nestedListAdapter.updateItems(listItems) }) }
2. 嵌套RecyclerView适配器修改
class NestedListAdapter( private val savedStateHandle: SavedStateHandle, private val listItems: MutableList<Movie> = mutableListOf() ) : RecyclerView.Adapter<NestedListAdapter.ViewHolder>() { private val viewPool = RecyclerView.RecycledViewPool() // 用SavedStateHandle的key前缀统一管理所有嵌套列表的状态 private fun getStateKey(position: Int): String { return "nested_scroll_state_${listItems.getOrNull(position)?.id ?: position}" } fun updateItems(newItems: List<Movie>) { listItems.clear() listItems.addAll(newItems) notifyDataSetChanged() } override fun onViewRecycled(holder: ViewHolder) { super.onViewRecycled(holder) // 回收时把状态保存到SavedStateHandle val key = getStateKey(holder.layoutPosition) val state = holder.rv.layoutManager?.onSaveInstanceState() savedStateHandle[key] = state } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val binding = NestedListAdapterBinding.inflate( LayoutInflater.from(parent.context), parent, false ) return ViewHolder(binding) } override fun getItemCount(): Int = listItems.size override fun onBindViewHolder(holder: ViewHolder, position: Int) { holder.bind(listItems[position]) } inner class ViewHolder( binding: NestedListAdapterBinding ) : RecyclerView.ViewHolder(binding.root) { val rv = binding.recyclerView fun bind(movieSection: Movie) { val lm = LinearLayoutManager(rv.context, HORIZONTAL, false) lm.initialPrefetchItemCount = 4 val movieAdapter = MovieListAdapter(listOf(movieSection)) movieAdapter.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY rv.apply { setRecycledViewPool(viewPool) layoutManager = lm adapter = movieAdapter } // 绑定后立即恢复状态 restoreState(layoutPosition) } private fun restoreState(position: Int) { val key = getStateKey(position) val state = savedStateHandle.get<Parcelable>(key) if (state != null) { rv.layoutManager?.onRestoreInstanceState(state) } else { val padding = rv.resources.getDimension(R.dimen.default_padding).toInt() rv.addItemDecoration(HorizontalListMarginDecoration(padding)) rv.layoutManager?.scrollToPosition(0) } } } }
3. 嵌套的MovieListAdapter补充
确保MovieListAdapter也设置状态恢复策略:
class MovieListAdapter(private val items: List<Movie>) : RecyclerView.Adapter<MovieListAdapter.ViewHolder>() { init { stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY } // ... 其他实现代码 }
四、额外注意事项
- 如果使用Android Navigation组件,确保Fragment的
retainInstance不要设置为true,否则会干扰状态保存流程。 - 避免在
onBindViewHolder里重复创建LinearLayoutManager,可以提前缓存或者用RecycledViewPool复用(不过你已经用了ViewPool,这部分没问题)。 - 测试时注意:屏幕旋转前确保嵌套列表已经滚动过,数据已经加载完成,否则状态恢复没有意义。
内容的提问来源于stack exchange,提问作者Tim
相关产品推荐
相关产品推荐

