Search code examples
androidandroid-fragmentsandroid-viewpagerandroid-architecture-navigationstaggeredgridlayoutmanager

RecyclerView with StaggeredGridLayoutManager in ViewPager, arranges items automatically when going back to fragment


I am using Navigation component in my App, using google Advanced Sample(here). my problem is when going back to a fragment, the scrolling position does not lost but it rearranges items and moves highest visible items so that top of those item align to top of recyclerview. please see this:

enter image description here

before going to next fragment:

enter image description here

and after back to fragment:

enter image description here

this problem is matter because some times clicked item goes down and not seen until scroll down. how to prevent this behavior?

please consider:

  • this problem exist if using navigation component to change fragment. if start fragment using supportFragmentManager.beginTransaction() or start another activity and then go to this fragment it is OK. but if I navigate to another fragment using navigation component this problem is exist.(maybe because of recreating fragment)

  • also this problem exist if using fragment in ViewPager. i.e recyclerView is in a fragment that handle with ViewPagerAdapter and viewPager is in HomeFragment that opened with Navigation component. if recyclerView is in HomeFragment there is no problem.

  • no problem with LinearLayoutManager. only with StaggeredGridLayoutManager.

  • there is not difference if using ViewPager2 and also FragmentStatePagerAdapter

  • I try to prevent recreate of fragment(by this solution) but not solved.

UPDATE: you can clone project with this problem from here


Solution

  • When using Navigation Component + ViewPager + StaggeredGridLayoutManager, wrong recyclerView.computeVerticalScrollOffset() has been returned during Fragment recreate.

    In general, all layout managers bundled in the support library already know how to save and restore scroll position, but in this case, we had to take responsibility for this.

    class TestFragment : Fragment(R.layout.fragment_test) {
    
        private val testListAdapter: TestListAdapter by lazy {
            TestListAdapter()
        }
    
        private var layoutManagerState: Parcelable? = null
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
    
            postListView.apply {
                layoutManager = StaggeredGridLayoutManager(
                    2, StaggeredGridLayoutManager.VERTICAL
                ).apply {
                    gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
                }
                setHasFixedSize(true)
    
                adapter = testListAdapter
            }
    
            testListAdapter.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT
    
        }
    
        override fun onPause() {
            saveLayoutManagerState()
            super.onPause()
        }
    
        override fun onViewStateRestored(savedInstanceState: Bundle?) {
            super.onViewStateRestored(savedInstanceState)
            restoreLayoutManagerState()
        }
    
        private fun restoreLayoutManagerState () {
            layoutManagerState?.let { postListView.layoutManager?.onRestoreInstanceState(it) }
        }
    
        private fun saveLayoutManagerState () {
            layoutManagerState = postListView.layoutManager?.onSaveInstanceState()
        }
    }
    

    Source code: https://github.com/dautovicharis/MyStaggeredListSample/tree/q_65539771