【问题标题】:Android clear backstack after reselecting Bottom Navigation tab选择底部导航选项卡后Android清除回栈
【发布时间】:2022-05-10 21:47:26
【问题描述】:

使用最新的Navigation ComponentBottomNavigationViewNavController 现在默认保存和恢复选项卡的状态:

作为此更改的一部分,onNavDestinationSelected()、BottomNavigationView.setupWithNavController() 和 NavigationView.setupWithNavController() 的 NavigationUI 方法现在可以自动保存和恢复弹出目的地的状态,无需任何代码更改即可支持多个返回堆栈。当使用带有 Fragments 的导航时,这是与多个返回堆栈集成的推荐方式。

这太棒了!现在切换标签会为您提供上次查看的堆栈。

但是,如果用户重新选择一个标签,说他们已经离开Home -> Detail Page A -> Detail Page B,然后他们选择了Home 标签并期望返回到默认视图,他们仍然会看到@987654328 @。

似乎讨论的一部分是处理issue tracker 中提到的“重新选择选项卡”行为,但我想不出推荐的实现方式。

NavigationAdvancedSample 中包含的所有内容是:

val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_nav)
bottomNavigationView.setupWithNavController(navController)

// Setup the ActionBar with navController and 3 top level destinations
appBarConfiguration = AppBarConfiguration(
        setOf(R.id.titleScreen, R.id.leaderboard,  R.id.register)
    )
setupActionBarWithNavController(navController, appBarConfiguration)

这只是恢复以前的状态,如发行说明中所述。

我们如何检查第二次点击导航栏项目并清除后台堆栈?

【问题讨论】:

  • 该解决方案是否适用于您的所有选项卡?事实上,对我来说,它只适用于主选项卡。谢谢

标签: android bottomnavigationview android-architecture-navigation android-navigation android-bottomnav


【解决方案1】:

BottomNavigationView 有自己的方法通过setOnItemReselectedListener() 处理重新选择(或者,当使用早期版本的材料设计库时,现在不推荐使用setOnNavigationItemReselectedListener())。

bottomNavigationView.setupWithNavController 没有设置这个监听器(因为没有关于重新选择选项卡应该做什么的 Material 规范),所以你需要自己设置它:

val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_nav)
bottomNavigationView.setupWithNavController(navController)

// Add your own reselected listener
bottomNavigationView.setOnItemReselectedListener { item ->
    // Pop everything up to the reselected item
    val reselectedDestinationId = item.itemId
    navController.popBackStack(reselectedDestinationId, inclusive = false)
}

【讨论】:

  • 按标签:A->B->C->B->C,返回栈为:FragmentA->FragmentB->FragmentC->FragmentB->FragmentC
  • 谢谢。当用户选择一个新标签时,是否可以做同样的效果?事实上,在新的导航版本 2.4.0 中,每个选项卡的后退堆栈都被保存了。现在我想在离开时重置每个选项卡的后台堆栈。提前致谢
  • 嗨,对不起,这个解决方案对我不起作用。它仅适用于主标签,但不适用于其他标签。
【解决方案2】:

接受的答案让我朝着正确的方向开始。然而,随着 Android 导航库 2.4.0 支持的多个 backstacks 的添加,这最终对我有用:

val currentRootFragment = supportFragmentManager.findFragmentById(R.id.main_fragment)
val navHost = currentRootFragment as? NavHostFragment
val selectedMenuItemNavGraph = navHost?.navController?.graph?.findNode(menuItem.itemId) as? NavGraph?
selectedMenuItemNavGraph?.let { menuGraph ->
             navHost?.navController?.popBackStack(menuGraph.startDestinationId, false)
}

【讨论】:

  • 这应该被标记为接受的答案
【解决方案3】:

使用您自己的setupWithNavController2 代替androidx.navigation.ui.BottomNavigationViewKt 中的setupWithNavController

例如:

在导航之前添加了对已选择项目的检查:

   if (navController.popBackStack(item.itemId, false)) {
        return true
    }

onNavDestinationSelected的cmets,setupWithNavController2的完整代码:


