Навигационный компонент Android и нижний навигационный вид — кнопка жесткого возврата возвращает в «Домой» с несохраненным состоянием
Контекст
Мы переходим на использование навигационного компонента в моей компании, и пока все идет нормально. У нас есть нижний вид навигации с 5 вкладками, и с помощью
Использование версии
Проблема
Каждая вкладка теперь имеет свой собственный backstack, однако ее состояние сохраняется, когда:
- Открыв вкладку «Главная», затем открыв FragmentA (теперь задняя часть этой вкладки — «Home» -> «FragmentA»).
- Затем переключитесь на другую вкладку, назовем ее TabX.
- Затем нажмите на аппаратную кнопку «Назад».
Ожидал
Поскольку нажатие назад приведет к удалению стека текущей вкладки, мы вернемся на вкладку «Главная» с неповрежденным предыдущим состоянием? (Фрагмент А нажат сверху).
Что происходит
Мы возвращаемся на вкладку «Главная» только с фрагментом «Главная», FragmentA не отображается. И странная часть заключается в том, что при повторном нажатии (повторном выборе) вкладки «Главная» теперь отображается ранее сохраненное состояние (FragmentA поверх «Главной»).
Поскольку это не лучший UX, что делать в этом случае? ожидается ли какое-либо из этих поведений?
Заранее спасибо!
2 ответа
Вы можете проверить, что ваши фрагменты также совпадают с идентификатором навигации.
для всплывающего окна навигации вы можете использовать
findNavController().popBackStack()
или
<fragment
android:id="@+id/c"
android:name="com.example.myapplication.C"
android:label="fragment_c"
tools:layout="@layout/fragment_c">
<action
android:id="@+id/action_c_to_a"
app:destination="@id/a"
app:popUpTo="@+id/a"
app:popUpToInclusive="true"/>`
Кроме того, убедитесь, что вы переопределили
onBackPressed()
метод из кода активности хоста как:override fun onBackPressed() { finish() super.onBackPressed() }
Начиная с версии навигации 2.4.0, BottomNavigationView с NavHostFragment поддерживает отдельный задний стек для каждой вкладки. Но он не поддерживает обратный стек для основных вкладок. Например, если у нас есть 4 основных фрагмента (вкладки) A, B, C и D , тоstartDestination
это . _ D имеет дочерние фрагменты D1, D2 и D3. Если пользователь перемещается как A -> B -> C ->D -> D1 -> D2-> D3, если пользователь нажимает кнопку «Назад» в официальной библиотеке, навигация будет следующей : D3 -> D2-> D1-> D. автор : А. Это означает, что основные вкладки B и C не будут находиться в задней стопке.
Чтобы поддержать стек основной вкладки, я создал стек со ссылкой на навигацию по основной вкладке. При обратном щелчке пользователя я обновил выбранный элемент BottomNavigationView на основе созданного стека.
Я создал этот репозиторий на Github , чтобы показать, что я сделал. Я пришел к этому ответу в следующих средних статьях.
- https://medium.com/r?url=https%3A%2F%2Fvedraj360.medium.com%2Fyoutube-like-backstack-in-jetpack-navigation-comComponent-android-2537b446668d
- https://medium.com/androiddevelopers/navigation-multiple-back-stacks-6c67ba41952f
Шаги по реализации
Добавьте последнюю версию библиотеки навигации в Gradle и следуйте официальному репозиторию для поддержки обратного стека для дочерних фрагментов.
Вместо создания одного nav_graph нам нужно создать отдельные навигационные графики для каждого элемента нижней панели, и эти три графика должны быть добавлены к одному основному графику следующим образом:
<navigation
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@+id/home">
<include app:graph="@navigation/home"/>
<include app:graph="@navigation/list"/>
<include app:graph="@navigation/form"/>
</navigation>
И свяжите нижний вид навигации и фрагмент хоста навигации сsetupWithNavController
Теперь приложение будет поддерживать обратный стек для дочерних фрагментов. Для поддержки основной обратной навигации нам нужно добавить больше строк.
private var addToBackStack: Boolean = true
private lateinit var fragmentBackStack: Stack<Int>
FragmentBackStack поможет нам сохранить все посещенные пункты назначения в стеке, а addToBackStack — это средство проверки, которое поможет определить, хотим ли мы добавить текущий пункт назначения в стек или нет.
navHostFragment.findNavController().addOnDestinationChangedListener { _, destination, _ ->
val bottomBarId = findBottomBarIdFromFragment(destination.id)
if (!::fragmentBackStack.isInitialized){
fragmentBackStack = Stack()
}
if (needToAddToBackStack && bottomBarId!=null) {
if (!fragmentBackStack.contains(bottomBarId)) {
fragmentBackStack.add(bottomBarId)
} else if (fragmentBackStack.contains(bottomBarId)) {
if (bottomBarId == R.id.home) {
val homeCount =
Collections.frequency(fragmentBackStack, R.id.home)
if (homeCount < 2) {
fragmentBackStack.push(bottomBarId)
} else {
fragmentBackStack.asReversed().remove(bottomBarId)
fragmentBackStack.push(bottomBarId)
}
} else {
fragmentBackStack.remove(bottomBarId)
fragmentBackStack.push(bottomBarId)
}
}
}
needToAddToBackStack = true
}
Когда
navHostFragment
изменяет фрагмент, к которому мы получаем обратный вызови проверяем, существует ли уже фрагмент в стеке или нет. Если нет, мы добавим его на вершину стека, если да, мы поменяем позицию на вершину стека. Поскольку сейчас мы используем отдельный график для каждой вкладки, идентификатор в
addOnDestinationChangedListener
и BottomNavigationView будут разными, поэтому мы используем
findBottomBarIdFromFragment
чтобы найти идентификатор элемента BottomNavigationView из целевого фрагмента.
private fun findBottomBarIdFromFragment(fragmentId:Int?):Int?{
if (fragmentId!=null){
val bottomBarId = when(fragmentId){
R.id.register ->{
R.id.form
}
R.id.leaderboard -> {
R.id.list
}
R.id.titleScreen ->{
R.id.home
}
else -> {
null
}
}
return bottomBarId
} else {
return null
}
}
И когда пользователь нажимает кнопку «Назад», мы переопределяем действиеметод( Примечание :onBackPressed
устарел. Я обновлю ответ, как только найду заменуsuper.onBackPressed()
внутриoverride fun onBackPressed()
)
override fun onBackPressed() {
val bottomBarId = if (::navController.isInitialized){
findBottomBarIdFromFragment(navController.currentDestination?.id)
} else {
null
}
if (bottomBarId!=null) {
if (::fragmentBackStack.isInitialized && fragmentBackStack.size > 1) {
if (fragmentBackStack.size == 2 && fragmentBackStack.lastElement() == fragmentBackStack.firstElement()){
finish()
} else {
fragmentBackStack.pop()
val fragmentId = fragmentBackStack.lastElement()
needToAddToBackStack = false
bottomNavigationView.selectedItemId = fragmentId
}
} else {
if (::fragmentBackStack.isInitialized && fragmentBackStack.size == 1) {
finish()
} else {
super.onBackPressed()
}
}
} else super.onBackPressed()
}
Когда пользователь нажмет кнопку «Назад», мы вытащим последний фрагмент из стека и установим идентификатор выбранного элемента в нижнем навигационном представлении.