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

BottomSheet内嵌RecyclerView的onRestoreInstanceState失效问题

RecyclerView Scroll Position Restoration Fails Unpredictably When Fragment Is Nested in BottomSheet

I’ve run into this exact frustrating issue before, and that random success/failure you’re seeing is definitely tied to how BottomSheet interacts with the fragment lifecycle during configuration changes. Let’s break down what’s happening and how to fix it:

The Problem Breakdown

  • Setup: Your ContentFragment (with its contentRecyclerView) is hosted inside a BottomSheet in HomeFragment’s layout. When this same fragment is used outside the BottomSheet, scroll position restores perfectly every time.
  • Symptom: After a config change (like screen rotation), the RecyclerView sometimes jumps back to position 0—even though:
    • You’re successfully saving the layout state in onSaveInstanceState
    • The saved state is non-null in onViewStateRestored and even after loading data into the adapter
  • Root Cause: BottomSheet’s lifecycle and layout timing don’t align with standard fragment behavior. When you try to restore the RecyclerView state right after loading data, the BottomSheet might still be collapsing/transitioning, or the RecyclerView hasn’t finished measuring its final visible size. This means the layout manager can’t properly apply the saved state.

Reliable Fixes to Try

1. Delay Restoration Until the BottomSheet Is Fully Expanded

BottomSheet’s state restoration happens after the fragment’s view state is restored. Wait until it’s fully expanded before applying the RecyclerView state:

// In ContentFragment's onViewCreated
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    
    // Get reference to the BottomSheetBehavior (adjust based on your layout structure)
    val parentView = requireView().parent as View
    val bottomSheetBehavior = BottomSheetBehavior.from(parentView)
    
    bottomSheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
        override fun onStateChanged(bottomSheet: View, newState: Int) {
            if (newState == BottomSheetBehavior.STATE_EXPANDED && savedRecyclerLayoutState != null) {
                // Post to the RecyclerView's message queue to ensure layout is complete
                contentRecyclerView.post {
                    contentRecyclerView.layoutManager?.onRestoreInstanceState(savedRecyclerLayoutState)
                    savedRecyclerLayoutState = null // Prevent duplicate restoration
                }
            }
        }

        override fun onSlide(bottomSheet: View, slideOffset: Float) {}
    })
}

2. Wait for RecyclerView to Finish Layout

Even if the BottomSheet is expanded, the RecyclerView might not have finished measuring its size. Use a global layout listener to trigger restoration once the view is ready:

// In ContentFragment after your adapter is populated with data
private fun restoreRecyclerStateIfReady() {
    if (savedRecyclerLayoutState == null) return
    
    contentRecyclerView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            // Remove the listener to avoid repeated calls
            contentRecyclerView.viewTreeObserver.removeOnGlobalLayoutListener(this)
            
            contentRecyclerView.layoutManager?.onRestoreInstanceState(savedRecyclerLayoutState)
            savedRecyclerLayoutState = null
        }
    })
}

3. Combine Data Load and BottomSheet State Checks

For maximum reliability, only restore when both the data is loaded AND the BottomSheet is expanded:

// In ContentFragment
private var isDataLoaded = false
private var isBottomSheetExpanded = false
private var savedRecyclerLayoutState: Parcelable? = null

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    savedRecyclerLayoutState = contentRecyclerView.layoutManager?.onSaveInstanceState()
    outState.putParcelable("recycler_state", savedRecyclerLayoutState)
}

override fun onViewStateRestored(savedInstanceState: Bundle?) {
    super.onViewStateRestored(savedInstanceState)
    savedRecyclerLayoutState = savedInstanceState?.getParcelable("recycler_state")
}

// Call this after your adapter is populated with data
private fun onDataLoaded() {
    isDataLoaded = true
    tryRestoreState()
}

// Call this when BottomSheet reaches STATE_EXPANDED
private fun onBottomSheetExpanded() {
    isBottomSheetExpanded = true
    tryRestoreState()
}

private fun tryRestoreState() {
    if (isDataLoaded && isBottomSheetExpanded && savedRecyclerLayoutState != null) {
        contentRecyclerView.post {
            contentRecyclerView.layoutManager?.onRestoreInstanceState(savedRecyclerLayoutState)
            savedRecyclerLayoutState = null
        }
    }
}

Why This Works

The core issue is timing: when you originally tried to restore the state right after data loading, the BottomSheet wasn’t fully ready to display the RecyclerView at its full size. By waiting for both the BottomSheet to be expanded and the RecyclerView to finish its layout, you ensure the layout manager has the correct view dimensions to apply the saved scroll position consistently every time.

内容的提问来源于stack exchange,提问作者adamhurwitz.eth

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 09:10:31