【问题标题】:Is there a way to keep fragment alive when using BottomNavigationView with new NavController?将 BottomNavigationView 与新的 NavController 一起使用时,有没有办法让片段保持活动状态?
【发布时间】:2018-11-02 07:15:47
【问题描述】:

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

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

有没有办法让我们的主要片段链接到我们的底部导航视图?

【问题讨论】:

  • 根据issuetracker.google.com/issues/80029773 的评论看来,这最终会得到解决。但是,我也很好奇,如果人们有一个便宜的解决方法,不涉及放弃图书馆以在过渡期间完成这项工作。

标签: android navigation bottomnavigationview


【解决方案1】:

试试这个。

导航器

创建自定义导航器。

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

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>

更新

我创建了示例项目。 link

我不创建自定义 NavHostFragment。我用navController.navigatorProvider += navigator

【讨论】:

  • 虽然此解决方案有效(fragments 被重复使用,而不是重新创建),但导航存在问题。 bottomnavigationview 中的每个 menuitem 都被认为是主要的 - 因此,当按下 BACK 时,应用程序完成(或转到顶部组件)。更好的解决方案是像 YouTube 应用程序一样运行:(1) 点击主页; (2)点击趋势; (3) 点击收件箱; (4)点击趋势; (5) 点击BACK - 进入收件箱; (6) 点击BACK - 进入首页; (7) 点击BACK - 应用程序存在。总而言之,“menuitems”之间的 YouTube BACK 功能可以追溯到最近的,没有重复项。
  • @jL4,我也遇到了同样的问题,可能你忘记从活动的 NavHostFragment 中删除 app:navGraph
  • 为什么谷歌不把这个功能开箱即用?
  • NavigationComponent 应该成为一个解决方案而不是另一个问题,因此程序员必须创建这样的解决方法。
  • @SyedAhmedJamil 我的答案是旧的。所以请检查这个存储库github.com/STAR-ZERO/navigation-keep-fragment-sample
【解决方案2】:

更新 19.05.2021 多个 backstack
从 Jetpack Navigation 2.4.0-alpha01 开始,我们就可以使用它了。 检查Google Navigation Adavanced Sample

旧答案:
Google samples link 只需将 NavigationExtensions 复制到您的应用程序并通过示例进行配置。效果很好。

【讨论】:

  • 但它只适用于底部导航。如果你有一个通用的导航,你就有问题
  • 我面临同样的问题,我检查的每个解决方案都包含 kotlin 代码,但我担心我在项目中使用 Java。有人可以帮助我如何在 java 中解决这个问题。
  • @CodeREDInnovations 我也是,虽然我尝试过使用 kotlin 文件编译并成功,但导航仍然像这样:imgur.com/a/DcsKqPr 它没有“替换”,除此之外,似乎应用程序变成了更重且卡住。
  • @AcauãPitta 你在 Java 中找到解决方案了吗?
  • @CodeREDInnovations 我们可以在 java 文件中使用带有 kotlin 的扩展函数。为此,需要在 gradle 中配置 kotlin,看来这是最优化的方式了。
【解决方案3】:

经过数小时的研究,我找到了解决方案。它一直就在我们面前 :) 有一个函数: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)
    }

【讨论】:

  • 如何以及在哪里使用它?如果我从 Fragment A 导航到 B,然后从 B 导航到 A,我怎么能不调用 oncraeteView() 和 onViewCreated() 方法,简而言之,不重新启动 Fragment A
  • @UsamaSaeedUS:能否分享一下代码,如何使用?就像基尚所说的那样。
【解决方案4】:

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

fragment.arguments = args

在课堂上KeepStateNavigator

【讨论】:

    【解决方案5】:

    目前不可用。

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

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

    【讨论】:

    • 这确实是真的,并且 data > view 但问题是当您还想保持视图状态时,例如加载了多个页面并且用户向下滚动了:)。
    【解决方案6】:

    如果您在这里只是为了在使用BottomNavigationViewNavController 在片段之间导航时保​​持精确 RecyclerView 滚动状态,那么有一个简单的方法是存储@987654324 @state in onDestroyView 并在onCreateView 上恢复它

    我使用 ActivityViewModel 来存储状态。如果您使用不同的方法,请确保将状态存储在父 Activity 或任何比片段本身存活时间更长的东西中。

    片段

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

    视图模型

    var plantListStateParcel: Parcelable? = null
    
    fun savePlanetListState(parcel: Parcelable) {
        plantListStateParcel = parcel
    }
    

    【讨论】:

    • 这是不正确的,除非viewmodel 中的Parcelable 实际上设置在SavedStateHandle
    • @EpicPandaForce 如果您只想在进程仍在运行时保留滚动位置(例如,用户在同一“会话”中的不同选项卡之间切换),它也应该在没有SavedStateHandle 的情况下工作。跨度>
    • 试过了,总是不行。
    【解决方案7】:

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

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

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

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

    【讨论】:

    • idk 为什么但是 currentDestination!!.id 总是给我所有 3 个片段的相同值
    【解决方案8】:

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

    这是一个示例:

    https://github.com/android/architecture-components-samples/tree/main/NavigationAdvancedSample

    示例代码
    在项目 build.gradle 中

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

    现在,当你在 Fragment 2 内按回时,NavController 会自动弹出 Fragment 1。

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

    【讨论】:

    • 你好。 “它会记住堆栈中的最后一个片段”--什么适配器?
    • @Krahmal 我编写的适配器代码是您的自定义适配器 - 您可以在其中导航到第二个片段。后台堆栈由 Android 导航组件 NavController 类自动管理。 developer.android.com/guide/navigation/…
    【解决方案9】:

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

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

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

    【讨论】:

    • 很高兴我的解决方案对您有用。在从 backStack 调用片段之前,我实际上正在检查底部导航视图中的当前菜单项:bottomNavigationView.setOnNavigationItemSelectedListener
    【解决方案10】:

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

    第一步

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

    第 2 步

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

    第 3 步

    将布局 xml FragmentContainerView 标签属性从 android:name="com.example.learn1.navigation.TabNavHostFragment" 替换为您自定义的 navHostFragment(在步骤 2 中)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-10-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-12
      • 2021-08-01
      • 1970-01-01
      相关资源
      最近更新 更多