首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >RecyclerView onRestoreInstanceState Not不工作

RecyclerView onRestoreInstanceState Not不工作
EN

Stack Overflow用户
提问于 2019-01-18 21:42:05
回答 1查看 1.1K关注 0票数 0

摘要:这似乎是在底部工作表中托管RecyclerView HomeFragment 的一个问题,因为父片段承载子片段 ContentFragment 的另一个实例,该子片段没有嵌套在底层表中, onRestoreInstanceState 按预期执行。

期望的

当将RecyclerView LayoutManager的状态保存并返回到FragmentonSaveInstanceStateonViewStateRestored方法中时,预期的结果是RecyclerView将显示在与配置更改之前相同的位置。

观察到的

在屏幕配置更改时,RecyclerView有时会显示在位置,而不是在配置更改之前的RecyclerView位置。在某些情况下,它还成功地保留了预期的布局状态。由于随机性,这似乎涉及到生命周期+底片问题。

  • contentRecyclerView.layoutManager!!.onSaveInstanceState()onSaveInstanceState上记录为null。
  • savedRecyclerLayoutStateonViewStateRestored上记录为null。
  • savedRecyclerLayoutStateadapter加载数据后作为null记录在下面的observeContentUpdated中的SAVED.name案例中。

实现

层次结构

ContentFragmentHomeFragment托管在fragment_home布局中名为bottomSheetBottomSheet片段中。ContentFragmentfragment_content布局包含contentRecyclerView

加载保存的状态

onRestoreInstanceState的情况下,在observeContentUpdated中数据被加载到Adapter之后调用SAVED.name。在null之后,实例状态被设置为onRestoreInstanceState,因为RecyclerView中的单元格是不可接受的,并将导致数据再次加载。这确保只在配置更改后进行一次恢复。

HomeFragment.kt

initSavedBottomSheet创建包含保存的片段ContentFragment的底部工作表。

代码语言:javascript
复制
class HomeFragment : Fragment() {

...

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    outState.putParcelable(USER_KEY, user)
    outState.putBoolean(APP_BAR_EXPANDED_KEY, isAppBarExpanded)
    outState.putBoolean(SAVED_CONTENT_EXPANDED_KEY, isSavedContentExpanded)
}

override fun onViewStateRestored(savedInstanceState: Bundle?) {
    super.onViewStateRestored(savedInstanceState)
    if (savedInstanceState != null) {
        if (savedInstanceState.getBoolean(APP_BAR_EXPANDED_KEY)) appBar.setExpanded(true)
        else appBar.setExpanded(false)
        if (savedInstanceState.getBoolean(SAVED_CONTENT_EXPANDED_KEY)) {
            swipeToRefresh.isEnabled = false
            bottomSheetBehavior.state = STATE_EXPANDED
            setBottomSheetExpanded()
        }
        updateAds()
    }
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    homeViewModel = ViewModelProviders.of(activity!!).get(HomeViewModel::class.java)
}

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                          savedInstanceState: Bundle?): View? {
    binding = FragmentHomeBinding.inflate(inflater, container, false)
    binding.setLifecycleOwner(this)
    binding.viewmodel = homeViewModel
    return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    user = homeViewModel.getCurrentUser()
    ...
    observeSignIn(savedInstanceState)
    initSavedBottomSheet(savedInstanceState)
    ...
    initSwipeToRefresh()
    ...
}

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    if (savedInstanceState == null
            && childFragmentManager.findFragmentByTag(PRICEGRAPH_FRAGMENT_TAG) == null
            && childFragmentManager.findFragmentByTag(CONTENT_FEED_FRAGMENT_TAG) == null) {
        childFragmentManager.beginTransaction()
                .replace(priceContainer.id, PriceFragment.newInstance(), PRICEGRAPH_FRAGMENT_TAG)
                .commit()
        childFragmentManager.beginTransaction().replace(contentContainer.id,
                ContentFragment.newInstance(Bundle().apply {
                    putString(FEED_TYPE_KEY, MAIN.name)
                }), CONTENT_FEED_FRAGMENT_TAG)
                .commit()
    }
}

...

