我在Fragment
中有一个Viewpager2
(让我们称它为HomeFragment
)。该Viewpager
本身也包含Fragments
。当我离开HomeFragment
时,它的视图将被销毁,当我导航回来时,该视图将被重新创建。现在,我在onViewCreated()
期间在HomeFragment
中设置了Viewpager2
的适配器。因此,当我导航回HomeFragment
时,将重新创建适配器,这也会重新创建Viewpager2
中的所有Fragments
,并且当前项将重置为0。如果我尝试重用在第一次创建HomeFragment
时实例化的适配器,则会得到一个异常,因为在FragmentStateAdapter
中进行了此检查
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
checkArgument(mFragmentMaxLifecycleEnforcer == null);
有人知道如何防止在返回时重新创建所有内容吗?否则,这是一个相当大的性能开销,并阻碍我的用户体验。
发布于 2020-10-30 15:04:41
我在这方面花了一些时间,我已经为任何需要听到它的人诊断了这个问题。我试着让我的解决方案尽可能的传统。如果我们看一下你的声明:
因此,当我导航回HomeFragment时,适配器将被重新创建,这也将重新创建Viewpager2中的所有片段,并且当前项被重置为0。
问题是当前项被重置为0,因为重新创建了适配器所基于的列表。要解决这个问题,我们不需要保存适配器,只需要保存其中的数据。考虑到这一点,解决问题一点也不难。
让我们来布置一些定义:
正如你所说,
HomeFragment
是你的ViewPager2
的主机,MainActivity
是运行的活动,它托管了HomeFragment
和它里面所有创建的片段,MyFragment
的实例。您甚至可以通过多种类型的片段进行分页,但这超出了您的ViewPager2
.的适配器FragmentStateAdapter
的范围,这超出了example.
PagerAdapter
的范围在本例中,MyFragment
具有构造函数constructor(id : Int)
。然后,PagerAdapter
可能会出现如下所示:
class PagerAdapter(fm : Fragment) : FragmentStateAdapter(fm){
var ids : List<Int> = listOf()
...
override fun createFragment(position : Int) : Fragment{
return MyFragment(ids[position])
}
}
我们面临的问题是,每次重新创建PagerAdapter
时,构造函数都会被调用,正如我们在上面看到的那样,该构造函数会将ids
设置为一个空列表。
我的第一个想法是,也许我可以把fm
换成MainActivity
。我不会离开MainActivity
,所以我不知道为什么,但是这个解决方案不起作用。
相反,您需要做的是从PagerAdapter
中提取数据。创建一个"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
中进行以下调整:
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
中,您将拥有如下内容:
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
...
}
...
}
发布于 2020-12-28 12:44:58
您可以将ViewPager的初始页面作为NavHostFragment,这些页面有自己的后端堆栈,这将导致在下面的gif中实现
为每个选项卡创建一个NavHost片段,也可以有一个通用的选项卡来添加它
/**
* Using [FragmentStateAdapter.registerFragmentTransactionCallback] with [FragmentStateAdapter] solves back navigation instead of using [OnBackPressedCallback.handleOnBackPressed] in every [NavHostFragment]
* ### Should set app:defaultNavHost="true" for [NavHostFragment] for this to work
*/
class DashboardNavHostFragment : BaseDataBindingFragment<FragmentNavhostDashboardBinding>() {
override fun getLayoutRes(): Int = R.layout.fragment_navhost_dashboard
private var navController: NavController? = null
private val nestedNavHostFragmentId = R.id.nested_nav_host_fragment_dashboard
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val nestedNavHostFragment =
childFragmentManager.findFragmentById(nestedNavHostFragmentId) as? NavHostFragment
navController = nestedNavHostFragment?.navController
}
}
此片段的布局
<?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">
<androidx.constraintlayout.widget.ConstraintLayout
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:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:layout_constraintEnd_toEndOf="parent"
android:background="#0D47A1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="@style/ThemeOverlay.AppCompat.ActionBar" />
</com.google.android.material.appbar.AppBarLayout>
<fragment
android:id="@+id/nested_nav_host_fragment_dashboard"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/appbar"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph_dashboard"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
并为仪表板的每个页面创建一个导航图,正如您在上面看到的,我们需要nav_graph_dashboard.
此页的图形为
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph_dashboard"
app:startDestination="@id/dashboardFragment1">
<fragment
android:id="@+id/dashboardFragment1"
android:name="com.smarttoolfactory.tutorial6_4_navigationui_viewpager_fragmenttoolbar_nested_navigation.blankfragment.DashboardFragment1"
android:label="DashboardFragment1"
tools:layout="@layout/fragment_dashboard1">
<action
android:id="@+id/action_dashboardFragment1_to_dashboardFragment2"
app:destination="@id/dashboardFragment2" />
</fragment>
<fragment
android:id="@+id/dashboardFragment2"
android:name="com.smarttoolfactory.tutorial6_4_navigationui_viewpager_fragmenttoolbar_nested_navigation.blankfragment.DashboardFragment2"
android:label="DashboardFragment2"
tools:layout="@layout/fragment_dashboard2">
<action
android:id="@+id/action_dashboardFragment2_to_dashboardFragment3"
app:destination="@id/dashboardFragment3" />
</fragment>
<fragment
android:id="@+id/dashboardFragment3"
android:name="com.smarttoolfactory.tutorial6_4_navigationui_viewpager_fragmenttoolbar_nested_navigation.blankfragment.DashboardFragment3"
android:label="DashboardFragment3"
tools:layout="@layout/fragment_dashboard3" >
<action
android:id="@+id/action_dashboardFragment3_to_dashboardFragment1"
app:destination="@id/dashboardFragment1"
app:popUpTo="@id/dashboardFragment1"
app:popUpToInclusive="true" />
</fragment>
</navigation>
让我们将这些NavHostFragments与FragmentStateAdapter合并,并实现默认情况下不起作用的back press导航。
/**
* FragmentStateAdapter to contain ViewPager2 fragments inside another fragment.
*
* * Create FragmentStateAdapter with viewLifeCycleOwner instead of Fragment to make sure
* that it lives between [Fragment.onCreateView] and [Fragment.onDestroyView] while [View] is alive
*
* * https://stackoverflow.com/questions/61779776/leak-canary-detects-memory-leaks-for-tablayout-with-viewpager2
*/
class ChildFragmentStateAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) :
FragmentStateAdapter(fragmentManager, lifecycle) {
init {
// Add a FragmentTransactionCallback to handle changing
// the primary navigation fragment
registerFragmentTransactionCallback(object : FragmentTransactionCallback() {
override fun onFragmentMaxLifecyclePreUpdated(
fragment: Fragment,
maxLifecycleState: Lifecycle.State
) = if (maxLifecycleState == Lifecycle.State.RESUMED) {
// This fragment is becoming the active Fragment - set it to
// the primary navigation fragment in the OnPostEventListener
OnPostEventListener {
fragment.parentFragmentManager.commitNow {
setPrimaryNavigationFragment(fragment)
}
}
} else {
super.onFragmentMaxLifecyclePreUpdated(fragment, maxLifecycleState)
}
})
}
override fun getItemCount(): Int = 3
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> HomeNavHostFragment()
1 -> DashboardNavHostFragment()
else -> NotificationHostFragment()
}
}
}
您还需要注意内存泄漏,因此如果您的ViewPager2
本身在Fragment
中,请使用viewLifecycleOwner而不是lifeycleOwner
。
您可以在此tutorial link中查看其他示例和更多内容。
发布于 2021-04-02 07:40:38
我试着设置
viewPager2.setOffscreenPageLimit(ViewPager2.OFFSCREEN PAGE LIMIT_DEFAULT);
在那之后,碎片的行为变得正常。
有关OffscreenPageLimit here的更多信息
https://stackoverflow.com/questions/57271553
复制相似问题