Android очистит backstack после повторного выбора нижней вкладки навигации

#android #bottomnavigationview #android-architecture-navigation #android-navigation #android-bottomnav

Вопрос:

Используя новейший компонент навигации с BottomNavigationView , NavController теперь сохраняет и восстанавливает состояния вкладок по умолчанию:

В рамках этого изменения методы NavigationUI onNavDestinationSelected (), BottomNavigationView.setupWithNavController() и NavigationView.setupWithNavController() теперь автоматически сохраняют и восстанавливают состояние всплывающих назначений, обеспечивая поддержку нескольких обратных стеков без каких-либо изменений кода. При использовании навигации с фрагментами это рекомендуемый способ интеграции с несколькими задними стеками.

Это здорово! Теперь переключение вкладок дает вам последний просмотренный стек.

Но если пользователь повторно выбирает вкладку, скажем, они ушли Home -> Detail Page A -> Detail Page B , затем они выбирают Home вкладку, ожидая вернуться к представлению по умолчанию, они все равно видят Detail Page B .

Похоже, что часть обсуждения заключалась в том, чтобы обработать поведение «повторный выбор вкладки», как указано в отслеживателе проблем, но я не могу понять рекомендуемый способ реализации этого.

Все, что включено в пример Navigationadvanced, это:

 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)
 

И это просто восстанавливает предыдущее состояние, как отмечено в примечаниях к выпуску.

Как мы можем проверить, не коснулись ли элемента навигационной панели во второй раз, и очистить задний стек?

Ответ №1:

BottomNavigationView имеет свой собственный метод обработки повторного выбора с помощью setOnItemReselectedListener() (или, при использовании более ранней версии библиотеки Material Design, теперь устаревшей setOnNavigationItemReselectedListener() ).

bottomNavigationView.setupWithNavController не устанавливает этот прослушиватель (так как нет спецификации материала для точного определения того, что должен делать повторный выбор вкладки), поэтому вам нужно установить его самостоятельно:

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

Комментарии:

1. Нажмите на вкладки: A->B->>C->>>B->>> > C, backstack будет: ФрагментА->>>>>ФрагментВ->>> > >>ФрагментС->>>>> > > > ФрагментВ->>>>>>> > > ФрагментС

Ответ №2:

Используйте свой собственный setupWithNavController2 , а setupWithNavController не из androidx.navigation.ui.BottomNavigationViewKt

Например:

Добавлена проверка уже выбранного элемента перед навигацией :

    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 amp;amp; currentDestination?.parent != null) {
        currentDestination = currentDestination.parent
    }
    return currentDestination?.id == destId
}