fun BottomNavigationView.setupWithNavController2(navController: NavController) {
    val bottomNavigationView = this
    bottomNavigationView.setOnItemSelectedListener { item ->
        onNavDestinationSelected(item, navController)
    }
    val weakReference = WeakReference(bottomNavigationView)
    navController.addOnDestinationChangedListener(
        object : NavController.OnDestinationChangedListener {
            override fun onDestinationChanged(
                controller: NavController,
                destination: NavDestination,
                arguments: Bundle?
            ) {
                val view = weakReference.get()
                if (view == null) {
                    navController.removeOnDestinationChangedListener(this)
                    return
                }
                val menu = view.menu
                var i = 0
                val size = menu.size()
                while (i < size) {
                    val item = menu.getItem(i)
                    if (matchDestination(destination, item.itemId)) {
                        item.isChecked = true
                    }
                    i++
                }
            }
        })

    // Add your own reselected listener
    bottomNavigationView.setOnItemReselectedListener { item ->
        // Pop everything up to the reselected item
        val reselectedDestinationId = item.itemId
        navController.popBackStack(reselectedDestinationId, false)
    }
}

fun onNavDestinationSelected(
    item: MenuItem,
    navController: NavController
): Boolean {
    val builder = NavOptions.Builder()
        .setLaunchSingleTop(true)
    if (navController.currentDestination?.parent?.findNode(item.itemId) is ActivityNavigator.Destination) {
        builder.setEnterAnim(R.anim.nav_default_enter_anim)
            .setExitAnim(R.anim.nav_default_exit_anim)
            .setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
            .setPopExitAnim(R.anim.nav_default_pop_exit_anim)
    } else {
        builder.setEnterAnim(R.animator.nav_default_enter_anim)
            .setExitAnim(R.animator.nav_default_exit_anim)
            .setPopEnterAnim(R.animator.nav_default_pop_enter_anim)
            .setPopExitAnim(R.animator.nav_default_pop_exit_anim)
    }
    if (item.order and Menu.CATEGORY_SECONDARY == 0) {
        val findStartDestination = findStartDestination(navController.graph)
        if (findStartDestination != null) {
            builder.setPopUpTo(findStartDestination.id, false)
        }
    }
    val options = builder.build()
    //region The code was added to avoid adding already exist item
    if (navController.popBackStack(item.itemId, false)) {
        return true
    }
    //endregion
    return try {
        // TODO provide proper API instead of using Exceptions as Control-Flow.
        navController.navigate(item.itemId, null, options)
        true
    } catch (e: IllegalArgumentException) {
        false
    }
}

fun findStartDestination(graph: NavGraph): NavDestination? {
    var startDestination: NavDestination? = graph
    while (startDestination is NavGraph) {
        val parent = startDestination
        startDestination = parent.findNode(parent.startDestination)
    }
    return startDestination
}

fun matchDestination(
    destination: NavDestination,
    @IdRes destId: Int
): Boolean {
    var currentDestination: NavDestination? = destination
    while (currentDestination?.id != destId && currentDestination?.parent != null) {
        currentDestination = currentDestination.parent
    }
    return currentDestination?.id == destId
}

【讨论】:

    【解决方案4】:

    这里有两种方法可以解决...

    1. 在选择项目后更新选择(带有后退堆栈的项目,最新版本 - 2.4.2,当顶部目的地存在后退堆栈时,选择该项目不会首先选择项目)。

      NavigationBarView.setOnItemSelectedListener {}

    2. 等待第二次点击并弹出回栈。

      NavigationBarView.setOnItemReselectedListener {}

    最终代码是,

    val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment?
        val navController = navHostFragment?.navController
    
        mainBinding?.bottomNavigation?.apply {
            navController?.let { navController ->
                NavigationUI.setupWithNavController(
                    this,
                    navController
                )
                setOnItemSelectedListener { item ->
                    NavigationUI.onNavDestinationSelected(item, navController)
                    true
                }
                setOnItemReselectedListener {
                    navController.popBackStack(destinationId = it.itemId, inclusive = false)
                }
            }
    

    }

    希望这会有所帮助..

    【讨论】:

      猜你喜欢
      • 2020-12-02
      • 1970-01-01
      • 2018-08-21
      • 1970-01-01
      • 1970-01-01
      • 2015-06-26
      • 1970-01-01
      • 2022-01-15
      • 1970-01-01
      相关资源
      最近更新 更多