Я пытаюсь использовать новый компонент навигации. Я использую BottomNavigationView с navController: NavigationUI.setupWithNavController(bottomNavigation, navController)
Но когда я переключаю фрагменты, они каждый раз уничтожают / создают, даже если раньше использовались.
Есть ли способ сохранить в действии наши основные фрагменты ссылки на наш BottomNavigationView?
Попробуй это.
Создайте собственный навигатор.
@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.
class CustomNavHostFragment: NavHostFragment() {
override fun onCreateNavController(navController: NavController) {
super.onCreateNavController(navController)
navController.navigatorProvider += PersistentNavigator(context!!, childFragmentManager, id)
}
}
Используйте собственный тег вместо тега фрагмента.
<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 по коду? Как вызвать метод навигации?
Хотя это решение работает (fragments используются повторно, а не воссоздаются), есть проблема с навигацией. Каждый menuitem в bottomnavigationview считается основным - таким образом, при нажатии BACK приложение завершает свою работу (или переходит к верхнему компоненту). Лучшим решением было бы вести себя так же, как приложение YouTube: (1) Нажмите «Домой»; (2) Нажмите «Тенденции»; (3) Нажмите «Входящие»; (4) Нажмите «Тенденции»; (5) Нажмите BACK - переход во Входящие; (6) Нажать BACK - перейти на главную; (7) Коснитесь BACK - приложение существует. Таким образом, функциональность YouTube BACK между "menuitems" восходит к самому последнему, без повторения элементов.
Кроме того, если вы хотите использовать как fragment, так и custom_fragment в одном и том же xml-файле навигации, вам следует добавить super.createFragmentNavigator() в navController в переопределенном createFragmentNavigator(), передав его в navController.navigatorProvider.addNavigator. Таким образом, элементы, начинающиеся с тега <fragment, будут продолжать вести себя нормально, в то время как элементы с типом <custom_fragment попадут в ваш навигатор.
Как сохранить работоспособность кнопки возврата? Приложение завершает работу при нажатии кнопки «Назад».
Вы можете использовать мою библиотеку для решения этой проблемы: github.com/ZachBublil/ZNavigator
Кидает странный java.lang.IllegalStateException: Could not find Navigator with name "keep_state_fragment", хотя я добавил навигатор с помощью navigatorProvider.
@ jL4, у меня была такая же проблема, возможно, вы забыли удалить app:navGraph из NavHostFragment вашей активности.
Я столкнулся с той же проблемой, что и @Francis. Приложение закрывается при нажатии кнопки возврата, даже если app:defaultNavHost = "true" включен.
Глубинная ссылка не работает должным образом, если вы хотите открыть страницу с подробными сведениями. Вы пробовали использовать глубокие ссылки? Я попытался изменить начальную точку назначения как navigation_home, и теперь глубокая ссылка работает правильно
Почему Google не использует эту функциональность из коробки?
Кстати, что, если заменить методы прикрепления / отсоединения, чтобы показать / скрыть? Он сохранит многоразовые фрагменты со всеми изменениями пользовательского интерфейса, такими как EditText, набранный в тексте, и позиция прокрутки RecyclerView в основном все. Потому что с текущим решением все изменения пользовательского интерфейса просто исчезнут, когда один раз фрагмент переключится.
NavigationComponent должен стать решением, а не другой проблемой, поэтому программист должен создать обходной путь, подобный этому.
@STAR_ZERO спасибо за ваш ответ, он был действительно полезен для меня, знаете ли вы, почему saveInstanceState всегда имеет значение null и как я могу это исправить? Как я могу определить, что фрагмент был воссоздан?
У вас есть какая-нибудь версия java? Я пытался преобразовать ваш код в java, и все фрагменты перекрывают друг друга
@STAR_ZERO Это именно то, что я искал. Я хотел знать, как вы догадались, как использовать настраиваемый навигатор фрагментов и связать его с NavController. Я просто плохо читаю документацию или вы нашли какие-либо документы, в которых упоминается, как это сделать? Потому что я не мог найти его в официальных документах.
@SyedAhmedJamil Вот официальная документация developer.android.com/guide/navigation/navigation-add-new Однако не включена документация о том, как реализовать CustomNavigator. Чтобы реализовать это, я сделал метод проб и ошибок, прочитал реализацию компонента навигации.
@STAR_ZERO, кстати, изменился ли FragmentManager изнутри после того, как вы разместили свой ответ? Потому что мне не удалось найти метод dispatchOnNavigatorNavigated() в классе навигатора фрагментов. Хотя я смог реализовать это даже без этого метода, но мне просто любопытно. Можете ли вы заглянуть внутрь FragmentNavigator и сообщить мне, прав я или нет?
@SyedAhmedJamil Мой ответ старый. Так что проверьте этот репозиторий github.com/STAR-ZERO/navigation-keep-fragment-sample
@STAR_ZERO нам обязательно нужны изменения databinding, чтобы эта работа работала? Или это могло бы работать без необходимости обертывать весь наш макет тегами <layout>?
@Arsenius Потому что лучшее, что они могут сделать, - это создавать дерьмовые компоненты, рассказывать об этом на каждой конференции, делать их похожими на решение, когда на самом деле это просто еще одна гибкая лента, чтобы исправить недостатки корневого дизайна в Android
Есть ли шанс получить это на Java?
А что будет, если два фрагмента навигации зависят друг от друга?
На данный момент недоступно.
В качестве обходного пути вы можете сохранить все полученные данные в модели представления и сделать эти данные доступными при воссоздании фрагмента. Убедитесь, что вы получаете представление с использованием контекста действий.
Вы можете использовать LiveData, чтобы сделать ваши данные наблюдаемыми с учетом жизненного цикла
Это действительно так, и данные> просмотр, но проблема в том, что вы также хотите сохранить состояние просмотра, например загружено несколько страниц, и пользователь прокрутил страницу вниз :).
Обновление 19.05.2021 Множественный бэкстек
Начиная с версии Jetpack Navigation 2.4.0-alpha01, она доступна прямо из коробки.
Проверить Пример расширенной навигации Google
Старый ответ:
Ссылка на образцы Google
Просто скопируйте NavigationExtensions в свое приложение и настройте на примере. Работает отлично.
Но работает только для нижней навигации. Если у вас общая навигация, у вас проблема
Я столкнулся с той же проблемой, и каждое решение, которое я проверяю, содержит код kotlin, но я боюсь, что я использую Java в своем проекте. Может ли кто-нибудь помочь мне, как исправить эту проблему в java.
@CodeREDInnovations и меня тоже, хотя я пытался и успешно скомпилировал с файлом kotlin, навигация остается такой: imgur.com/a/DcsKqPr он не «заменяет», кроме того, кажется, что приложение стало тяжелее и зависает.
@ AcauãPitta Вы нашли решение на Java?
@CodeREDInnovations Мы можем использовать функции расширения с kotlin в файле java. Для этого нужно настроить kotlin в gradle, похоже, это самый оптимальный способ.
при копировании файла обязательно создайте отдельный навигационный график для каждого выбора bottomNav и убедитесь, что идентификатор пункта меню совпадает с идентификатором графика
После многих часов исследований я нашел решение. Это все время было прямо перед нами :) Есть функция: 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: Не могли бы вы поделиться кодом, как его использовать? Как то, что упоминает Кишан.
Я использовал ссылку, предоставленную @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 фрагментов
Решение, предоставленное @ piotr-prus, помогло мне, но мне пришлось добавить проверку текущего пункта назначения:
if (navController.currentDestination?.id == resId) {
return //do not navigate
}
без этой проверки текущий пункт назначения будет воссоздан, если вы по ошибке перейдете к нему, потому что он не будет найден в заднем стеке.
Я рад, что мое решение сработало для вас. Я фактически проверяю текущий элемент меню в bottomNavigationView перед вызовом фрагмента из backStack, используя: bottomNavigationView.setOnNavigationItemSelectedListener
Если у вас возникли проблемы с передачей аргументов, добавьте:
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.
Согласно комментарию к Issuesetracker.google.com/issues/80029773, похоже, что в конечном итоге это будет решено. Однако мне также было бы любопытно, есть ли у людей дешевый обходной путь, который не предполагает отказа от библиотеки, чтобы эта работа работала на промежуточном этапе.