private fun initSavedBottomSheet(savedInstanceState: Bundle?) {
    bottomSheetBehavior = from(bottomSheet)
    bottomSheetBehavior.isHideable = false
    bottomSheetBehavior.peekHeight = SAVED_BOTTOM_SHEET_PEEK_HEIGHT
    bottomSheet.layoutParams.height = getDisplayHeight(context!!)
    if (savedInstanceState == null && homeViewModel.user.value == null)
        childFragmentManager.beginTransaction().replace(
                R.id.savedContentContainer,
                SignInDialogFragment.newInstance(Bundle().apply {
                    putInt(SIGNIN_TYPE_KEY, FULLSCREEN.code)
                }))
                .commit()
    bottomSheetBehavior.setBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
        override fun onStateChanged(bottomSheet: View, newState: Int) {
            if (newState == STATE_EXPANDED) {
                homeViewModel.bottomSheetState.value = STATE_EXPANDED
                setBottomSheetExpanded()
            }
            if (newState == STATE_COLLAPSED) {
                isSavedContentExpanded = false
                appBar.visibility = VISIBLE
                bottom_handle.visibility = VISIBLE
                bottom_handle_elevation.visibility = VISIBLE
            }
        }

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

private fun setBottomSheetExpanded() {
    isSavedContentExpanded = true
    appBar.visibility = GONE
    bottom_handle.visibility = GONE
    bottom_handle_elevation.visibility = GONE
}

private fun initSavedContentFragment() {
    childFragmentManager.beginTransaction().replace(
            savedContentContainer.id,
            ContentFragment.newInstance(Bundle().apply { putString(FEED_TYPE_KEY, SAVED.name) }),
            SAVED_CONTENT_TAG).commit()
}

...

private fun observeSignIn(savedInstanceState: Bundle?) {
    homeViewModel.user.observe(this, Observer { user: FirebaseUser? ->
        this.user = user
        ...
        if (user != null) { // Signed in.
            ...
            if (savedInstanceState == null || savedInstanceState.getParcelable<FirebaseUser>(USER_KEY) == null) {
                initMainContent()
                initSavedContentFragment()
            }
        } else if (savedInstanceState == null)  /*Signed out.*/ initMainContent()
    })
}

private fun initMainContent() {
    (childFragmentManager.findFragmentById(R.id.contentContainer) as ContentFragment)
            .initMainContent(false)
}

fun initSwipeToRefresh() {
    homeViewModel.isSwipeToRefreshEnabled.observe(viewLifecycleOwner, Observer { isEnabled: Boolean ->
        ...
        (childFragmentManager.findFragmentById(R.id.priceContainer) as PriceFragment)
                .getPrices(false, false)
        if (homeViewModel.accountType.value == FREE) updateAds()
    }
}

private fun updateAds() {
    (childFragmentManager.findFragmentById(R.id.contentContainer) as ContentFragment)
            .updateAds(true)
    if (childFragmentManager.findFragmentById(R.id.savedContentContainer) as ContentFragment != null)
        (childFragmentManager.findFragmentById(R.id.savedContentContainer) as ContentFragment)
                .updateAds(true)
}

...
}

ContentFragment.kt

contentRecyclerViewinitializeAdapters方法中填充。

代码语言:javascript
复制
class ContentFragment : Fragment() {

...

private var savedRecyclerLayoutState: Parcelable? = null

companion object {
    @JvmStatic
    fun newInstance(contentBundle: Bundle) = ContentFragment().apply {
        arguments = contentBundle
    }
}

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
        if (contentRecyclerView != null)
                outState.putParcelable(CONTENT_RECYCLER_VIEW_STATE,
                        contentRecyclerView.layoutManager!!.onSaveInstanceState())
}

override fun onViewStateRestored(savedInstanceState: Bundle?) {
    super.onViewStateRestored(savedInstanceState)
    if (savedInstanceState != null) {
        savedRecyclerLayoutState = savedInstanceState.getParcelable(CONTENT_RECYCLER_VIEW_STATE)
    }
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    feedType = ContentFragmentArgs.fromBundle(arguments!!).feedType
    analytics = getInstance(FirebaseApp.getInstance()!!.applicationContext)
    contentViewModel = ViewModelProviders.of(this).get(ContentViewModel::class.java)
    homeViewModel = ViewModelProviders.of(activity!!).get(HomeViewModel::class.java)
    contentViewModel.feedType = feedType
    if (savedInstanceState == null) homeViewModel.isRealtime.observe(this, Observer { isRealtime: Boolean ->
        when (feedType) {
            SAVED.name, DISMISSED.name -> initCategorizedContent(feedType, homeViewModel.user.value!!.uid)
        }
    })
}

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                          savedInstanceState: Bundle?): View? {
    analytics.setCurrentScreen(activity!!, feedType, null)
    binding = FragmentContentBinding.inflate(inflater, container, false)
    binding.setLifecycleOwner(this)
    binding.viewmodel = contentViewModel
    binding.actionbar.viewmodel = contentViewModel
    binding.emptyContent.viewmodel = contentViewModel
    return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    setToolbar()
    initializeAdapters()
}

