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

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

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

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

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

Jimmy Alexander 15.06.2018 20:51
113
1
33 241
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

Ответ принят как подходящий

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

Навигатор

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

@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.

Как я могу нажать на secondView по коду? Как вызвать метод навигации?

Patrick 03.10.2018 15:46

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

miguelt 10.10.2018 21:10

Кроме того, если вы хотите использовать как fragment, так и custom_fragment в одном и том же xml-файле навигации, вам следует добавить super.createFragmentNavigator() в navController в переопределенном createFragmentNavigator(), передав его в navController.navigatorProvider.addNavigator. Таким образом, элементы, начинающиеся с тега <fragment, будут продолжать вести себя нормально, в то время как элементы с типом <custom_fragment попадут в ваш навигатор.

Mel 11.10.2018 13:44

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

Francis 23.11.2018 19:01

Вы можете использовать мою библиотеку для решения этой проблемы: github.com/ZachBublil/ZNavigator

Zach Bublil 03.12.2018 15:49

Кидает странный java.lang.IllegalStateException: Could not find Navigator with name "keep_state_fragment", хотя я добавил навигатор с помощью navigatorProvider.

jL4 06.12.2018 12:09

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

Paul Chernenko 14.12.2018 15:25

Я столкнулся с той же проблемой, что и @Francis. Приложение закрывается при нажатии кнопки возврата, даже если app:defaultNavHost = "true" включен.

Raicky Derwent 17.12.2018 12:14

Глубинная ссылка не работает должным образом, если вы хотите открыть страницу с подробными сведениями. Вы пробовали использовать глубокие ссылки? Я попытался изменить начальную точку назначения как navigation_home, и теперь глубокая ссылка работает правильно

toffor 28.01.2019 09:01

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

Arsenius 22.02.2019 05:35

Кстати, что, если заменить методы прикрепления / отсоединения, чтобы показать / скрыть? Он сохранит многоразовые фрагменты со всеми изменениями пользовательского интерфейса, такими как EditText, набранный в тексте, и позиция прокрутки RecyclerView в основном все. Потому что с текущим решением все изменения пользовательского интерфейса просто исчезнут, когда один раз фрагмент переключится.

Arsenius 22.02.2019 12:42

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

Arie Agung 27.03.2019 17:22

@STAR_ZERO спасибо за ваш ответ, он был действительно полезен для меня, знаете ли вы, почему saveInstanceState всегда имеет значение null и как я могу это исправить? Как я могу определить, что фрагмент был воссоздан?

Viktor Apoyan 23.04.2019 09:16

У вас есть какая-нибудь версия java? Я пытался преобразовать ваш код в java, и все фрагменты перекрывают друг друга

user2905416 22.10.2019 09:04

@STAR_ZERO Это именно то, что я искал. Я хотел знать, как вы догадались, как использовать настраиваемый навигатор фрагментов и связать его с NavController. Я просто плохо читаю документацию или вы нашли какие-либо документы, в которых упоминается, как это сделать? Потому что я не мог найти его в официальных документах.

Syed Ahmed Jamil 12.12.2019 01:50

@SyedAhmedJamil Вот официальная документация developer.android.com/guide/navigation/navigation-add-new Однако не включена документация о том, как реализовать CustomNavigator. Чтобы реализовать это, я сделал метод проб и ошибок, прочитал реализацию компонента навигации.

STAR_ZERO 13.12.2019 05:58

@STAR_ZERO, кстати, изменился ли FragmentManager изнутри после того, как вы разместили свой ответ? Потому что мне не удалось найти метод dispatchOnNavigatorNavigated() в классе навигатора фрагментов. Хотя я смог реализовать это даже без этого метода, но мне просто любопытно. Можете ли вы заглянуть внутрь FragmentNavigator и сообщить мне, прав я или нет?

Syed Ahmed Jamil 17.12.2019 02:27

@SyedAhmedJamil Мой ответ старый. Так что проверьте этот репозиторий github.com/STAR-ZERO/navigation-keep-fragment-sample

STAR_ZERO 18.12.2019 12:45

@STAR_ZERO нам обязательно нужны изменения databinding, чтобы эта работа работала? Или это могло бы работать без необходимости обертывать весь наш макет тегами <layout>?

faizanjehangir 06.02.2020 19:04

@Arsenius Потому что лучшее, что они могут сделать, - это создавать дерьмовые компоненты, рассказывать об этом на каждой конференции, делать их похожими на решение, когда на самом деле это просто еще одна гибкая лента, чтобы исправить недостатки корневого дизайна в Android

Farid 14.07.2020 21:04

Есть ли шанс получить это на Java?

developKinberg 12.11.2020 16:46

А что будет, если два фрагмента навигации зависят друг от друга?

Laura Galera 03.06.2021 18:25

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

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

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

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

Joaquim Ley 14.07.2019 20:37

Обновление 19.05.2021 Множественный бэкстек
Начиная с версии Jetpack Navigation 2.4.0-alpha01, она доступна прямо из коробки. Проверить Пример расширенной навигации Google

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

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

Georgiy Chebotarev 23.06.2020 22:13

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

CodeRED Innovations 09.07.2020 13:56

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

Acauã Pitta 06.10.2020 07:35

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

developKinberg 12.11.2020 16:48

@CodeREDInnovations Мы можем использовать функции расширения с kotlin в файле java. Для этого нужно настроить kotlin в gradle, похоже, это самый оптимальный способ.

Zakhar Rodionov 19.11.2020 16:46

при копировании файла обязательно создайте отдельный навигационный график для каждого выбора bottomNav и убедитесь, что идентификатор пункта меню совпадает с идентификатором графика

Aleyam 14.02.2021 17:24

После многих часов исследований я нашел решение. Это все время было прямо перед нами :) Есть функция: 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

Kishan Solanki 06.05.2020 18:10

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

Code_Life 21.06.2020 08:09

Я использовал ссылку, предоставленную @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 фрагментов

Avishek Das 23.01.2020 12:29

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

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

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

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

Piotr Prus 14.04.2020 09:17

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

fragment.arguments = args

в классе KeepStateNavigator

Если вы здесь только для того, чтобы поддерживать состояние прокрутки точныйRecyclerView при переходе между фрагментами с использованием BottomNavigationView и NavController, то есть простой подход, который состоит в том, чтобы сохранить состояние layoutManager в onDestroyView и восстановить его на onCreateView.

Я использовал ActivityViewModel для хранения состояния. Если вы используете другой подход, убедитесь, что вы сохраняете состояние в родительской активности или во всем, что существует дольше, чем сам фрагмент.

Фрагмент

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    recyclerview.adapter = MyAdapter()
    activityViewModel.listStateParcel?.let { parcelable ->
        recyclerview.layoutManager?.onRestoreInstanceState(parcelable)
        activityViewModel.listStateParcel = null
    }
}

override fun onDestroyView() {
    val listState = planet_list?.layoutManager?.onSaveInstanceState()
    listState?.let { activityViewModel.saveListState(it) }
    super.onDestroyView()
}

ViewModel

var plantListStateParcel: Parcelable? = null

fun savePlanetListState(parcel: Parcelable) {
    plantListStateParcel = parcel
}

Это неверно, если Parcelable в viewmodel фактически не установлен на SavedStateHandle.

EpicPandaForce 28.04.2021 12:30

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