带有片段和 Jetpack 导航的 Viewpager2:还原片段而不是重新创建它们 [英] Viewpager2 with fragments and Jetpack navigation: Restore fragments instead of recreating them

本文介绍了带有片段和 Jetpack 导航的 Viewpager2:还原片段而不是重新创建它们的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在 Fragment 中有一个 Viewpager2(我们称之为 HomeFragment).Viewpager 本身也包含 Fragments.当我离开 HomeFragment 时,它的视图将被破坏,当我向后导航时,视图将被重新创建.现在我在 onViewCreated() 期间在 HomeFragment 中设置了 Viewpager2 的适配器.因此,当我导航回 HomeFragment 时,将重新创建适配器,这也会重新创建 Viewpager2 中的所有 Fragments 并将当前项目重置为0. 如果我尝试重用我在第一次创建 HomeFragment 时实例化的适配器,我会收到一个异常,因为在 FragmentStateAdapter 内部进行了检查:

I have a Viewpager2 inside a Fragment (lets call it HomeFragment). That Viewpager itself also contains Fragments. When I navigate away from the HomeFragment its view will be destroyed and when I navigate back the view will be recreated. Now I set the adapter of the Viewpager2 in the HomeFragment during onViewCreated(). Therefore the adapter will be recreated when I navigate back to the HomeFragment, which also recreates all Fragments in the Viewpager2 and the current item is reset to 0. If i try to re-use the adapter that I instantiated on the first creation of the HomeFragmenti get an exception, because of this check inside of the FragmentStateAdapter:

public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
        checkArgument(mFragmentMaxLifecycleEnforcer == null);

有人知道如何在导航回来时防止重新创建所有内容吗?否则,这是一个相当大的性能开销并阻碍我的用户体验.

Does anybody have an idea how I can prevent recreating everything when navigating back? Otherwise this is a pretty big performance overhead and hinders my UX.

推荐答案

我已经花了一些时间来解决这个问题,我已经为任何需要听到它的人诊断了这个问题.我尽量保持我的解决方案尽可能传统.如果我们查看您的声明:

I've spent a bit of time with this, and I've diagnosed the problem for anyone who needs to hear it. I tried to keep my solution as conventional as possible. If we look at your statement:

因此,当我导航回HomeFragment,它还会重新创建 Viewpager2 中的所有 Fragment 和当前项被重置为 0.

Therefore the adapter will be recreated when I navigate back to the HomeFragment, which also recreates all Fragments in the Viewpager2 and the current item is reset to 0.

问题是当前项被重置为 0,因为您的适配器基于 off-of 的列表被重新创建.要解决此问题,我们不需要保存适配器,只需保存其中的数据.考虑到这一点,解决问题根本不困难.

The problem is that the current item is reset to 0, because the list that your adapter is based off-of is recreated. To resolve the issue, we don't need to save the adapter, just the data inside of it. With that in mind, solving the problem is not difficult at all.

让我们布局一些定义:

  • HomeFragment,正如你所说的,是你的 ViewPager2 的宿主,
  • MainActivity 是正在运行的活动,它承载着 HomeFragment 和所有在其中创建的片段
  • 我们正在对 MyFragment 的实例进行分页.您甚至可以有不止一种类型的片段供您翻阅,但这超出了本示例的范围.
  • PagerAdapter 是你的 FragmentStateAdapter,它是 HomeFragmentViewPager2 的适配器.
  • HomeFragment is, as you've said, the host of your ViewPager2,
  • MainActivity is the running activity which hosts HomeFragment and all created fragments inside of it
  • We are paging through instances of MyFragment. You could even have more than one type of fragment that you page through, but that's beyond the scope of this example.
  • PagerAdapter is your FragmentStateAdapter, which is the adapter for HomeFragment's ViewPager2.

在此示例中,MyFragment 具有构造函数 constructor(id : Int).然后,PagerAdapter 可能会显示如下:

In this example, MyFragment has the constructor constructor(id : Int). Then, PagerAdapter is probably going to appear as follows:

class PagerAdapter(fm : Fragment) : FragmentStateAdapter(fm){
    
    var ids : List<Int> = listOf()

    ...
    
    override fun createFragment(position : Int) : Fragment{
        return MyFragment(ids[position])
    }
    

}

我们面临的问题是,每次重新创建 PagerAdapter 时都会调用构造函数,并且该构造函数,正如我们在上面看到的,会将 ids 设置为空列表.

The problem that we are facing is every time you recreate PagerAdapter the constructor is called and that constructor, as we can see above, sets ids to an empty list.

我的第一个想法是也许我可以将 fm 切换为 MainActivity.我没有导航出 MainActivity 所以我不知道为什么,但这个解决方案不起作用.

My first thought was that maybe I could switch fm to be MainActivity. I don't navigate out of MainActivity so I'm not sure why, but this solution doesn't work.

相反,您需要做的是从 PagerAdapter 中抽象出数据.创建一个视图模型":

Instead, what you need to do is abstract the data out of PagerAdapter. Create a "viewModel":

    /* We do NOT extend ViewModel. This naming just indicates that this is your data- 
    storage vehicle for PagerAdapter*/
    data class PagerAdapterViewModel(
    var ids : List<Int> 
    )

然后,在PagerAdapter中,做如下调整:

Then, in PagerAdapter, make the following adjustments:

class PagerAdapter(
    fm : Fragment,
    private val viewModel : PagerAdapterViewModel 
) : FragmentStateAdapter(fm){
    
    // by creating custom getters and setters, you are migrating your code to this 
    // implementation without needing to adjust any code outside of the adapter 
    var ids : List<Int>
        get() = viewModel.ids 
        set(value) {viewModel.ids = value} 
    
    override fun createFragment(position : Int) : Fragment{
        return MyFragment(ids[position])
    }
    

}

最后,在 HomeFragment 中,您将拥有如下内容:

Finally, in HomeFragment, you'll have something like:

class HomeFragment : Fragment(){ 

    ... 

    /** Calling "by lazy" ensures that this object is only created once, and hence
    we retain the data stored in it, even when navigating away. */
    private val pagerAdapterViewModel : PagerAdapterViewModel by lazy{
        PagerAdapterViewModel(listOf())
    }

    private lateinit var pagerAdapter : PagerAdapter

    ...

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        ...
        pagerAdapter = PagerAdapter(this, pagerAdapterViewModel)
        pager.adapter = pagerAdapter 
        ...
    }
    
    ...

}

这篇关于带有片段和 Jetpack 导航的 Viewpager2:还原片段而不是重新创建它们的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