【问题标题】:Android Navigation Component with Bottom Navigation doesn't destroy startDestination Fragment带有底部导航的 Android 导航组件不会破坏 startDestination 片段
【发布时间】:2020-01-02 09:30:57
【问题描述】:

我已经用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)}

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>

【问题讨论】:

  • 如果可能的话,你能分享给我你的代码吗?仅查看您提供的单行代码,我无法弄清楚问题所在。

标签: android android-architecture-navigation


【解决方案1】:

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

我假设在 BottomNavigationView 的情况下没有 updown。对我来说,我可以从这里导航的每个片段更像是 neighbors 分别 equal important 所以我认为不仅这些 ViewModel 被清除,而且我假设片段本身会在导航离开时通过onDestroy销毁。

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


编辑:

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

【讨论】:

  • 这是一个确认@isrzanza 答案的谈话。 youtu.be/ELGShpd17wc?t=1320
  • 作为一个可怕的 hack - 似乎可以在NavController.OnDestinationChangedListener 中获取 startDestination 片段并强制销毁。
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

    @chrgue 正确描述了问题

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

    如果应用程序只包含“顶级”片段,这尤其令人不快。

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

    代码变得很复杂,因为我必须解决问题:后退按钮无法正常工作并且在 OOM 后没有恢复(还有“弹出到”的操作导航),这是一个 可怕的 hack

    扩展方法

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

    用法(导航抽屉或底部导航视图)

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-27
      • 1970-01-01
      • 1970-01-01
      • 2018-03-10
      相关资源
      最近更新 更多