Чи є спосіб зберегти фрагмент живим при використанні BottomNavigationView з новим NavController?


96

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

Але коли я перемикаю фрагменти, вони кожного разу руйнуються / створюються, навіть якщо вони раніше використовувались.

Чи є спосіб зберегти наші основні фрагменти посиланням на наш BottomNavigationView?


2
За коментарем на Issetracker.google.com/issues/80029773, схоже, це врешті буде вирішено. Мені також було б цікаво, якщо у людей є дешевий спосіб вирішення проблеми, який не передбачає відмови від бібліотеки, щоб зробити цю роботу тимчасово.
Jimmy Alexander

Відповіді:


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) Натисніть BACK- додаток існує. Таким чином, BACKфункціональність YouTube між " menuitems" повертається до останньої, не повторюючи елементів.
Мігельт

3
Як зберегти функціональність кнопок назад? Додаток закінчується, коли натиснута кнопка "Назад".
Френсіс

5
@ jL4, у мене була та сама проблема, ймовірно, ти забув видалити app:navGraphзі своєї активності NavHostFragment.
Павло Черненко

6
Чому Google не використовує цю функцію нестандартно?
Арсеній

55
NavigationComponent повинен стати рішенням, а не стати іншою проблемою, тому програміст повинен створити подібні обхідні шляхи.
Arie Agung

24

Зразки Google посилання Просто скопіюйте NavigationExtensions у свою програму та налаштуйте на прикладі. Чудово працює.


1
Але це працює лише для нижньої навігації. Якщо у вас загальна навігація, у вас проблема
Георгій Чеботарев,

Я стикаюся з тією ж проблемою, і кожне рішення, яке я перевіряю, містить код kotlin, але я боюся, що я використовую Java у своєму проекті. Хтось може мені допомогти, як виправити цю проблему в Java.
CodeRED Innovations

@CodeREDInnovations і мені, хоча я намагався і успішно скомпілював файл kotlin, навігація залишається такою: imgur.com/a/DcsKqPr вона не "замінює", крім того, здається, додаток стає важчим і застряючим.
Acauã Pitta

@ AcauãPitta ти знайшов рішення на Java?
DevelopKinberg

@developKinberg немає брата ..
Acauã Pitta

11

Після багатьох годин досліджень я знайшов рішення. Це було весь час прямо перед нами :) Є функція: popBackStack(destination, inclusive)яка рухається до заданого пункту призначення, якщо його знайдете у backStack. Він повертає Boolean, тому ми можемо переходити туди вручну, якщо контролер не знаходить фрагмент.

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

Як і де це використовувати? Якщо я переходжу з фрагменту А на В, то з В на А, як я міг би перейти, не викликаючи методів oncraeteView () та onViewCreated (), коротше кажучи, без перезапуску фрагмента А
Кішан Соланкі

@UsamaSaeedUS: Чи можете ви поділитися кодом, як ним користуватися? Як і те, про що згадує Кішан.
Code_Life

2

Якщо у вас виникли проблеми з передачею аргументів, додайте:

fragment.arguments = args

в класі KeepStateNavigator


1

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

Як обхідне рішення ви можете зберегти всі отримані дані у моделі перегляду та мати ці дані доступними при відтворенні фрагмента. Переконайтеся, що ви отримуєте подання, використовуючи контекст діяльності.

Ви можете використовувати LiveData, щоб зробити ваші дані доступними для спостереження за життєвим циклом


1
Це справді так, і data> view, але проблема полягає в тому, що ви також хочете зберегти стан перегляду, наприклад, кілька завантажених сторінок і користувач прокрутив вниз :).
Joaquim Ley

1

Я використав посилання, надане @STAR_ZERO, і воно працює нормально. Тим, у кого є проблема з кнопкою "Назад", ви можете впоратися з нею на хості активності / навігації таким чином.

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

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

До речі, це рішення має працювати разом із посиланням на рішення вище, наданим STAR_ZERO, використовуючи keep_state_fragment .


idk why but currentDestination !!. id завжди дає мені однакові значення для всіх 3 фрагментів
Авішек Дас

1

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

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

без цієї перевірки поточний пункт призначення буде відтворено, якщо ви помилково перейдете до нього, оскільки його не буде знайдено у зворотному стеці.


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

0

Згідно з документацією Android ,

При створенні NavHostFragment за допомогою FragmentContainerView або якщо вручну додавати NavHostFragment до вашої активності за допомогою FragmentTransaction, намагаючись отримати NavController в onCreate () діяльності за допомогою 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.