Могу ли я использовать несколько элементов NavHostFragments в компоненте навигации?

Пожалуйста, посмотрите на блок-схему, которую я сделал, если у вас есть трудности в понимании следующего параграфа.

В настоящее время я делаю приложение для заметок с 3 пунктами назначения высшего уровня. Одно из мест назначения верхнего уровня (NotesList) отображает список заметок, созданных пользователем. NotesList имеет кнопку фильтра, которая вызывает нижний модальный лист с назначением FilterMenu. В FilterMenu есть кнопка поиска, которая при нажатии заменяет содержимое листа назначением поиска, а кнопка с именем теги, которая при нажатии заменяет содержимое листа фрагментом, содержащим список тегов, связанных со всеми заметками (TagList место назначения).

enter image description here

Все в голубом - это пункт назначения высшего уровня. Все в фиолетовом цвете присутствует на модальном листе.

FilterMenu, Search и TagList отображаются в модальном листе. Это означает, что NotesList содержит эти фрагменты и не заменяется ими. Они существуют в области экрана меньше, чем NotesList. Если я использую навигацию, фрагменты заменят друг друга.

Могу ли я использовать два NavHosts? Один для пунктов назначения высшего уровня и один для материала в модальном листе? Если так, то как бы я это реализовал? Если нет, что рекомендуется делать в этом случае?

Ответ 1

Вы можете создать две навигационные диаграммы для достижения желаемого поведения. Один для пунктов назначения высшего уровня и второй для модального листа. Они должны быть независимыми и не иметь никаких связей между собой. Вы не можете использовать только один навигационный график, так как "поверхность навигации" отличается. Для основной навигации это действие, а для модального нижнего листа это окно нижних листов (которое в случае BottomSheetDialogFragment фактически является другим окном).

Теоретически это может быть достигнуто очень легко:

  • main_nav.xml содержит Settings, NoteList и Trash
  • filter_nav.xml содержит FilterMenu, Search и TagList

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

Таким образом, в основном вам нужен (BottomSheet)DialogFragment которому нужен отдельный NavHost независимый от основного/другого NavHost. Вы можете достичь этого с помощью следующего класса:

dialog_fragment_modal_bottom_sheet.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/filterNavHost"/>

ModalBottomSheetDialogFragment.kt

class ModalBottomSheetDialogFragment : BottomSheetDialogFragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
        inflater.inflate(R.layout.dialog_fragment_modal_bottom_sheet, container, false)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // We can't inflate the NavHostFragment from XML because it will crash the 2nd time the dialog is opened
        val navHost = NavHostFragment()
        childFragmentManager.beginTransaction().replace(R.id.filterNavHost, navHost).commitNow()
        navHost.navController.setGraph(R.navigation.filter_nav)
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return super.onCreateDialog(savedInstanceState).apply {
            // Normally the dialog would close on back press. We override this behaviour and check if we can navigate back
            // If we can't navigate back we return false triggering the default implementation closing the dialog
            setOnKeyListener { _, keyCode, event ->
                if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) {
                    view?.findNavController()?.popBackStack() == true
                } else {
                    false
                }
            }
        }
    }
}

Мы делаем два трюка здесь:

  1. Нам нужно вручную создать фрагмент NavHost. Если мы напрямую поместим его в XML, то при следующем открытии диалога произойдет сбой, поскольку идентификатор уже используется.

  2. Нужно переписать диалог обратной навигации. Диалог - это отдельное окно поверх вашей активности, поэтому Activity onBackPressed() не onBackPressed(). Вместо этого мы добавляем OnKeyListener и когда кнопка "назад" отпускается (ACTION_UP), мы проверяем с помощью NavController, может ли он NavController задний стек (назад) или нет. Если он может вытолкнуть задний стек, мы возвращаем true и, таким образом, используем событие back. Диалог остается открытым, и NavController возвращается на один шаг назад. Если он уже находится в начальной точке, диалог закроется, когда мы вернем false.

Теперь вы можете создать вложенный граф внутри диалога и не заботиться о внешнем графике. Чтобы показать диалог с вложенным графиком, используйте:

val dialog = ModalBottomSheetDialogFragment()
dialog.show(childFragmentManager, "filter-menu")

Вы также можете добавить ModalBottomSheetDialogFragment как пункт назначения <dialog> в main_nav, main_nav я не проверял это. Эта функция в настоящее время все еще в альфа-версии и была введена в навигации 2.1.0-alpha03. Поскольку это все еще в альфа-версии, API может измениться, и я бы лично использовал приведенный выше код для отображения диалога. Как только это выйдет из альфа/бета, предпочтительным способом будет использование места назначения в main_nav.xml. Другой способ показать диалог не имеет значения с точки зрения пользователя.

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

Ответ 2

Простой способ создать два фрагмента navHostFragments - создать еще один файл navigation.xml.

В моем приложении, например, у меня есть два navHostsFragments.

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

После входа пользователя он переносится в mainActivity, в котором содержится мой новый navHostFragment.

Таким образом, всякий раз, когда я начинаю новое действие, которое содержит мой новый navHostFragment.

Надеюсь это поможет