Android在重新选择底部导航选项卡后清除堆栈

pbwdgjma  于 2023-03-16  发布在  Android
关注(0)|答案(6)|浏览(181)

BottomNavigationView使用最新的Navigation ComponentNavController现在默认保存和恢复选项卡的状态:
作为此更改的一部分,onNavDestinationSelected()、BottomNavigationView.setupWithNavController()和NavigationView.setupWithNavController()的NavigationUI方法现在可以自动保存和恢复弹出目标的状态,从而支持多个反向堆栈,而无需更改任何代码。
这太棒了!现在切换标签页会给你最后一次查看的堆栈。
但是,如果用户重新选择一个选项卡,假设他们已经转到Home -> Detail Page A -> Detail Page B,然后他们选择Home选项卡,希望返回默认视图,他们仍然看到Detail Page B
似乎讨论的一部分是处理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)

这只是恢复以前的状态,如发行说明中所述。
我们怎样才能检查第二次点击导航栏项目并清除后栈?

tkclm6bt

tkclm6bt1#

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

swvgeqrz2#

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

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

uinbv5nw3#

有两种方法可以拯救我们...
1.在选定项目后更新选择(具有反向堆栈的项目,使用最新版本-2.4.2,当顶部目标中存在反向堆栈时,选择该项目不会首先选择该项目)。
NavigationBarView.setOnItemSelectedListener {}
1.等待第二次单击并执行向后堆栈的弹出。
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)
            }
        }

}
希望这会有帮助。

qgzx9mmu

qgzx9mmu4#

此解决方案由Callie'sianhanniballake's解决方案组成。
行为是

  1. Home -> Detail Page A -> Detail Page B,然后按下Home button-〉Home
  2. Home -> Detail Page A -> Detail Page B,然后导航至Profile,然后返回Home将显示Detail Page B,然后再次按下Home将触发1行为(因此将返回Home屏幕)。
// navView = id of BottomNavigationView

navController = findNavController(R.id.nav_host_container)
// where nav_host_container is the id of the fragment which is your default Nav host. 

binding.navView.setOnItemReselectedListener { menuItem ->              
   val selectedMenuItemNavGraph = 
       navController.graph.findNode(menuItem.itemId) as NavGraph              

   selectedMenuItemNavGraph.let { menuGraph ->           
       navController.popBackStack(menuGraph.startDestinationId, false)              
   }          
}
egmofgnx

egmofgnx5#

使用您自己的setupWithNavController2,而不是androidx.navigation.ui.BottomNavigationViewKt中的setupWithNavController
例如:
在导航之前添加了已选定项目的检查:

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

参见onNavDestinationSelected的注解,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
}
kuhbmx9i

kuhbmx9i6#

你必须显示底部导航栏只为R.id.titleScreen, R.id.leaderboard, R.id.register片段,对于其他人,它应该是隐藏的,他们应该只有工具栏与后退按钮

相关问题