在使用 BottomNavigationView 和新的 NavController 时,有没有保持片段活性的方法?

我正在尝试使用新的导航组件

但是,当我切换片段时,它们每次都会被破坏/创建,即使它们以前使用过。

有没有办法保持我们的主要片段链接到我们的 BottomNavigationView?

45730 次浏览

试试这个。

领航员

创建自定义导航器。

@Navigator.Name("custom_fragment")  // Use as custom tag at navigation.xml
class CustomNavigator(
private val context: Context,
private val manager: FragmentManager,
private val containerId: Int
) : FragmentNavigator(context, manager, containerId) {


override fun navigate(destination: Destination, args: Bundle?, navOptions: NavOptions?) {
val tag = destination.id.toString()
val transaction = manager.beginTransaction()


val currentFragment = manager.primaryNavigationFragment
if (currentFragment != null) {
transaction.detach(currentFragment)
}


var fragment = manager.findFragmentByTag(tag)
if (fragment == null) {
fragment = destination.createFragment(args)
transaction.add(containerId, fragment, tag)
} else {
transaction.attach(fragment)
}


transaction.setPrimaryNavigationFragment(fragment)
transaction.setReorderingAllowed(true)
transaction.commit()


dispatchOnNavigatorNavigated(destination.id, BACK_STACK_DESTINATION_ADDED)
}
}

NavHostFragment

创建自定义 NavHostFragment。

class CustomNavHostFragment: NavHostFragment() {
override fun onCreateNavController(navController: NavController) {
super.onCreateNavController(navController)
navController.navigatorProvider += PersistentNavigator(context!!, childFragmentManager, id)
}
}

Xml

使用自定义标记代替片段标记。

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/navigation"
app:startDestination="@id/navigation_first">


<custom_fragment
android:id="@+id/navigation_first"
android:name="com.example.sample.FirstFragment"
android:label="FirstFragment" />
<custom_fragment
android:id="@+id/navigation_second"
android:name="com.example.sample.SecondFragment"
android:label="SecondFragment" />
</navigation>

活动布局

使用 CustomNavHostFragment 而不是 NavHostFragment。

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">


<fragment
android:id="@+id/nav_host_fragment"
android:name="com.example.sample.CustomNavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/bottom_navigation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/navigation" />


<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="@menu/navigation" />


</androidx.constraintlayout.widget.ConstraintLayout>

更新

我创建了样本项目。 链接

我不创建定制的 NavHostFragment,我使用 navController.navigatorProvider += navigator

现在没空。

作为一种解决方案,您可以将所有获取的数据存储到 ViewModel中,并在重新创建片段时使这些数据随时可用。确保使用活动上下文获取 ViewModel 对象。

您可以使用 LiveData 来使您的数据生命周期感知的可观察数据持有者。

更新19.05.2021多重回溯
自从 Jetpack 导航2.4.0-alpha01以来,我们已经开箱即用了。 检查 < a href = “ https://github.com/android/Architecture-Component-Sample/tree/main/NavigationAdvancedSample”rel = “ noReferrer”> Google 导航高级示例

老答案:
谷歌样本链接 只需将 NavigationExtended 复制到您的应用程序并通过示例进行配置。

经过几个小时的研究,我找到了解决办法。它一直就在我们面前:)有一个函数: popBackStack(destination, inclusive),如果在 backStack 中找到它,它会导航到给定的目的地。它返回 Boolean,所以如果控制器找不到片段,我们可以手动导航。

if(findNavController().popBackStack(R.id.settingsFragment, false)) {
Log.d(TAG, "SettingsFragment found in backStack")
} else {
Log.d(TAG, "SettingsFragment not found in backStack, navigate manually")
findNavController().navigate(R.id.settingsFragment)
}

我使用了@STAR _ ZERO 提供的链接,它工作得很好。对于那些有返回按钮问题的人,您可以像这样在 activity/nav 主机中处理它。

override fun onBackPressed() {
if(navController.currentDestination!!.id!=R.id.homeFragment){
navController.navigate(R.id.homeFragment)
}else{
super.onBackPressed()
}
}