override fun onDestroy() {
    moPubAdapter.destroy()
    compositeDisposable.dispose()
    super.onDestroy()
}

fun setToolbar() {
    when (feedType) {
        SAVED.name -> {
            binding.actionbar.toolbar.savedContentTitle.visibility = View.VISIBLE
        }
        DISMISSED.name -> {
            binding.actionbar.toolbar.title = getString(R.string.dismissed)
            (activity as AppCompatActivity).setSupportActionBar(binding.actionbar.toolbar)
            (activity as AppCompatActivity).supportActionBar!!.setDisplayHomeAsUpEnabled(true)
        }
    }
}

fun initMainContent(isRealtime: Boolean) {
    contentViewModel.initializeMainContent(isRealtime).observe(viewLifecycleOwner, Observer { status ->
        if (status == SUCCESS && homeViewModel.accountType.value == FREE) updateAds(true)
    })
}

fun initCategorizedContent(feedType: String, userId: String) {
    contentViewModel.initCategorizedContent(feedType, userId)
}

fun updateAds(toLoad: Boolean) {
    var toLoad = toLoad
    moPubAdapter.loadAds(AD_UNIT_ID)
    moPubAdapter.setAdLoadedListener(object : MoPubNativeAdLoadedListener {
        override fun onAdRemoved(position: Int) {}
        override fun onAdLoaded(position: Int) {
            if (toLoad) {
                moPubAdapter.notifyDataSetChanged()
                toLoad = false
            }
        }
    })
}

private fun initializeAdapters() {
    contentRecyclerView.layoutManager = LinearLayoutManager(context)
    populateAdapterType()
    observeContentUpdated()
    ...
}

private fun observeContentUpdated() {
    when (feedType) {
        MAIN.name -> {
            contentViewModel.getMainContentList().observe(viewLifecycleOwner, Observer { homeContentList ->
                adapter.submitList(homeContentList)
                if (homeContentList.isNotEmpty()) {
                    emptyContent.visibility = GONE
                    if (savedRecyclerLayoutState != null) {
                        contentRecyclerView.layoutManager?.onRestoreInstanceState(savedRecyclerLayoutState)
                        savedRecyclerLayoutState = null
                    }
                }
            })
        }
        SAVED.name, DISMISSED.name -> {
            contentViewModel.getCategorizedContentList(
                    if (feedType == SAVED.name) SAVED
                    else if (feedType == DISMISSED.name) DISMISSED
                    else NONE
            ).observe(viewLifecycleOwner, Observer { contentList ->
                adapter.submitList(contentList)
                if (!(contentList.size == 0 && (adapter.itemCount == 1 || adapter.itemCount == 0))) {
                    emptyContent.visibility = GONE
                    if (feedType == SAVED.name) {
                        if (savedRecyclerLayoutState != null) {
                            contentRecyclerView.layoutManager?.onRestoreInstanceState(savedRecyclerLayoutState)
                            savedRecyclerLayoutState = null
                        }
                    }
                    if (feedType == DISMISSED.name)
                        contentRecyclerView.layoutManager?.onRestoreInstanceState(savedRecyclerLayoutState)
                } 
            })
        }
    }
}

private fun populateAdapterType() {
    adapter = ContentAdapter(contentViewModel)
    // FREE
    if (homeViewModel.accountType.value!! == FREE) {
        moPubAdapter = MoPubRecyclerAdapter(activity!!, adapter,
                MoPubNativeAdPositioning.MoPubServerPositioning())
    ...            
        contentRecyclerView.adapter = moPubAdapter
        // Realtime, only need to set ads once.
        if (feedType == SAVED.name || feedType == DISMISSED.name) moPubAdapter.loadAds(AD_UNIT_ID)
    } /* PAID */ else contentRecyclerView.adapter = adapter
    ItemTouchHelper(homeViewModel).build(context!!, FREE, feedType, adapter, moPubAdapter, fragmentManager!!)
            .attachToRecyclerView(contentRecyclerView)
}

...

}

fragment_home.xml

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
    <variable
        name="viewmodel"
        type="app.coinverse.home.HomeViewModel" />
</data>

