You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ConcatAdapter中恢复嵌套RecyclerView的滚动状态

解决ConcatAdapter+嵌套RecyclerView的滚动状态恢复问题

我来帮你梳理下当前遇到的问题,以及对应的解决方案——核心问题在于滚动状态的保存没有持久化到系统的状态容器中,再加上ConcatAdapter的初始化方式和数据更新时机干扰了状态恢复流程,咱们一步步来调整:

一、先明确几个关键问题的根源

  1. 你之前在适配器里保存的scrollStates是内存级别的,屏幕旋转时Fragment和适配器都会被重建,这部分状态直接丢失了,所以无法恢复。
  2. stateRestorationPolicy = PREVENT_WHEN_EMPTY需要配合正确的状态保存时机,而且外层的RecyclerView和ConcatAdapter也需要同步配置,否则系统默认的状态恢复会覆盖你的自定义逻辑。
  3. 频繁创建或替换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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 18:18:09