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

时间:2020-01-02 09:30:57

标签: android android-architecture-navigation

我已经以最基本的方式使用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" />

MainActivity:

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

activity_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>

3 个答案:

答案 0 :(得分:0)

您要描述的是导航组件的默认行为。 导航向下时,仅当导航时不会破坏您导航的片段。

我个人不明白为什么要通知viewModel该片段已被破坏,但是如果要在导航到另一个目标时运行某些代码,则可以在主目录中使用NavController.OnDestinationChangedListener活动(或在您的片段中,但不要忘了在监听器销毁后将其删除),并根据您的起始和结束目标执行一些操作。

如果您想破坏片段,可以尝试在导航图的导航操作中更改“ pop to”参数。

答案 1 :(得分:0)

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

我假设在使用BottomNavigationView时没有向上向下。对我来说,我可以从此处浏览的每个片段分别更像邻居同等重要,因此我认为不仅清除了这些片段的ViewModel,而且我认为片段本身也会在离开时通过 onDestroy 销毁。

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


编辑:

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

答案 2 :(得分:0)

@chrgue 正确描述了问题

在“顶级”导航的情况下,没有 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)
    }

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