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