Навигационный компонент Android и нижний навигационный вид — кнопка жесткого возврата возвращает в «Домой» с несохраненным состоянием

Контекст

Мы переходим на использование навигационного компонента в моей компании, и пока все идет нормально. У нас есть нижний вид навигации с 5 вкладками, и с помощьюнастроить его. У нас есть «Дом» в качестве начальной вкладки для нашего навигационного графика.

Использование версиипринадлежащийбиблиотеки.

Проблема

Каждая вкладка теперь имеет свой собственный backstack, однако ее состояние сохраняется, когда:

  • Открыв вкладку «Главная», затем открыв FragmentA (теперь задняя часть этой вкладки — «Home» -> «FragmentA»).
  • Затем переключитесь на другую вкладку, назовем ее TabX.
  • Затем нажмите на аппаратную кнопку «Назад».

Ожидал

Поскольку нажатие назад приведет к удалению стека текущей вкладки, мы вернемся на вкладку «Главная» с неповрежденным предыдущим состоянием? (Фрагмент А нажат сверху).

Что происходит

Мы возвращаемся на вкладку «Главная» только с фрагментом «Главная», FragmentA не отображается. И странная часть заключается в том, что при повторном нажатии (повторном выборе) вкладки «Главная» теперь отображается ранее сохраненное состояние (FragmentA поверх «Главной»).

Поскольку это не лучший UX, что делать в этом случае? ожидается ли какое-либо из этих поведений?

Заранее спасибо!

2 ответа

  1. Вы можете проверить, что ваши фрагменты также совпадают с идентификатором навигации.

  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"/>`
  1. Кроме того, убедитесь, что вы переопределили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 , чтобы показать, что я сделал. Я пришел к этому ответу в следующих средних статьях.

Шаги по реализации

Добавьте последнюю версию библиотеки навигации в 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()
}

Когда пользователь нажмет кнопку «Назад», мы вытащим последний фрагмент из стека и установим идентификатор выбранного элемента в нижнем навигационном представлении.

Средняя ссылка

Другие вопросы по тегам