<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
    android:id="@+id/swipeToRefresh"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.google.android.material.appbar.AppBarLayout
            android:id="@+id/appBar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/white">

            <com.google.android.material.appbar.CollapsingToolbarLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:fitsSystemWindows="true"
                app:layout_scrollFlags="scroll|snap">

                <androidx.appcompat.widget.Toolbar
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize">

                    <androidx.constraintlayout.widget.ConstraintLayout
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:paddingTop="@dimen/padding_small"
                        android:paddingRight="@dimen/padding_small">

                        <ImageView
                            android:id="@+id/profileButton"
                            android:layout_width="@dimen/toolbar_button_dimen"
                            android:layout_height="@dimen/toolbar_button_dimen"
                            android:layout_gravity="start"
                            android:contentDescription="@string/profile_content_description"
                            android:src="@drawable/ic_astronaut_color_accent_24dp"
                            app:layout_constraintLeft_toLeftOf="parent" />

                    </androidx.constraintlayout.widget.ConstraintLayout>

                </androidx.appcompat.widget.Toolbar>

                <FrameLayout
                    android:id="@+id/priceContainer"
                    android:name="app.carpecoin.PriceDataFragment"
                    android:layout_width="match_parent"
                    android:layout_height="@dimen/price_graph_height"
                    app:layout_collapseMode="parallax"
                    app:layout_constraintLeft_toLeftOf="parent"
                    app:layout_constraintRight_toRightOf="parent"
                    app:layout_constraintTop_toBottomOf="parent"
                    app:layout_constraintTop_toTopOf="parent" />

            </com.google.android.material.appbar.CollapsingToolbarLayout>

        </com.google.android.material.appbar.AppBarLayout>

        <FrameLayout
            android:id="@+id/contentContainer"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior" />

        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/bottomSheet"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:paddingBottom="@dimen/margin_large"
            app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">

            <ImageView
                android:id="@+id/bottom_handle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/ic_bottom_sheet_handle"
                android:contentDescription="@string/saved_bottomsheet_handle_content_description"
                android:elevation="@dimen/bottom_sheet_elevation_height"
                android:src="@drawable/ic_save_planet_dark_48dp"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <ImageView
                android:id="@+id/bottom_handle_elevation"
                android:layout_width="0dp"
                android:layout_height="@dimen/bottom_sheet_elevation_height"
                android:background="@color/bottom_sheet_handle_elevation"
                android:contentDescription="@string/saved_bottomsheet_handle_content_description"
                app:layout_constraintBottom_toBottomOf="@id/bottom_handle"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent" />

            <FrameLayout
                android:id="@+id/savedContentContainer"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:background="@android:color/white"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintTop_toBottomOf="@id/bottom_handle_elevation" />

        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

fragment_content.xml

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<data>

    <variable
        name="viewmodel"
        type="app.coinverse.content.ContentViewModel" />

</data>


<androidx.coordinatorlayout.widget.CoordinatorLayout
    android:id="@+id/contentFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <include
            android:id="@+id/actionbar"
            layout="@layout/toolbar"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/contentRecyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_below="@id/actionbar" />

        <include
            android:id="@+id/emptyContent"
            layout="@layout/empty_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_below="@id/actionbar" />

    </RelativeLayout
</androidx.coordinatorlayout.widget.CoordinatorLayout>

</layout>
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2019-01-19 04:00:41

作为保存RecyclerView状态的解决办法,可以将该位置保存在实例状态中。

代码语言:javascript
复制
override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    if (contentRecyclerView != null)
        when (feedType) {
            MAIN.name, DISMISSED.name ->
                outState.putParcelable(CONTENT_RECYCLER_VIEW_STATE,
                        contentRecyclerView.layoutManager!!.onSaveInstanceState())
            SAVED.name ->
                outState.putInt(CONTENT_RECYCLER_VIEW_POSITION,
                        (contentRecyclerView.layoutManager as LinearLayoutManager)
                                .findLastVisibleItemPosition())
        }
}

override fun onViewStateRestored(savedInstanceState: Bundle?) {
    super.onViewStateRestored(savedInstanceState)
    if (savedInstanceState != null)
        when (feedType) {
            MAIN.name, DISMISSED.name -> savedRecyclerLayoutState = savedInstanceState.getParcelable(CONTENT_RECYCLER_VIEW_STATE)
            SAVED.name -> savedRecyclerPosition = savedInstanceState.getInt(CONTENT_RECYCLER_VIEW_POSITION)
        }
}

为了确保保存的索引不超出范围,需要进行检查。另外,由于RecyclerView项被取消,所以清除保存的索引位置非常重要,这样就不会在一个项目被取消后更新RecyclerView,因为这个代码片段包含在LiveData观察者中。

代码语言:javascript
复制
if (feedType == SAVED.name && savedRecyclerPosition != 0) {
                        val position: Int =
                                if (savedRecyclerPosition >= adapter.itemCount) adapter.itemCount - 1
                                else savedRecyclerPosition
                        contentRecyclerView.layoutManager?.scrollToPosition(position)
                        savedRecyclerPosition = 0
                    }
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/54261795

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档