带有片段和 Jetpack 导航的 Viewpager2:还原片段而不是重新创建它们 [英] Viewpager2 with fragments and Jetpack navigation: Restore fragments instead of recreating them
问题描述
我在 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 HomeFragment
i 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
,它是HomeFragment
的ViewPager2
的适配器.
HomeFragment
is, as you've said, the host of yourViewPager2
,MainActivity
is the running activity which hostsHomeFragment
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 yourFragmentStateAdapter
, which is the adapter forHomeFragment
'sViewPager2
.
在此示例中,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屋!