Как обрабатывать несколько NavHosts / NavControllers? - PullRequest
0 голосов
/ 28 июня 2019

У меня проблема при работе с несколькими NavHosts.Эта проблема очень похожа на ту, которую задали здесь .Я думаю, что решение этого вопроса также помогло бы мне, но это сообщение от 2017 года, и до сих пор нет ответа.Документация для разработчиков Android не помогает, и поиск в Интернете не показывает абсолютно ничего, что могло бы помочь.

Так что в основном у меня есть одно задание и два фрагмента.Давайте назовем их FruitsActivity, FruitListFragment, FruitDetailFragment, где FruitsActivity не имеет соответствующего кода, а его XML-макет состоит из тега <fragment>, служащего NavHost, например:

<fragment
            android:id="@+id/fragmentContainer"
            android:name="androidx.navigation.fragment.NavHostFragment"
            app:navGraph="@navigation/fruits_nav_graph"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

FruitListFragment - этоstartDestination моего NavGraph, он обрабатывает список фруктов, которые придут с сервера.FruitDetailFragment показывает подробности о Fruit, выбранном в списке, отображаемом FruitListFragment.

Пока у нас есть Activity -> ListFragment -> DetailFragment.

Теперь мне нужно добавить еще один фрагмент под названием GalleryFragment,Это простой фрагмент, который отображает множество картинок выбранного фрукта и вызывается FruitDetailFragment при нажатии кнопки.

Проблема здесь в следующем: в портретном режиме я просто использую findNavController().navigate(...) и перемещаюсь по фрагментам, как я.хочу.но когда я использую планшет в альбомном режиме, я использую этот мастер детализации для отображения списка и деталей на одном экране.Вот пример того, как это работает здесь , и я хочу, чтобы GalleryFragment заменял FruitDetailFragment, разделяя экран со списком фруктов, но пока мне удалось только заставить его заменить «основную навигацию»"Поток, занимая весь экран и отправляя FruitListFragment в Back Stack.

Я уже пытался поиграться с методом findNavController(), но независимо от того, откуда я его вызываю, я могу получить только тот же NavController все время, и он всегда перемещается одинаково линейно.Я пытался реализовать свой собственный NavHost, но я получаю сообщение об ошибке "файл класса для androidx.navigation.NavHost not found".

Это xml моей FruitsActivity:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <fragment
            android:id="@+id/fragmentContainer"
            android:name="androidx.navigation.fragment.NavHostFragment"
            app:navGraph="@navigation/listing_nav_graph"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </FrameLayout>

</layout>

Thisэто xml FruitListActivity в ландшафтном режиме (в портретном режиме есть только RecyclerView):

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/rvFruits"
                android:layout_weight="3"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_margin="8dp"/>

            <FrameLayout
                android:layout_weight="1"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
                <fragment
                    android:id="@+id/sideFragmentContainer"
                    android:name="fruits.example.FruitDetailFragment"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"/>
            </FrameLayout>

        </LinearLayout>

    </RelativeLayout>

</layout>

А теперь я хочу вызвать GalleryFragment и заставить его заменить только <fragment> of id 'sideFragmentContainer'вместо целого экрана (вместо замены <fragment> идентификатора fragmentContainer в xml Деятельности).

Я не нашел никаких объяснений того, как обрабатывать несколько NavHosts или <fragment> внутри <fragment>.

Итак, на основании этого можно ли использовать навигационную архитектуру и отображать фрагмент внутри другого фрагмента?Мне нужно несколько NavHosts для этого или есть другой способ?

1 Ответ

1 голос
/ 28 июня 2019

Как предложено jsmyth886 , , это сообщение в блоге указало мне правильное направление. Трюк состоял в том, чтобы findFragmentById() получить NavHost непосредственно из контейнера фрагментов (в этом случае тот, который делит экран с остальной частью экрана Master Detail). Это позволило мне получить доступ к правильному NavController и перемещаться, как ожидалось. Также важно создать вторую NavGraph.

Итак, быстрый шаг за шагом:

  • Создайте основной NavGraph, чтобы сделать всю обычную навигацию (как это будет работать без Master Detail Flow);
  • Создает вторичный NavGraph, содержащий только возможные Destinations, к которым будет иметь доступ фрагмент Master Detail. Нет Actions подключения, просто Destinations.
  • В главном контейнере <fragment> установите атрибуты так:

    <fragment
                android:id="@+id/fragmentContainer"
                android:name="androidx.navigation.fragment.NavHostFragment"
                app:navGraph="@navigation/main_nav_graph"
                app:defaultNavHost="true"
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>

  • Значение app:defaultNavHost="true" важно. Тогда макет Master Detail будет выглядеть так:

    <?xml version="1.0" encoding="utf-8"?>
        <layout xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto">

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <LinearLayout
                    android:orientation="horizontal"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent">

                    <androidx.recyclerview.widget.RecyclerView
                        android:id="@+id/rvFruits"
                        android:layout_weight="3"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:layout_margin="8dp"/>

                    <FrameLayout
                        android:layout_weight="1"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent">
                        <fragment
                            android:id="@+id/sideFragmentContainer"
                            android:name="androidx.navigation.fragment.NavHostFragment"
                            app:navGraph="@navigation/secondary_nav_graph"
                            app:defaultNavHost="false"
                            android:layout_width="match_parent"
                            android:layout_height="match_parent"/>
                    </FrameLayout>

                </LinearLayout>

            </RelativeLayout>

        </layout>

  • Опять же, атрибут app:defaultNavGraph важен, установите здесь значение false.
  • В части кода у вас должен быть логический флаг, чтобы проверить, работает ли ваше приложение на планшете или нет (ссылка, приведенная в начале ответа, объясняет, как это сделать). В моем случае это MutableLiveData внутри моего ViewModel, вот так я могу наблюдать и соответственно изменять макеты.
  • Если это не планшет (то есть соответствует нормальному навигационному потоку), просто позвоните findNavController().navigate(R.id.your_action_id_from_detail_to_some_other_fragment). Основная навигация будет происходить с использованием основной NavController;
  • Если в планшете используется поток основных данных, вы должны найти правильные NavHost и NavController, найдя <fragment>, в котором он содержится, например:
val navHostFragment = childFragmentManager.findFragmentById(R.id. sideFragmentContainer) as NavHostFragment
  • И, наконец, вы можете перейти к фрагменту, который вы хотите разделить, разделяя экран с остальной частью экрана Master Detail, вызвав navHostFragment.navController.navigate(R.id.id_of_the_destination). Обратите внимание, что здесь мы не называем Actions, мы вызываем Destination напрямую.

Вот и все, проще, чем я думал. Спасибо Лара Мартин за сообщение в блоге и jsmyth886 за указание на правильное направление!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...