繁体   English   中英

带有底部导航的 Android 导航组件不会破坏 startDestination 片段

[英]Android Navigation Component with Bottom Navigation doesn't destroy startDestination Fragment

我已经以最基本的方式使用nav graph设置了底部导航 -

NavigationUI.setupWithNavController(bottomNavigationView, navHostFragment.navController)

声明为startDestination的片段在从它导航(仅暂停)时永远不会被销毁,而所有其他片段在导航离开时都会被销毁。

(我需要销毁它,以便在与它关联的 viewModel 中调用onCleared() )。

知道为什么吗? 或如何改变这种行为?

导航:

<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/navigation"
app:startDestination="@id/drawingFragment">

<fragment
    android:id="@+id/controllerFragment"
    android:name="com.example.android.myApp.ControllerFragment"
    android:label="fragment_controller"
    tools:layout="@layout/fragment_controller" >
    <action
        android:id="@+id/action_controllerFragment_to_drawingFragment"
        app:destination="@id/drawingFragment" />
</fragment>
<fragment
    android:id="@+id/drawingFragment"
    android:name="com.example.android.myApp.DrawingFragment"
    android:label="fragment_drawing"
    tools:layout="@layout/fragment_drawing" >
    <action
        android:id="@+id/action_drawingFragment_to_clippingFragment"
        app:destination="@id/clippingFragment"
        app:launchSingleTop="true"
        app:popUpTo="@+id/drawingFragment"
        app:popUpToInclusive="true" />
</fragment>
<fragment
    android:id="@+id/clippingFragment"
    android:name="com.example.android.myApp.ClippingFragment"
    android:label="fragment_clipping"
    tools:layout="@layout/fragment_clipping" />

主要活动:

class MainActivity : AppCompatActivity() {

private lateinit var navHostFragment: NavHostFragment
private lateinit var bottomNavigationView: BottomNavigationView


override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    setUpNavigation()
}

fun setUpNavigation(){
    bottomNavigationView = findViewById(R.id.bttm_nav)
     navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment

    NavigationUI.setupWithNavController(bottomNavigationView, navHostFragment.navController)}

活动_main/xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/navigation" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bttm_nav"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:itemTextAppearanceActive="@style/bottomNaActive"
        app:itemTextAppearanceInactive="@style/bottomNavInactive"
        app:layout_constraintBottom_toBottomOf="@+id/nav_host_fragment"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:menu="@menu/bottom_menu_nav" />
</androidx.constraintlayout.widget.ConstraintLayout>

这实际上不是答案,而是对@isrzanza 答案的评论(抱歉,声誉不够)。

我假设在 BottomNavigationView 的情况下没有updown 对我来说,每一个我可以从这里导航片段更像是邻居分别等于重要的,所以我想不仅是此类的的ViewModels被清除,而我asssumed该片段本身将通过的onDestroy导航离开时,被销毁。

我不知道为什么特别是起始片段应该留在内存中而其他片段不应该,因为它们同样重要,不是吗?


编辑:

我还想提一下,我注意到如果我再次导航到 startDestination-fragment 会创建一个相同类型的新片段(onCreate 将再次执行)并且旧片段将被销毁(onDestroy 将被执行)。 对我来说,这是另一种资源浪费。 将这种情况的片段保留在内存中并在之后重新创建它对我来说毫无意义。 希望我在这里误解了一些东西:)

您所描述的是导航组件的默认行为。 向下导航,您从中导航的片段不会被销毁,只有在向上导航才会销毁。

我个人不明白为什么你要通知 viewModel 片段被销毁,但是如果你想在导航到另一个目的地时运行某段代码,你可以在你的主要活动中使用 NavController.OnDestinationChangedListener (或在您的片段中,但不要忘记在销毁时删除侦听器),并根据您的开始和结束目的地执行一些操作。

如果您无论如何都想销毁片段,您可以尝试更改导航图中导航操作中的“pop to”参数。

@chrgue 正确描述了问题

在“顶级”导航的情况下,没有 is no updown概念。 但是startDestination片段保存在内存中,同时在切换到它时会重新创建。

如果应用程序只包含“顶级”片段,那会特别令人不快。

我不知道如何正确解决这个问题。 对于我自己,我编写了下一个代码。

代码变得很复杂,因为我必须解决问题:后退按钮不能正常工作并且在 OOM 后没有恢复(还有带有“pop to”的动作导航),这是一个可怕的黑客

扩展方法

fun FragmentActivity.enableDestroyStartDestination(
    navController: NavController,
    appBarConfiguration: AppBarConfiguration
) {
    val startDestinationId = navController.graph.startDestination

    var firstStart = true
    var preventMainRecursionFlag = false
    var latestDestionationIsMain = false
    navController.addOnDestinationChangedListener { controller, destination, args ->
        if (appBarConfiguration.topLevelDestinations.contains(destination.id)) {
            if (destination.id == startDestinationId) {
                latestDestionationIsMain = true
                if (firstStart) {
                    firstStart = false
                    return@addOnDestinationChangedListener
                }
                if (preventMainRecursionFlag) {
                    preventMainRecursionFlag = false
                    return@addOnDestinationChangedListener
                }
                preventMainRecursionFlag = true
                val options = NavOptions.Builder().setLaunchSingleTop(true).build()
                controller.navigate(startDestinationId, args, options)
            } else {
                val navHostFragment =
                    supportFragmentManager.primaryNavigationFragment as NavHostFragment
                if (navHostFragment.childFragmentManager.fragments.size > 0) {
                    if (latestDestionationIsMain) {
                        val fragment = navHostFragment.childFragmentManager.fragments[0]
                        navHostFragment.childFragmentManager.beginTransaction().remove(fragment)
                            .commitNowAllowingStateLoss()
                    }
                }
                latestDestionationIsMain = false
            }
        } else {
            latestDestionationIsMain = false
        }
    }
}

用法(Navigation Drawer 或 BottomNavigationView)

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    ...

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    
        ...

        appBarConfiguration = AppBarConfiguration(
            setOf(...    ), drawerLayout
        )
        setupActionBarWithNavController(navController, appBarConfiguration)
        navView.setupWithNavController(navController)

        this.enableDestroyStartDestination(navController, appBarConfiguration)
    }

我不能保证它可以在任何地方使用 - 我的应用程序太简单了。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM