Есть ли способ сохранить фрагмент живым при использовании BottomNavigationView с новым NavController?


96

Я пытаюсь использовать новый компонент навигации. Я использую BottomNavigationView с navController: NavigationUI.setupWithNavController (bottomNavigation, navController)

Но когда я переключаю фрагменты, они каждый раз уничтожают / создают, даже если раньше использовались.

Есть ли способ сохранить в действии наши основные фрагменты ссылки на наш BottomNavigationView?


2
Согласно комментарию на issueetracker.google.com/issues/80029773, похоже, что в конечном итоге эта проблема будет решена. Однако мне также было бы любопытно, есть ли у людей дешевый обходной путь, который не предполагает отказа от библиотеки, чтобы сделать эту работу в промежуточный период.
Джимми Александр

Ответы:


69

Попробуй это.

Навигатор

Создать собственный навигатор.

@Navigator.Name("custom_fragment")  // Use as custom tag at navigation.xml
class CustomNavigator(
    private val context: Context,
    private val manager: FragmentManager,
    private val containerId: Int
) : FragmentNavigator(context, manager, containerId) {

    override fun navigate(destination: Destination, args: Bundle?, navOptions: NavOptions?) {
        val tag = destination.id.toString()
        val transaction = manager.beginTransaction()

        val currentFragment = manager.primaryNavigationFragment
        if (currentFragment != null) {
            transaction.detach(currentFragment)
        }

        var fragment = manager.findFragmentByTag(tag)
        if (fragment == null) {
            fragment = destination.createFragment(args)
            transaction.add(containerId, fragment, tag)
        } else {
            transaction.attach(fragment)
        }

        transaction.setPrimaryNavigationFragment(fragment)
        transaction.setReorderingAllowed(true)
        transaction.commit()

        dispatchOnNavigatorNavigated(destination.id, BACK_STACK_DESTINATION_ADDED)
    }
}

NavHostFragment

Создайте собственный NavHostFragment.

class CustomNavHostFragment: NavHostFragment() {
    override fun onCreateNavController(navController: NavController) {
        super.onCreateNavController(navController)
        navController.navigatorProvider += PersistentNavigator(context!!, childFragmentManager, id)
    }
}

navigation.xml

Используйте собственный тег вместо тега фрагмента.

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/navigation"
    app:startDestination="@id/navigation_first">

    <custom_fragment
        android:id="@+id/navigation_first"
        android:name="com.example.sample.FirstFragment"
        android:label="FirstFragment" />
    <custom_fragment
        android:id="@+id/navigation_second"
        android:name="com.example.sample.SecondFragment"
        android:label="SecondFragment" />
</navigation>

макет деятельности

Используйте CustomNavHostFragment вместо NavHostFragment.

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="com.example.sample.CustomNavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/bottom_navigation"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/navigation" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_navigation"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:menu="@menu/navigation" />

</androidx.constraintlayout.widget.ConstraintLayout>

Обновить

Я создал образец проекта. ссылка на сайт

Я не создаю собственный NavHostFragment. Пользуюсь navController.navigatorProvider += navigator.


3
Хотя это решение работает ( fragmentsиспользуются повторно, а не воссоздаются), есть проблема с навигацией. Каждый menuitemв bottomnavigationviewсчитается первичным - таким образом, при BACKнажатии на приложение заканчивается (или переходит к началу компонента). Лучшим решением было бы вести себя так же, как приложение YouTube: (1) Нажмите «Домой»; (2) Нажмите «Тенденции»; (3) Нажмите «Входящие»; (4) Нажмите «Тенденции»; (5) Нажмите BACK- переход во Входящие; (6) Тап BACK- перейти на главную; (7) Tap BACK- приложение существует. Таким образом, BACKфункциональность YouTube между " menuitems" восходит к самому последнему, без повторения элементов.
miguelt

3
Как сохранить функциональность кнопки возврата? Приложение завершает работу при нажатии кнопки «Назад».
Фрэнсис

5
@ jL4, у меня была такая же проблема, возможно, вы забыли удалить app:navGraphNavHostFragment из своей активности.
Павел Черненко

6
Почему Google не использует эту функциональность из коробки?
Арсениус

55
NavigationComponent должен стать решением, а не другой проблемой, поэтому программист должен создать обходной путь, подобный этому.
Arie Agung

24

Ссылка на образцы Google. Просто скопируйте NavigationExtensions в свое приложение и настройте на примере. Работает отлично.


1
Но работает только для нижней навигации. Если у вас общая навигация, у вас проблемы
Георгий Чеботарев

Я столкнулся с той же проблемой, и каждое решение, которое я проверяю, содержит код kotlin, но я боюсь, что использую Java в своем проекте. Может ли кто-нибудь помочь мне, как исправить эту проблему в java.
CodeRED Innovations

@CodeREDInnovations меня тоже, хотя я пытался и успешно скомпилировал с файлом kotlin, навигация остается такой: imgur.com/a/DcsKqPr она не «заменяет», кроме того, кажется, что приложение стало тяжелее и зависает.
Акауа Питта,

@ AcauãPitta Вы нашли решение на Java?
developKinberg

11

После многих часов исследований я нашел решение. Это все время было прямо перед нами :) Есть функция: popBackStack(destination, inclusive)которая перемещается к заданному месту назначения, если найдена в backStack. Он возвращает логическое значение, поэтому мы можем перемещаться туда вручную, если контроллер не найдет фрагмент.

if(findNavController().popBackStack(R.id.settingsFragment, false)) {
        Log.d(TAG, "SettingsFragment found in backStack")
    } else {
        Log.d(TAG, "SettingsFragment not found in backStack, navigate manually")
        findNavController().navigate(R.id.settingsFragment)
    }

Как и где это использовать? Если я перейду от фрагмента A к B, а затем от B к A, как я могу перейти без вызова методов oncraeteView () и onViewCreated (), короче говоря, без перезапуска фрагмента A
Кишан Соланки

@UsamaSaeedUS: Не могли бы вы поделиться кодом, как его использовать? Как то, о чем упоминает Кишан.
Code_Life

2

Если у вас возникли проблемы с передачей аргументов, добавьте:

fragment.arguments = args

в классе KeepStateNavigator


1

На данный момент недоступно.

В качестве обходного пути вы можете сохранить все выбранные вами данные в модели представления и сделать эти данные доступными при воссоздании фрагмента. Убедитесь, что вы получаете представление с использованием контекста действий.

Вы можете использовать LiveData, чтобы сделать ваши данные наблюдаемыми с учетом жизненного цикла


1
Это действительно так, и данные> просмотр, но проблема в том, что вы хотите также сохранить состояние просмотра, например, загружено несколько страниц и пользователь прокрутил страницу вниз :).
Joaquim Ley

1

Я использовал ссылку, предоставленную @STAR_ZERO, и она отлично работает. Для тех, у кого проблема с кнопкой возврата, вы можете справиться с этим в хосте activity / nav следующим образом.

override fun onBackPressed() {
        if(navController.currentDestination!!.id!=R.id.homeFragment){
            navController.navigate(R.id.homeFragment)
        }else{
            super.onBackPressed()
        }
    }

Просто проверьте, является ли текущий пункт назначения вашим корневым / домашним фрагментом (обычно первым в нижнем навигационном представлении), если нет, просто перейдите к фрагменту, если да, только выйдите из приложения или делайте все, что хотите.

Кстати, это решение должно работать вместе со ссылкой на решение выше, предоставленной STAR_ZERO, с использованием keep_state_fragment .


idk, почему, но currentDestination !!. id всегда дает мне одинаковые значения для всех 3 фрагментов
Авишек Дас

1

Решение, предоставленное @ piotr-prus, помогло мне, но мне пришлось добавить некоторую проверку текущего пункта назначения:

if (navController.currentDestination?.id == resId) {
    return       //do not navigate
}

без этой проверки текущий пункт назначения будет воссоздан, если вы по ошибке перейдете к нему, потому что он не будет найден в заднем стеке.


Я рад, что мое решение сработало для вас. На самом деле я проверяю текущий элемент меню в bottomNavigationView перед вызовом фрагмента из backStack, используя: bottomNavigationView.setOnNavigationItemSelectedListener
Петр Прус,

0

Согласно документации Android ,

При создании NavHostFragment с помощью FragmentContainerView или при ручном добавлении NavHostFragment к вашей активности через FragmentTransaction попытка получить NavController в onCreate () Activity через Navigation.findNavController (Activity, @IdRes int) завершится ошибкой. Вместо этого вы должны получить NavController непосредственно из NavHostFragment.

Измените свой nav_host_fragment на -> FragmentContainerView и извлеките navController

    val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController

Вы можете сохранить состояние фрагмента с помощью компонентов навигации, используя этот подход.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.