Ответ 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
}
}
}
}
}
Мы делаем два трюка здесь:
-
Нам нужно вручную создать фрагмент
NavHost
. Если мы напрямую поместим его в XML, то при следующем открытии диалога произойдет сбой, поскольку идентификатор уже используется. -
Нужно переписать диалог обратной навигации. Диалог - это отдельное окно поверх вашей активности, поэтому
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. Я использовал нижнюю панель для основной навигации, но вы можете заменить ее на ящик.