将BottomNavigationView与新的NavController一起使用时,是否有办法使片段保持活动状态?


95

我正在尝试使用新的导航组件。我将BottomNavigationView与navController一起使用:NavigationUI.setupWithNavController(bottomNavigation,navController)

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

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


2
根据对issuetracker.google.com/issues/80029773的评论,看来这将最终得到解决。但是我也很好奇,如果人们有一个便宜的解决方法而不涉及放弃图书馆在此期间进行这项工作。
吉米·亚历山大

Answers:


68

试试这个。

航海家

创建自定义导航器。

@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。

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

navigation.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


3
尽管此解决方案有效(fragments可以重用,而不是重新创建),但导航存在问题。每个menuitem输入都bottomnavigationview被认为是主要输入-因此,当BACK按下该键时,应用程序完成(或转到顶部组件)。更好的解决方案是使其行为类似于YouTube应用程序:(1)点击“主页”;(2)点击趋势;(3)点击收件箱;(4)点击趋势;(5)点击BACK-进入收件箱;(6)点击BACK-转到主页;(7)点击BACK-应用存在。总而言之,BACKmenuitems”之间的YouTube功能可恢复到最新状态,无需重复任何项目。
miguelt '18 -10-10

3
如何保留后退按钮功能?按下后退按钮时,应用程序正在完成。
弗朗西斯

5
@ jL4,我遇到了同样的问题,可能您忘记了app:navGraph从活动的NavHostFragment中删除。
保罗·切尔年科

6
为什么Google不开箱即用呢?
Arsenius

55
NavigationComponent应该成为一个解决方案,而不是另一个问题,因此程序员必须创建这样的解决方法。
阿里·阿贡

24

Google样本链接 仅将NavigationExtensions复制到您的应用程序并通过示例进行配置。效果很好。


1
但是它仅适用于底部导航。如果您有一般的导航,则遇到问题
Georgiy Chebotarev

我面临着同样的问题,并且我检查的每个解决方案都包含kotlin代码,但我担心我在项目中使用Java。有人可以帮助我如何在Java中解决此问题。
CodeRED Innovations

@CodeRED也给我带来了创新,尽管我尝试并使用kotlin文件对sucesfull进行了编译,但导航仍然像这样:imgur.com/a/DcsKqPr,它不会“替换”,除此之外,看来应用程序变得越来越重且卡住了。
–AcauãPitta

11

经过数小时的研究,我找到了解决方案。一直在我们面前:)有一个功能:popBackStack(destination, inclusive)如果在backStack中找到,它可以导航到给定的目的地。它返回布尔值,因此如果控制器找不到片段,我们可以手动导航到那里。

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)
    }

如何以及在哪里使用?如果我从片段A导航到B,然后从B到A,我又如何不调用oncraeteView()和onViewCreated()方法,总之不重新启动片段A
Kishan Solanki,

@UsamaSaeedUS:能否请您共享代码,如何使用它?就像Kishan提到的一样。
Code_Life

2

如果您在传递参数时遇到麻烦,请添加:

fragment.arguments = args

在班上 KeepStateNavigator


1

暂时不可用。

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

您可以使用LiveData使您的数据可感知生命周期


1
这确实是正确的,并且data> view确实如此,但是问题是当您还希望保持视图状态时,例如,加载了多个页面并且用户已经向下滚动:)。
Joaquim Ley

1

我使用了@STAR_ZERO提供的链接,它可以正常工作。对于那些对后退按钮有疑问的人,您可以像这样在活动/导航主机中处理它。

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

只需检查当前目的地是否是您的根目录/主目录片段(通常是底部导航视图中的第一个),否则,只需导航回到该片段,如果是,则仅退出应用程序或执行您想做的任何事情。

顺便说一句,此解决方案需要使用keep_state_fragment与STAR_ZERO提供的上述解决方案链接一起使用。


idk为什么为什么,但是currentDestination !!。id总是给我所有3个片段相同的值
Avishek Das

1

@ piotr-prus提供的解决方案对我有所帮助,但是我不得不添加一些当前的目的地检查:

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

如果不进行此检查,则如果您错误地导航到当前目的地,则将重新创建该目的地,因为在后堆栈中找不到该目的地。


我很高兴我的解决方案为您服务。我实际上正在使用以下方法从backStack调用片段之前检查bottomNavigationView中的当前menuItem:bottomNavigationView.setOnNavigationItemSelectedListener
Piotr Prus

0

根据Android文档

使用FragmentContainerView创建NavHostFragment时,或者通过FragmentTransaction手动将NavHostFragment添加到您的活动时,尝试通过Navigation.findNavController(Activity,@IdRes int)在活动的onCreate()中检索NavController将会失败。您应该直接从NavHostFragment检索NavController。

将您的nav_host_fragment更改为-> FragmentContainerView 并检索navController

    val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController

您可以使用此方法通过导航组件保留片段状态

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.