RecyclerView.StateRestorationPolicy.PREVENT_WHEN_EMPTY失效,返回时滚动位置不恢复
RecyclerView返回Fragment后无法保留滚动位置的问题解决建议
问题背景
应用中的RecyclerView列表,在用户点击条目进入新Fragment、返回原Fragment时,无法保留之前的滚动位置。尝试过PREVENT_WHEN_EMPTY状态策略、保存布局管理器状态、记录滚动位置等方案均无效,相关代码如下:
Adapter定义
class Adapter(val clickListener: PreviewListener) : ListAdapter<DataItem, RecyclerView.ViewHolder>(EntryDiffCallback()) {
Adapter初始化(onViewCreated方法内)
private var previewAdapter: PreviewAdapter? = null if (previewAdapter == null) { previewAdapter = PreviewAdapter( PreviewListener { info -> previewViewModel.updateCurrentInfo(info) findNavController() .navigateSafely( PreviewFragmentDirections .actionToExerciseFragment()) }) previewAdapter?.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY } binding.previewListView.adapter = previewAdapter postponeEnterTransition()
列表数据填充逻辑
previewViewModel.listItems.observe( viewLifecycleOwner, Observer { list -> previewAdapter?.submitList(list) (view.parent as? ViewGroup)?.doOnPreDraw { startPostponedEnterTransition() } })
附加配置
- 使用了共享元素动画:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) sharedElementEnterTransition = MaterialContainerTransform().apply { duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong() scrimColor = Color.TRANSPARENT interpolator = DecelerateInterpolator() setAllContainerColors(requireContext().getColorFromAttr(R.attr.colorSurface)) } }
- 在
onDestroyView和onDestroy中清理数据,移除后仅触发LeakCanary泄漏提示,无其他改善:
override fun onDestroyView() { super.onDestroyView() binding.previewListView.adapter = null _binding = null } override fun onDestroy() { super.onDestroy() previewAdapter = null }
解决建议
1. 调整Adapter状态恢复策略
当前PREVENT_WHEN_EMPTY仅在列表为空时阻止状态恢复,返回时列表已有数据的情况下无法生效。尝试改为允许自动恢复:
previewAdapter?.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.ALLOW
2. 移除onDestroyView中清空Adapter的代码
清空Adapter会导致RecyclerView丢失状态引用,改为仅清空Binding,保留Adapter实例(若Fragment为复用状态):
override fun onDestroyView() { super.onDestroyView() _binding = null // 保留adapter引用,避免状态丢失 }
若担心泄漏,可将Adapter与ViewModel绑定,依托ViewModel的生命周期管理实例。
3. 手动控制布局管理器状态的保存与恢复
导航组件默认会保存视图状态,但动态布局管理器可能需要手动处理:
- 保存状态:在
onSaveInstanceState中记录布局状态
override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) binding.previewListView.layoutManager?.let { outState.putParcelable("recycler_state", it.onSaveInstanceState()) } }
- 恢复状态:在数据提交完成后恢复,避免因列表为空导致恢复无效
previewViewModel.listItems.observe(viewLifecycleOwner) { list -> previewAdapter?.submitList(list) { // 数据提交完成后恢复滚动位置 arguments?.getParcelable<Parcelable>("recycler_state")?.let { state -> binding.previewListView.layoutManager?.onRestoreInstanceState(state) } (view.parent as? ViewGroup)?.doOnPreDraw { startPostponedEnterTransition() } } }
4. 规避共享元素动画的干扰
共享元素动画可能与状态恢复冲突,可尝试延迟恢复滚动位置至动画结束:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // ... 初始化代码 ... postponeEnterTransition() view.postDelayed({ savedInstanceState?.getParcelable<Parcelable>("recycler_state")?.let { state -> binding.previewListView.layoutManager?.onRestoreInstanceState(state) } startPostponedEnterTransition() }, sharedElementEnterTransition?.duration ?: 300) }
5. 检查Fragment导航配置
- 确认导航图中对应action未设置
launchSingleTop="true",该属性可能导致Fragment实例被重建,丢失视图状态。 - 确保Fragment的
retainInstance属性为默认的false,retainInstance=true会导致视图销毁但实例保留,干扰状态保存逻辑。
内容的提问来源于stack exchange,提问作者Ana
相关产品推荐
相关产品推荐