只要检查当前目的地是否是你的根/主页片段(通常是底部导航视图中的第一个片段) ,如果不是,只导航回到片段,如果是,只退出应用程序或做任何你想要的。

顺便说一下,这个解决方案需要使用 Keep _ state _ 片段与 STAR _ ZERO 提供的解决方案链接一起工作。

@ piotr-epops 提供的解决方案帮助了我,但我不得不加上一些当前的目的地检查:

if (navController.currentDestination?.id == resId) {
return       //do not navigate
}

如果没有这个检查,当前目标将会重新创建,如果您错误地导航到它,因为它不会在回栈中找到。

如果在传递参数方面遇到困难,可以添加:

fragment.arguments = args

KeepStateNavigator

如果您在这里只是为了在使用 BottomNavigationViewNavController在片段之间导航时维护 一模一样 RecyclerView滚动状态,那么有一种简单的方法是将 layoutManager状态存储在 onDestroyView中并在 onCreateView上恢复它

我使用 ActivityViewModel 来存储状态。如果您正在使用不同的方法,请确保将状态存储在父活动或任何存活时间长于片段本身的内容中。

碎片

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerview.adapter = MyAdapter()
activityViewModel.listStateParcel?.let { parcelable ->
recyclerview.layoutManager?.onRestoreInstanceState(parcelable)
activityViewModel.listStateParcel = null
}
}


override fun onDestroyView() {
val listState = planet_list?.layoutManager?.onSaveInstanceState()
listState?.let { activityViewModel.saveListState(it) }
super.onDestroyView()
}

ViewModel

var plantListStateParcel: Parcelable? = null


fun savePlanetListState(parcel: Parcelable) {
plantListStateParcel = parcel
}

在最新的导航组件版本中,底部导航视图将跟踪堆栈中的最新片段。

下面是一个例子:

Https://github.com/android/architecture-components-samples/tree/main/navigationadvancedsample

示例代码
在项目构建中

dependencies {
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.4.0-alpha01"
}

在应用程序 build.gradle

plugins {
id 'com.android.application'
id 'kotlin-android'
id 'androidx.navigation.safeargs'
}


dependencies {
implementation "androidx.navigation:navigation-fragment-ktx:2.4.0-alpha01"
implementation "androidx.navigation:navigation-ui-ktx:2.4.0-alpha01"


}

在您的活动-您可以设置与 toolbarbottom navigation view导航

val navHostFragment = supportFragmentManager.findFragmentById(R.id.newsNavHostFragment) as NavHostFragment
val navController = navHostFragment.navController
//setup with bottom navigation view
binding.bottomNavigationView.setupWithNavController(navController)
//if you want to disable back icon in first page of the bottom navigation view
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.feedFragment,
R.id.favoriteFragment
)
).
//setup with toolbar back navigation
binding.toolbar.setupWithNavController(navController, appBarConfiguration)

现在在片段中,您可以导航到第二个片段 & 当您取消选择/选择底部导航项时-NavController 将记住堆栈中的最后一个片段。

示例: 在自定义适配器中

adapter.setOnItemClickListener { item ->
findNavController().navigate(
R.id.action_Fragment1_to_Fragment2
)
}

现在,当您按回片段2内部时,NavController 将自动弹出片段1。

Https://developer.android.com/guide/navigation/navigation-navigate

自定义通用片段导航的超级简单解决方案:

第一步

创建 FragmentNavigator的子类,根据需要覆盖 instantiateFragmentnavigate。如果我们希望片段只创建一次,我们可以在这里缓存它,并在 instantiateFragment方法返回缓存的片段。

第二步

创建 NavHostFragment的子类,覆盖 createFragmentNavigatoronCreateNavController,这样就可以注入我们自定义的导航器(在步骤1中)。

第三步

将布局 xmlFragmentContainerView标记属性从 android:name="com.example.learn1.navigation.TabNavHostFragment"替换为定制的 navHostFragment (在步骤2中)。