Android Jetpack Navigation, BottomNavigationView с Youtube или Instagram, например, правильная обратная навигация (фрагмент обратного стека)?

Android Jetpack Navigation, BottomNavigationView с автоматическим фрагментом обратного стека при нажатии кнопки возврата?

То, что я хотел, после выбора нескольких вкладок одна за другой пользователем, и пользователь, нажимающий кнопку «Назад», приложение должно перенаправлять на последнюю страницу, которую он / она открывал.

Я добился того же с помощью Android ViewPager, сохранив текущий выбранный элемент в ArrayList. Есть ли какой-либо автоматический возвратный стек после выпуска Android Jetpack Navigation Release? Я хочу добиться этого с помощью навигационного графика

activity_main.xml

<?xml version = "1.0" encoding = "utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android = "http://schemas.android.com/apk/res/android"
    xmlns:app = "http://schemas.android.com/apk/res-auto"
    xmlns:tools = "http://schemas.android.com/tools"
    android:id = "@+id/container"
    android:layout_width = "match_parent"
    android:layout_height = "match_parent"
    tools:context = ".main.MainActivity">

    <fragment
        android:id = "@+id/my_nav_host_fragment"
        android:name = "androidx.navigation.fragment.NavHostFragment"
        android:layout_width = "match_parent"
        android:layout_height = "0dp"
        app:defaultNavHost = "true"
        app:layout_constraintBottom_toTopOf = "@+id/navigation"
        app:layout_constraintEnd_toEndOf = "parent"
        app:layout_constraintStart_toStartOf = "parent"
        app:layout_constraintTop_toTopOf = "parent"
        app:navGraph = "@navigation/nav_graph" />

    <android.support.design.widget.BottomNavigationView
        android:id = "@+id/navigation"
        android:layout_width = "0dp"
        android:layout_height = "wrap_content"
        android:layout_marginStart = "0dp"
        android:layout_marginEnd = "0dp"
        android:background = "?android:attr/windowBackground"
        app:layout_constraintBottom_toBottomOf = "parent"
        app:layout_constraintLeft_toLeftOf = "parent"
        app:layout_constraintRight_toRightOf = "parent"
        app:menu = "@menu/navigation" />

</android.support.constraint.ConstraintLayout>

navigation.xml

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

    <item
        android:id = "@+id/navigation_home"
        android:icon = "@drawable/ic_home"
        android:title = "@string/title_home" />

    <item
        android:id = "@+id/navigation_people"
        android:icon = "@drawable/ic_group"
        android:title = "@string/title_people" />

    <item
        android:id = "@+id/navigation_organization"
        android:icon = "@drawable/ic_organization"
        android:title = "@string/title_organization" />

    <item
        android:id = "@+id/navigation_business"
        android:icon = "@drawable/ic_business"
        android:title = "@string/title_business" />

    <item
        android:id = "@+id/navigation_tasks"
        android:icon = "@drawable/ic_dashboard"
        android:title = "@string/title_tasks" />

</menu>

также добавил

bottomNavigation.setupWithNavController(Navigation.findNavController(this, R.id.my_nav_host_fragment))

Я получил один ответ от Levi Moreira, а именно:

navigation.setOnNavigationItemSelectedListener {item ->

            onNavDestinationSelected(item, Navigation.findNavController(this, R.id.my_nav_host_fragment))

        }

Но при этом происходит только повторное создание экземпляра последнего открытого фрагмента.

Обеспечение правильной обратной навигации для BottomNavigationView

Привет, @BincyBaby, мне нужно то же самое, у тебя есть какие-нибудь решения?

Ramki Anba 10.08.2018 06:28

еще не получил ответа

Bincy Baby 12.08.2018 13:28

Комментируя немного поздно, но после некоторого рытья я обнаружил, что popBackStack вызывается из функции NavController.navigate(), когда NavOptions не равны нулю. Я предполагаю, что на данный момент это невозможно сделать из коробки. Требуется специальная реализация NavController, которая обращается к mBackStack через отражение или что-то в этом роде.

HawkPriest 06.09.2018 16:18

Если вы добавите слушателя в нижнюю навигацию, вы можете переопределить навигацию, чтобы он возвращал стек, если стек уже содержит новое место назначения, или иным образом выполнять обычную навигацию, если это не так. if (!navHost.popBackStack(it.itemId, false)) navHost.navigate(it.itemId)

Marcus Hooper 18.10.2018 08:25

Обход проблемы с воссозданием фрагмента - stackoverflow.com/a/51684125/6024687

jL4 06.12.2018 06:55

Вы нашли какой-нибудь способ добиться этого?

hosseinAmini 18.05.2019 08:30
mobologicplus.com/… это руководство может помочь
Sunil Kumar 19.06.2019 07:35
73
7
49 985
14
Перейти к ответу Данный вопрос помечен как решенный

Ответы 14

Вы можете настроить viewpager с нижним навигационным представлением. Каждый фрагмент в окне просмотра будет фрагментом контейнера, у него будут дочерние фрагменты со своим собственным стеком. Таким образом, вы можете поддерживать backstack для каждой вкладки в окне просмотра.

Я использовал этот способ, но запуск приложения занимает слишком много времени до первого запуска

Bincy Baby 29.05.2018 08:16

Тогда вы, должно быть, делаете что-то не так, убедитесь, что вы не выполняете тяжелую работу при oncreate или oncreateview фрагментов. Нет никакого способа, чтобы это заняло время

Suhaib Roomy 29.05.2018 08:21

Мне нужно загрузить содержимое, я не думаю, что YouTube или Instagram использовали ViewPager

Bincy Baby 29.05.2018 08:48

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

Suhaib Roomy 29.05.2018 11:28

Я также предполагаю, что YouTube или Instagram не используют ViewPager. Восстановление происходит из-за всплывающего действия backStack, которое возобновляет базовый фрагмент, который добавлен в первую очередь, а не заменен.

HawkPriest 06.09.2018 16:13

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

Основная концепция заключается в том, что у вас есть основное действие, на котором будет размещаться BottomNavigationView, и это узел навигации для вашего навигационного графика, вот как выглядит xml для него:

activity_main.xml

<?xml version = "1.0" encoding = "utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android = "http://schemas.android.com/apk/res/android"
    xmlns:app = "http://schemas.android.com/apk/res-auto"
    xmlns:tools = "http://schemas.android.com/tools"
    android:id = "@+id/container"
    android:layout_width = "match_parent"
    android:layout_height = "match_parent"
    tools:context = ".main.MainActivity">

    <fragment
        android:id = "@+id/my_nav_host_fragment"
        android:name = "androidx.navigation.fragment.NavHostFragment"
        android:layout_width = "match_parent"
        android:layout_height = "0dp"
        app:defaultNavHost = "true"
        app:layout_constraintBottom_toTopOf = "@+id/navigation"
        app:layout_constraintEnd_toEndOf = "parent"
        app:layout_constraintStart_toStartOf = "parent"
        app:layout_constraintTop_toTopOf = "parent"
        app:navGraph = "@navigation/nav_graph" />

    <android.support.design.widget.BottomNavigationView
        android:id = "@+id/navigation"
        android:layout_width = "0dp"
        android:layout_height = "wrap_content"
        android:layout_marginStart = "0dp"
        android:layout_marginEnd = "0dp"
        android:background = "?android:attr/windowBackground"
        app:layout_constraintBottom_toBottomOf = "parent"
        app:layout_constraintLeft_toLeftOf = "parent"
        app:layout_constraintRight_toRightOf = "parent"
        app:menu = "@menu/navigation" />

</android.support.constraint.ConstraintLayout>

Меню навигации (меню вкладок) для BottomNavigationView выглядит следующим образом:

navigation.xml

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

    <item
        android:id = "@+id/navigation_home"
        android:icon = "@drawable/ic_home"
        android:title = "@string/title_home" />

    <item
        android:id = "@+id/navigation_people"
        android:icon = "@drawable/ic_group"
        android:title = "@string/title_people" />

    <item
        android:id = "@+id/navigation_organization"
        android:icon = "@drawable/ic_organization"
        android:title = "@string/title_organization" />

    <item
        android:id = "@+id/navigation_business"
        android:icon = "@drawable/ic_business"
        android:title = "@string/title_business" />

    <item
        android:id = "@+id/navigation_tasks"
        android:icon = "@drawable/ic_dashboard"
        android:title = "@string/title_tasks" />

</menu>

Все это просто установка BottomNavigationView. Теперь, чтобы заставить его работать с компонентом Navigation Arch, вам нужно войти в редактор графа навигации, добавить все места назначения фрагментов (в моем случае у меня их 5, по одному для каждой вкладки) и установить идентификатор места назначения с тем же имя как в файле navigation.xml:

Это скажет Android установить связь между вкладкой и фрагментом, теперь каждый раз, когда пользователь щелкает вкладку «Главная», Android будет заботиться о загрузке правильного фрагмента. Также есть один фрагмент кода kotlin, который необходимо добавить в ваш NavHost (основное действие) для подключения к BottomNavigationView:

Вам нужно добавить в свой onCreate:

bottomNavigation.setupWithNavController(Navigation.findNavController(this, R.id.my_nav_host_fragment))

Это сообщает Android, что необходимо выполнить соединение между компонентом архитектуры навигации и BottomNavigationView. См. Больше в документы.

Чтобы получить такой же внешний вид, как при использовании YouTube, просто добавьте это:

navigation.setOnNavigationItemSelectedListener {item ->

            onNavDestinationSelected(item, Navigation.findNavController(this, R.id.my_nav_host_fragment))

        }

Это приведет к тому, что пункты назначения попадут в стек, поэтому, когда вы нажмете кнопку «Назад», появится последний посещенный пункт назначения.

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

JamesSugrue 01.06.2018 01:21

Можем ли мы иметь единственный экземпляр фрагмента?

Bincy Baby 02.06.2018 12:12

Я не уверен, что lib - это тот, кто заботится о создании экземпляра, но я нигде не вижу места, где мы могли бы это настроить: /

Levi Moreira 02.06.2018 13:07

Но на самом деле я не ищу этого, я хочу вернуть тот экземпляр, на котором мы в последний раз ходили

Bincy Baby 05.06.2018 12:25

Это нормально работает с кнопкой возврата. Но если пользователь нажимает на нижние вкладки, он не восстанавливает ранее открытый дочерний фрагмент этой вкладки (если он доступен). Он просто открывает новый момент (родительского) фрагмента каждый раз, когда пользователь нажимает на нижние вкладки. Таким образом, этот способ приведет к сбиванию с толку / разочарованию пользователей при многократной навигации с использованием нижних вкладок. Опасная реализация

Niroshan 25.06.2018 07:26

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

Suhaib Roomy 26.06.2018 10:48

Если кнопка возврата не работает (то есть, если приложение закрывается вместо перехода к первому фрагменту), убедитесь, что все идентификаторы настроены правильно (мне пришлось добавить несколько '+' в '@id' (как в @ + id) и что NavHostFragment настроен с помощью app: defaultNavHost = true

Patrick Steiger 19.07.2018 19:05

Я думал, что в BottomNavigationView max может быть только три вкладки. Разве это больше не так?

Marty Miller 30.09.2018 22:34

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

Levi Moreira 30.09.2018 22:36

@LeviMoreira Не могли бы вы предоставить для этого демонстрационную ссылку?

Sohel S9 29.04.2019 12:56

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

kondal 26.07.2019 14:41

@kondal есть ли у вас какое-нибудь решение, чтобы не воссоздавать фрагменты снова и снова?

Sohail Zahid 14.10.2019 09:13

@Niroshan, вы нашли решение своей проблемы, помогите мне, если вы нашли

OK200 25.10.2019 10:01

@ OK200 Не официальный ответ! Но мы можем преодолеть это, расширив FragmentNavigator по умолчанию. Посмотрите этот простой пример github.com/STAR-ZERO/navigation-keep-fragment-sample! (Взгляните на класс KeepStateNavigator). ** Не забудьте заменить ключевое слово 'фрагмент' на 'keep_state_fragment' (или аннотацию ключевого слова, используемую в вашем расширенном классе), используемую в вашем nav_graph (xml)

Niroshan 08.11.2019 05:20

Решите проблему с воссозданием вкладок, указав это в вашем BaseFragment: (Это должно быть рекомендуемое решение, но я забыл, где я его читал) private var previousLoadedView: View? = null override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { if (previousLoadedView == null) { previousLoadedView = inflater.inflate(layoutId, container, false) } else { (previousLoadedView?.parent as? ViewGroup)?.removeAllViews() } return previousLoadedView }

Morten Holmgaard 10.12.2019 11:58

@Niroshan В принципе, существует официальный обходной путь, пока проблема не будет устранена. Вы можете найти это здесь github.com/android/architecture-components-samples/blob/mast‌er /…

Vaios 16.02.2020 12:23

Я создал подобное приложение (все еще не опубликованное в PlayStore) с такой же навигацией, возможно, его реализация отличается от того, что делает Google в своих приложениях, но функциональность такая же.

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

public void switchTo(final Fragment fragment, final String tag /*Each fragment should have a different Tag*/) {

// We compare if the current stack is the current fragment we try to show
if (fragment == getSupportFragmentManager().getPrimaryNavigationFragment()) {
  return;
}

// We need to hide the current showing fragment (primary fragment)
final Fragment currentShowingFragment = getSupportFragmentManager().getPrimaryNavigationFragment();

final FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
if (currentShowingFragment != null) {
  fragmentTransaction.hide(currentShowingFragment);
}

// We try to find that fragment if it was already added before
final Fragment alreadyAddedFragment = getSupportFragmentManager().findFragmentByTag(tag);
if (alreadyAddedFragment != null) {
  // Since its already added before we just set it as primary navigation and show it again
  fragmentTransaction.setPrimaryNavigationFragment(alreadyAddedFragment);
  fragmentTransaction.show(alreadyAddedFragment);
} else {
  // We add the new fragment and then show it
  fragmentTransaction.add(containerId, fragment, tag);
  fragmentTransaction.show(fragment);
  // We set it as the primary navigation to support back stack and back navigation
  fragmentTransaction.setPrimaryNavigationFragment(fragment);
}

fragmentTransaction.commit();
}

Вы должны установить навигацию хоста, как показано ниже xml:

<LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android"
    xmlns:app = "http://schemas.android.com/apk/res-auto"
    android:layout_width = "match_parent"
    android:layout_height = "match_parent"
    android:orientation = "vertical">

    <android.support.v7.widget.Toolbar
        android:id = "@+id/toolbar"
        android:layout_width = "match_parent"
        android:layout_height = "wrap_content"
        android:background = "@color/colorPrimary" />

    <fragment
        android:id = "@+id/navigation_host_fragment"
        android:name = "androidx.navigation.fragment.NavHostFragment"
        android:layout_width = "match_parent"
        android:layout_height = "0dp"
        android:layout_weight = "1"
        app:defaultNavHost = "true"
        app:navGraph = "@navigation/nav_graph" />

    <android.support.design.widget.BottomNavigationView
        android:id = "@+id/bottom_navigation_view"
        android:layout_width = "match_parent"
        android:layout_height = "wrap_content"
        app:itemIconTint = "@drawable/color_state_list"
        app:itemTextColor = "@drawable/color_state_list"
        app:menu = "@menu/menu_bottom_navigation" />
</LinearLayout>

Настройка с помощью контроллера навигации:

NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.navigation_host_fragment);
NavigationUI.setupWithNavController(bottomNavigationView, navHostFragment.getNavController());

menu_bottom_navigation.xml:

<?xml version = "1.0" encoding = "utf-8"?>
<menu xmlns:android = "http://schemas.android.com/apk/res/android">
    <item
        android:id = "@id/tab1"  // Id of navigation graph 
        android:icon = "@mipmap/ic_launcher"
        android:title = "@string/tab1" />
    <item
        android:id = "@id/tab2" // Id of navigation graph
        android:icon = "@mipmap/ic_launcher"
        android:title = "@string/tab2" />

    <item
        android:id = "@id/tab3" // Id of navigation graph
        android:icon = "@mipmap/ic_launcher"
        android:title = "@string/tab3" />
</menu>

nav_graph.xml:

<navigation xmlns:android = "http://schemas.android.com/apk/res/android"
    xmlns:app = "http://schemas.android.com/apk/res-auto"
    xmlns:tools = "http://schemas.android.com/tools"
    android:id = "@+id/nav_graph"
    app:startDestination = "@id/tab1">
    <fragment
        android:id = "@+id/tab1"
        android:name = "com.navigationsample.Tab1Fragment"
        android:label = "@string/tab1"
        tools:layout = "@layout/fragment_tab_1" />

    <fragment
        android:id = "@+id/tab2"
        android:name = "com.navigationsample.Tab2Fragment"
        android:label = "@string/tab2"
        tools:layout = "@layout/fragment_tab_2"/>

    <fragment
        android:id = "@+id/tab3"
        android:name = "com.simform.navigationsample.Tab3Fragment"
        android:label = "@string/tab3"
        tools:layout = "@layout/fragment_tab_3"/>
</navigation>

При установке того же идентификатора «nav_graph» на «menu_bottom_navigation» будет обрабатываться щелчок нижней навигации.

Вы можете обработать обратное действие, используя свойство popUpTo в теге action.

Можете ли вы уточнить использование popUpTo?

Bincy Baby 12.08.2018 12:49

Свойство @BincyBaby popUpTo помогает вам вернуться к определенному фрагменту при обратном нажатии.

SANAT 18.08.2018 17:51

@SANAT, а как настроить popUpTo на фрагмент, который сразу же нажимался раньше? Например, если вы были в frag1, перешли на frag2, а затем на frag3, нажатие назад должно вернуться к frag2. Если вы были в frag1 и сразу перешли на frag3, обратное нажатие вернет вас на frag1. popUpTo, кажется, позволяет вам выбрать только один фрагмент для возврата, независимо от пути пользователя.

Allan Veloso 30.01.2019 23:57

Без сохранения порядка стека назад, кнопка «Назад» переходит на 1-ю вкладку, исключая 2-ю вкладку. Мало того, не сохраняется состояние фрагмента, вместо этого создается новый экземпляр при каждом щелчке ButtomNavigationItem.

Farid 14.06.2019 17:06

@Farid может контролировать, чтобы не создавать новый экземпляр каждый раз при выборе.

Sohail Zahid 14.10.2019 09:14

@Farid, знаете ли вы, как это разрешить? Это вызывает у меня проблему.

Suyash Dixit 16.01.2020 16:56

@SuyashDixit, честно говоря, я не пошел с этим шаблоном, но вы можете сделать следующее. Создавайте свои фрагменты и show/hide их вместо add/replace. Если это непонятно, я отправлю фрагмент кода в качестве ответа.

Farid 17.01.2020 06:15

есть идеи, можем ли мы использовать это в моей демонстрации stackoverflow.com/questions/63052712/…

Sunil Chaudhary 07.08.2020 10:29

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

Nouman Ch 30.09.2020 09:24

Если у вас есть bottomNavigationView с 3 элементами, соответствующими 3 Fragment: FragmentA, FragmentB и FragmentC, где FragmentA - это startDestination на вашем навигационном графике, то, когда вы находитесь на FragmentB или FragmentC и нажимаете назад, вы будете перенаправлены на FragmentA, это поведение, рекомендованное Google и реализованное по умолчанию.

Однако, если вы хотите изменить это поведение, вам нужно будет либо использовать ViewPager, как было предложено некоторыми другими ответами, либо вручную обрабатывать фрагменты backStack и обратные транзакции самостоятельно, что в некотором смысле подорвало бы использование Navigation компонент в целом-.

Но YouTube, Instagram, Saavn ведут себя иначе.

Bincy Baby 29.08.2018 04:05

Правда, нет правильного или неправильного способа сделать это, это просто о том, что Google поддерживает по умолчанию (и, следовательно, рекомендует) и каковы ваши потребности. Если эти два не совпадают, вам нужно обойти это.

Husayn Hakeem 30.08.2018 20:26

Но проблема в том, что если вы используете JetPack Navigation, backStack будет пустым. Очевидно, JetPack ничего не добавляет в задний стек при обработке щелчков BottomNavigation.

Allan Veloso 31.01.2019 00:12

Во-первых, позвольте мне прояснить, как Youtube и Instagram обрабатывают фрагментную навигацию.

  • Когда пользователь находится на фрагменте детали, выполняется резервное или восходящее всплывающее окно стека один раз с правильным восстановлением состояния. Второй щелчок по уже выбранному элементу нижней панели выталкивает весь стек в корень, обновляя его.
  • Когда пользователь находится в корневом фрагменте, он возвращается к последнему меню, выбранному на нижней панели, отображая последний фрагмент детали, с правильным восстановлением состояния (JetPack этого не делает).
  • Когда пользователь находится на стартовом целевом фрагменте, назад завершает действие

Ни один из других ответов выше не решает всех этих проблем с использованием навигации на реактивном ранце.

Навигация JetPack не имеет стандартного способа сделать это, способ, который я нашел более простым, - это разделить навигационный xml-график на один для каждого нижнего элемента навигации, самостоятельно обработать задний стек между элементами навигации с помощью действия FragmentManager и использовать JetPack NavController для обработки внутренней навигации между корневым и детальным фрагментами (его реализация использует стек childFragmentManager).

Предположим, у вас в папке navigation есть 3 xmls:

res/navigation/
    navigation_feed.xml
    navigation_explore.xml
    navigation_profile.xml

Иди назначения в xmls навигации должны совпадать с идентификаторами меню bottomNavigationBar. Кроме того, для каждого xml установите app:startDestination на фрагмент, который вы хотите использовать в качестве корня элемента навигации.

Создайте класс BottomNavController.kt:

class BottomNavController(
        val context: Context,
        @IdRes val containerId: Int,
        @IdRes val appStartDestinationId: Int
) {
    private val navigationBackStack = BackStack.of(appStartDestinationId)
    lateinit var activity: Activity
    lateinit var fragmentManager: FragmentManager
    private var listener: OnNavigationItemChanged? = null
    private var navGraphProvider: NavGraphProvider? = null

    interface OnNavigationItemChanged {
        fun onItemChanged(itemId: Int)
    }

    interface NavGraphProvider {
        @NavigationRes
        fun getNavGraphId(itemId: Int): Int
    }

    init {
        var ctx = context
        while (ctx is ContextWrapper) {
            if (ctx is Activity) {
                activity = ctx
                fragmentManager = (activity as FragmentActivity).supportFragmentManager
                break
            }
            ctx = ctx.baseContext
        }
    }

    fun setOnItemNavigationChanged(listener: (itemId: Int) -> Unit) {
        this.listener = object : OnNavigationItemChanged {
            override fun onItemChanged(itemId: Int) {
                listener.invoke(itemId)
            }
        }
    }

    fun setNavGraphProvider(provider: NavGraphProvider) {
        navGraphProvider = provider
    }

    fun onNavigationItemReselected(item: MenuItem) {
        // If the user press a second time the navigation button, we pop the back stack to the root
        activity.findNavController(containerId).popBackStack(item.itemId, false)
    }

    fun onNavigationItemSelected(itemId: Int = navigationBackStack.last()): Boolean {

        // Replace fragment representing a navigation item
        val fragment = fragmentManager.findFragmentByTag(itemId.toString())
                ?: NavHostFragment.create(navGraphProvider?.getNavGraphId(itemId)
                        ?: throw RuntimeException("You need to set up a NavGraphProvider with " +
                                "BottomNavController#setNavGraphProvider")
                )
        fragmentManager.beginTransaction()
                .setCustomAnimations(
                        R.anim.nav_default_enter_anim,
                        R.anim.nav_default_exit_anim,
                        R.anim.nav_default_pop_enter_anim,
                        R.anim.nav_default_pop_exit_anim
                )
                .replace(containerId, fragment, itemId.toString())
                .addToBackStack(null)
                .commit()

        // Add to back stack
        navigationBackStack.moveLast(itemId)

        listener?.onItemChanged(itemId)

        return true
    }

    fun onBackPressed() {
        val childFragmentManager = fragmentManager.findFragmentById(containerId)!!
                .childFragmentManager
        when {
            // We should always try to go back on the child fragment manager stack before going to
            // the navigation stack. It's important to use the child fragment manager instead of the
            // NavController because if the user change tabs super fast commit of the
            // supportFragmentManager may mess up with the NavController child fragment manager back
            // stack
            childFragmentManager.popBackStackImmediate() -> {
            }
            // Fragment back stack is empty so try to go back on the navigation stack
            navigationBackStack.size > 1 -> {
                // Remove last item from back stack
                navigationBackStack.removeLast()

                // Update the container with new fragment
                onNavigationItemSelected()
            }
            // If the stack has only one and it's not the navigation home we should
            // ensure that the application always leave from startDestination
            navigationBackStack.last() != appStartDestinationId -> {
                navigationBackStack.removeLast()
                navigationBackStack.add(0, appStartDestinationId)
                onNavigationItemSelected()
            }
            // Navigation stack is empty, so finish the activity
            else -> activity.finish()
        }
    }

    private class BackStack : ArrayList<Int>() {
        companion object {
            fun of(vararg elements: Int): BackStack {
                val b = BackStack()
                b.addAll(elements.toTypedArray())
                return b
            }
        }

        fun removeLast() = removeAt(size - 1)
        fun moveLast(item: Int) {
            remove(item)
            add(item)
        }
    }
}

// Convenience extension to set up the navigation
fun BottomNavigationView.setUpNavigation(bottomNavController: BottomNavController, onReselect: ((menuItem: MenuItem) -> Unit)? = null) {
    setOnNavigationItemSelectedListener {
        bottomNavController.onNavigationItemSelected(it.itemId)
    }
    setOnNavigationItemReselectedListener {
        bottomNavController.onNavigationItemReselected(it)
        onReselect?.invoke(it)
    }
    bottomNavController.setOnItemNavigationChanged { itemId ->
        menu.findItem(itemId).isChecked = true
    }
}

Сделайте свой макет main.xml следующим образом:

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android = "http://schemas.android.com/apk/res/android"
    xmlns:app = "http://schemas.android.com/apk/res-auto"
    xmlns:tools = "http://schemas.android.com/tools"
    android:layout_width = "match_parent"
    android:layout_height = "match_parent">

    <FrameLayout
        android:id = "@+id/container"
        android:layout_width = "match_parent"
        android:layout_height = "0dp"
        app:layout_constraintBottom_toTopOf = "@id/bottomNavigationView"
        app:layout_constraintTop_toTopOf = "parent" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id = "@+id/bottomNavigationView"
        android:layout_width = "match_parent"
        android:layout_height = "wrap_content"
        android:layout_marginStart = "0dp"
        android:layout_marginEnd = "0dp"
        app:layout_constraintBottom_toBottomOf = "parent"
        app:menu = "@menu/navigation" />

</androidx.constraintlayout.widget.ConstraintLayout>

Используйте в своей деятельности так:

class MainActivity : AppCompatActivity(),
        BottomNavController.NavGraphProvider  {

    private val navController by lazy(LazyThreadSafetyMode.NONE) {
        Navigation.findNavController(this, R.id.container)
    }

    private val bottomNavController by lazy(LazyThreadSafetyMode.NONE) {
        BottomNavController(this, R.id.container, R.id.navigation_feed)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)

        bottomNavController.setNavGraphProvider(this)
        bottomNavigationView.setUpNavigation(bottomNavController)
        if (savedInstanceState == null) bottomNavController
                .onNavigationItemSelected()

        // do your things...
    }

    override fun getNavGraphId(itemId: Int) = when (itemId) {
        R.id.navigation_feed -> R.navigation.navigation_feed
        R.id.navigation_explore -> R.navigation.navigation_explore
        R.id.navigation_profile -> R.navigation.navigation_profile
        else -> R.navigation.navigation_feed
    }

    override fun onSupportNavigateUp(): Boolean = navController
            .navigateUp()

    override fun onBackPressed() = bottomNavController.onBackPressed()
}

это решение выглядит хорошо, но есть некоторые вещи, которые я заметил: <FrameLayout /> должен быть NavHostFragment, каждый график имеет собственное домашнее значение по умолчанию, поэтому выполнение этого if (savedInstanceState == null) bottomNavController .onNavigationItemSelected() будет запускать фрагмент два раза, он не сохраняет состояния для фрагментов.

Sim 04.03.2019 07:46

Идея сохраненного состояния экземпляра позволяет избежать создания фрагмента дважды. Я не могу это проверить, потому что в итоге я расширил NavController и создал собственный навигатор исключительно для NavHostFragment, добавив его в этот NavController (я назвал NavHostFragmentNavController). Затем я создаю граф с именем navigation_main.xml с элементами <nav-fragment>, каждый из которых представляет собой нижний элемент навигации. Новые реализации больше, но в использовании довольно просто. В коде все еще есть небольшие ошибки, которые я еще не исправил. Я выложу, когда исправлю.

Allan Veloso 04.03.2019 15:52

да, я думаю, что на данный момент расширение NavController - это разумное решение, пока Google не выпустит образец.

Sim 05.03.2019 08:18

Разве здесь не следует передавать пункт назначения в качестве последнего параметра вместо R.id.navigation_feed? private val bottomNavController by lazy(LazyThreadSafetyMode.NONE) { BottomNavController(this, R.id.container, R.id.navigation_feed) }

WWJD 05.03.2019 11:19

@WWJD, R.id.navigation_feed - это пункт назначения. Я назвал идентификатор графа тем же именем, что и начальный пункт назначения, поэтому у R.navigation.navigation_feed есть пункт назначения R.id.navigation_feed.

Allan Veloso 05.03.2019 15:09

@AllanVeloso, это идеально подходит для меня. Отличное решение, я просто удалил NavigationExtension.kt из демонстрации Google, добавил весь ваш код и отлично работает. Я только что внес некоторые изменения в код, потому что я использую AHBottomNavigation, но работает так же. Для меня это было бы реальным решением сделать многостадийную навигацию с новой системой навигации.

jfcogato 24.04.2019 12:38

Извините за шум, я только что улучшил этот код, добавив «fragmentManager.addOnBackStackChangedListener» в инициализацию контроллера, чтобы вы могли добавить «OnDestinationChangedListener». Таким образом, вы всегда будете знать, в каком фрагменте вы находитесь, от контроллера. Таким образом, вы можете внести некоторые обновления в пользовательский интерфейс Activity в случае необходимости. Напишите мне, если вам нужно обновить код. Еще раз спасибо за эту ветку! Теперь у меня отлично работает.

jfcogato 25.04.2019 11:58

@jfcogato Чтобы отслеживать изменение пункта назначения, вы также можете расширить MutableLiveData. Использование было бы очень простым: DestinationLiveData(navController).observe(this, Observer { dest -> // do things }). Он также автоматически обрабатывает методы добавления и удаления слушателя. Прямо сейчас я пытаюсь разработать более полную библиотеку, которая переопределяет реализацию Jetpack NavController с этим поведением Instagram / youtube, но я все еще исправляю некоторые ошибки.

Allan Veloso 25.04.2019 22:39

@AllanVeloso уверен, что это будет еще один способ получить это. Выглядит более чистым кодом, чем мой :) Буду ждать библиотеку! возможно, я смогу обновить все, что я сделал с вашим. Кстати, вы действительно спасли мне день, это решение отлично работает! и я думаю, что весь мой проект будет работать с этой навигационной системой сейчас и навсегда! :)

jfcogato 26.04.2019 11:05

@AllanVeloso onNavigationItemReselected Не работает. Я имею в виду, что popBackStack всегда возвращает false. В остальном все работает как шарм.

Dushyant Suthar 27.04.2019 11:48

@AllanVeloso Не могли бы вы посмотреть или помочь в этом?

Dushyant Suthar 16.05.2019 15:11

@DushyantSuthar: реализация библиотеки навигации арки Android изменилась, и теперь popBackStack (), на мой взгляд, имеет довольно странное поведение (по мнению команды Android). Вы можете попробовать заменить .popBackStack () на .navigate (navController.currentDestination.parent) или что-то подобное и проверить, работает ли он.

Allan Veloso 25.05.2019 05:11

Короткий и хороший код на Kotlin для соединения нижних элементов навигации с фрагментами внутри графа навигации:

    val navControl = findNavController( R.id.nav_host_frag_main)
    bottomNavigationView?.setupWithNavController(navControl)

* Просто учтите: идентификаторы и фрагменты нижней навигации внутри графа навигации должны иметь одинаковый идентификатор. Также благодаря хорошему объяснению из ответа @sanat

вам также следует скрыть стрелки «вверх» на прямых дочерних элементах BNV: setupActionBarWithNavController (navController, AppBarConfiguration.Builder (bottomNavigationView.menu) .build‌ ())

rubenwardy 10.08.2019 18:57

Лучшее решение - это решение, предоставленное командой Google в его репо, кнопка «Назад» по-прежнему отправляет вас обратно к первой кнопке, но остальное поведение его «нормальное» ... Похоже, что Google по-прежнему не предлагает хорошего решения, даже когда они используют его (YouTube, Google Фото и т. д.), они сказали, что androidx может помочь, но похоже, что мы просто идем и находим обходной путь для обычных вещей.

Вот ссылка на Google Repo, где они используют nav. снизу с навигационным графом для каждой кнопки. https://github.com/android/architecture-components-samples/blob/master/NavigationAdvancedSample/app/src/main/java/com/example/android/navigationadvancedsample/NavigationExtensions.kt скопируйте этот файл в свой проект и посмотрите, как он реализован в их проекте. Для поведения кнопки возврата вы можете создать собственный стек, а onBackpressed просто перемещаться по этому стеку.

Официальных решений не нашел, но использую свой путь

Сначала я создаю стек для фрагментов ручки

    needToAddToBackStack : Boolen = true


    private lateinit var fragmentBackStack: Stack<Int>
    fragmentBackStack = Stack()

И в

navController.addOnDestinationChangedListener { _, destination, _ ->
        if (needToAddToBackStack) {
            fragmentBackStack.add(destination.id)
        }


        needToAddToBackStack = true

    }

и обработайте кнопку возврата

override fun onBackPressed() {
    if (::fragmentBackStack.isInitialized && fragmentBackStack.size > 1) {
        fragmentBackStack.pop()
        val fragmentId = fragmentBackStack.lastElement()
        needToAddToBackStack = false
        navController.navigate(fragmentId)

    } else {
        if (::fragmentBackStack.isInitialized && fragmentBackStack.size == 1) {
            finish()
        } else {
            super.onBackPressed()
        }
    }

Он отлично работает с обычной навигацией, но есть одна проблема при навигации с использованием BottomNavigationView. Например, скажем, у меня есть BottomNavigation с тремя вкладками с уважаемыми фрагментами A, B, C. Теперь мой путь навигации - от фрагмента A до B (щелкните вкладку B), от B до D (это еще один фрагмент, который открывается при нажатии кнопки из B), от D до E (другой фрагмент открывается нажатием кнопки с D) и, наконец, от E до C (при нажатии на вкладку C); оттуда, когда я нажимаю назад, он фрагментирует E, но показывает текущую выбранную вкладку C (в идеале она должна отображать вкладку B). Есть ли способ исправить это?

Manish Karena 22.02.2020 10:24

Прочитав ваш вопрос, я снова проверил документ Google. И я увидел, что они предоставили решение, позволяющее улучшить работу интерфейса навигации с BottomNavigationView. Итак, я создал туториал для всех, кому он нужен, как и мне. Для текстовой версии: https://nhatvm.com/how-to-use-navigationui-with-bottomnavigation-in-android/ А для версии youtube: https://thewikihow.com/video_2uxILvBbkyY

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

Shalan93 28.04.2020 00:40

В навигации с помощью реактивного ранца вам нужно внести изменения только в один xml-файл графа навигации для управления навигацией, обработкой бэкстэков, пользовательской анимацией, передачей аргументов между фрагментами.

Если вы хотите просто вернуться к предыдущему фрагменту,

<action
        android:id = "@+id/action_deleteEmployeeFragment_to_employeesListFragment2"
        app:destination = "@id/employeesListFragment"/>


    btn_cancel.setOnClickListener {
                it.findNavController().popBackStack()
            }

Если вы хотите очистить все бэкстэки и перейти к новому фрагменту

<action
        android:id = "@+id/action_deleteEmployeeFragment_to_employeesListFragment2"
        app:destination = "@id/employeesListFragment"
        app:popUpTo = "@id/employeesListFragment"
        app:popUpToInclusive = "true"
        app:launchSingleTop = "true" />


 btn_submit.setOnClickListener {
                   it.findNavController().navigate(DeleteEmployeeFragmentDirections.actionDeleteEmployeeFragmentToEmployeesListFragment2())
        }

Для получения дополнительной информации: Пример навигации на реактивном ранце

Ключевым моментом для создания правильного заднего стека, который хранит состояние, является наличие NavHostFragment, у которого есть childFragmentManager и их собственный задний стек. Именно это и делает файл расширения расширенного примера компонента "Навигация".

/**
 * Manages the various graphs needed for a [BottomNavigationView].
 *
 * This sample is a workaround until the Navigation Component supports multiple back stacks.
 */
fun BottomNavigationView.setupWithNavController(
    navGraphIds: List<Int>,
    fragmentManager: FragmentManager,
    containerId: Int,
    intent: Intent
): LiveData<NavController> {

    // Map of tags
    val graphIdToTagMap = SparseArray<String>()
    // Result. Mutable live data with the selected controlled
    val selectedNavController = MutableLiveData<NavController>()

    var firstFragmentGraphId = 0

    // First create a NavHostFragment for each NavGraph ID
    navGraphIds.forEachIndexed { index, navGraphId ->
        val fragmentTag = getFragmentTag(index)

        // Find or create the Navigation host fragment
        val navHostFragment = obtainNavHostFragment(
            fragmentManager,
            fragmentTag,
            navGraphId,
            containerId
        )

        // Obtain its id
        val graphId = navHostFragment.navController.graph.id

        if (index == 0) {
            firstFragmentGraphId = graphId
        }

        // Save to the map
        graphIdToTagMap[graphId] = fragmentTag

        // Attach or detach nav host fragment depending on whether it's the selected item.
        if (this.selectedItemId == graphId) {
            // Update livedata with the selected graph
            selectedNavController.value = navHostFragment.navController
            attachNavHostFragment(fragmentManager, navHostFragment, index == 0)
        } else {
            detachNavHostFragment(fragmentManager, navHostFragment)
        }
    }

    // Now connect selecting an item with swapping Fragments
    var selectedItemTag = graphIdToTagMap[this.selectedItemId]
    val firstFragmentTag = graphIdToTagMap[firstFragmentGraphId]
    var isOnFirstFragment = selectedItemTag == firstFragmentTag

    // When a navigation item is selected
    setOnNavigationItemSelectedListener { item ->
        // Don't do anything if the state is state has already been saved.
        if (fragmentManager.isStateSaved) {
            false
        } else {
            val newlySelectedItemTag = graphIdToTagMap[item.itemId]
            if (selectedItemTag != newlySelectedItemTag) {
                // Pop everything above the first fragment (the "fixed start destination")
                fragmentManager.popBackStack(
                    firstFragmentTag,
                    FragmentManager.POP_BACK_STACK_INCLUSIVE
                )
                val selectedFragment = fragmentManager.findFragmentByTag(newlySelectedItemTag)
                        as NavHostFragment

                // Exclude the first fragment tag because it's always in the back stack.
                if (firstFragmentTag != newlySelectedItemTag) {
                    // Commit a transaction that cleans the back stack and adds the first fragment
                    // to it, creating the fixed started destination.
                    fragmentManager.beginTransaction()
                        .attach(selectedFragment)
                        .setPrimaryNavigationFragment(selectedFragment)
                        .apply {
                            // Detach all other Fragments
                            graphIdToTagMap.forEach { _, fragmentTagIter ->
                                if (fragmentTagIter != newlySelectedItemTag) {
                                    detach(fragmentManager.findFragmentByTag(firstFragmentTag)!!)
                                }
                            }
                        }
                        .addToBackStack(firstFragmentTag)
                        .setCustomAnimations(
                            R.anim.nav_default_enter_anim,
                            R.anim.nav_default_exit_anim,
                            R.anim.nav_default_pop_enter_anim,
                            R.anim.nav_default_pop_exit_anim
                        )
                        .setReorderingAllowed(true)
                        .commit()
                }
                selectedItemTag = newlySelectedItemTag
                isOnFirstFragment = selectedItemTag == firstFragmentTag
                selectedNavController.value = selectedFragment.navController
                true
            } else {
                false
            }
        }
    }

    // Optional: on item reselected, pop back stack to the destination of the graph
    setupItemReselected(graphIdToTagMap, fragmentManager)

    // Handle deep link
    setupDeepLinks(navGraphIds, fragmentManager, containerId, intent)

    // Finally, ensure that we update our BottomNavigationView when the back stack changes
    fragmentManager.addOnBackStackChangedListener {
        if (!isOnFirstFragment && !fragmentManager.isOnBackStack(firstFragmentTag)) {
            this.selectedItemId = firstFragmentGraphId
        }

        // Reset the graph if the currentDestination is not valid (happens when the back
        // stack is popped after using the back button).
        selectedNavController.value?.let { controller ->
            if (controller.currentDestination == null) {
                controller.navigate(controller.graph.id)
            }
        }
    }
    return selectedNavController
}

private fun BottomNavigationView.setupDeepLinks(
    navGraphIds: List<Int>,
    fragmentManager: FragmentManager,
    containerId: Int,
    intent: Intent
) {
    navGraphIds.forEachIndexed { index, navGraphId ->
        val fragmentTag = getFragmentTag(index)

        // Find or create the Navigation host fragment
        val navHostFragment = obtainNavHostFragment(
            fragmentManager,
            fragmentTag,
            navGraphId,
            containerId
        )
        // Handle Intent
        if (navHostFragment.navController.handleDeepLink(intent)
            && selectedItemId != navHostFragment.navController.graph.id
        ) {
            this.selectedItemId = navHostFragment.navController.graph.id
        }
    }
}

private fun BottomNavigationView.setupItemReselected(
    graphIdToTagMap: SparseArray<String>,
    fragmentManager: FragmentManager
) {
    setOnNavigationItemReselectedListener { item ->
        val newlySelectedItemTag = graphIdToTagMap[item.itemId]
        val selectedFragment = fragmentManager.findFragmentByTag(newlySelectedItemTag)
                as NavHostFragment
        val navController = selectedFragment.navController
        // Pop the back stack to the start destination of the current navController graph
        navController.popBackStack(
            navController.graph.startDestination, false
        )
    }
}

private fun detachNavHostFragment(
    fragmentManager: FragmentManager,
    navHostFragment: NavHostFragment
) {
    fragmentManager.beginTransaction()
        .detach(navHostFragment)
        .commitNow()
}

private fun attachNavHostFragment(
    fragmentManager: FragmentManager,
    navHostFragment: NavHostFragment,
    isPrimaryNavFragment: Boolean
) {
    fragmentManager.beginTransaction()
        .attach(navHostFragment)
        .apply {
            if (isPrimaryNavFragment) {
                setPrimaryNavigationFragment(navHostFragment)
            }
        }
        .commitNow()

}

private fun obtainNavHostFragment(
    fragmentManager: FragmentManager,
    fragmentTag: String,
    navGraphId: Int,
    containerId: Int
): NavHostFragment {
    // If the Nav Host fragment exists, return it
    val existingFragment = fragmentManager.findFragmentByTag(fragmentTag) as NavHostFragment?
    existingFragment?.let { return it }

    // Otherwise, create it and return it.
    val navHostFragment = NavHostFragment.create(navGraphId)
    fragmentManager.beginTransaction()
        .add(containerId, navHostFragment, fragmentTag)
        .commitNow()
    return navHostFragment
}

private fun FragmentManager.isOnBackStack(backStackName: String): Boolean {
    val backStackCount = backStackEntryCount
    for (index in 0 until backStackCount) {
        if (getBackStackEntryAt(index).name == backStackName) {
            return true
        }
    }
    return false
}

private fun getFragmentTag(index: Int) = "bottomNavigation#$index"

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

приватное развлечение GetNavHostFragment ( fragmentManager: FragmentManager, fragmentTag: строка, navGraphId: Int, containerId: Int ): NavHostFragment { // Если фрагмент Nav Host существует, вернуть его val existingFragment = fragmentManager.findFragmentByTag (fragmentTag) как NavHostFragment? existingFragment? .let {вернуть его}

// Otherwise, create it and return it.
val navHostFragment = NavHostFragment.create(navGraphId)
fragmentManager.beginTransaction()
    .add(containerId, navHostFragment, fragmentTag)
    .commitNow()
return navHostFragment

}

Я построил один, используя расширение NavigationExtension выше, которое выглядит так

BottomNavigationView with Navigation Component

с вложенной навигацией.

Графики навигации похожи, поэтому добавляю только один

nav_graph_home.xml

<?xml version = "1.0" encoding = "utf-8"?>
<navigation xmlns:android = "http://schemas.android.com/apk/res/android"
    xmlns:app = "http://schemas.android.com/apk/res-auto"
    xmlns:tools = "http://schemas.android.com/tools"
    android:id = "@+id/nav_graph_home"
    app:startDestination = "@id/homeFragment1">


    <fragment
        android:id = "@+id/homeFragment1"
        android:name = "com.smarttoolfactory.tutorial5_3navigationui_bottomnavigation_nestednavigation.blankfragment.HomeFragment1"
        android:label = "HomeFragment1"
        tools:layout = "@layout/fragment_home1">
        <action
            android:id = "@+id/action_homeFragment1_to_homeFragment2"
            app:destination = "@id/homeFragment2" />
    </fragment>

    <fragment
        android:id = "@+id/homeFragment2"
        android:name = "com.smarttoolfactory.tutorial5_3navigationui_bottomnavigation_nestednavigation.blankfragment.HomeFragment2"
        android:label = "HomeFragment2"
        tools:layout = "@layout/fragment_home2">
        <action
            android:id = "@+id/action_homeFragment2_to_homeFragment3"
            app:destination = "@id/homeFragment3" />
    </fragment>

    <fragment
        android:id = "@+id/homeFragment3"
        android:name = "com.smarttoolfactory.tutorial5_3navigationui_bottomnavigation_nestednavigation.blankfragment.HomeFragment3"
        android:label = "HomeFragment3"
        tools:layout = "@layout/fragment_home3" >
        <action
            android:id = "@+id/action_homeFragment3_to_homeFragment1"
            app:destination = "@id/homeFragment1"
            app:popUpTo = "@id/homeFragment1"
            app:popUpToInclusive = "true" />
    </fragment>

</navigation>

Меню для нижней навигации

menu_bottom_nav.xml

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

    <item
            android:id = "@+id/nav_graph_home"
            android:icon = "@drawable/ic_baseline_home_24"
            android:title = "Home"/>

    <item
            android:id = "@+id/nav_graph_dashboard"
            android:icon = "@drawable/ic_baseline_dashboard_24"
            android:title = "Dashboard"/>

    <item
            android:id = "@+id/nav_graph_notification"
            android:icon = "@drawable/ic_baseline_notifications_24"
            android:title = "Notification"/>
    
</menu>

Макет для MainActivity, содержащий FragmentContainerView и BottomNavigationView

activiy_main.xml

<?xml version = "1.0" encoding = "utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android = "http://schemas.android.com/apk/res/android"
    xmlns:app = "http://schemas.android.com/apk/res-auto"
    xmlns:tools = "http://schemas.android.com/tools"
    android:layout_width = "match_parent"
    android:layout_height = "match_parent"
    tools:context = ".MainActivity">

    <androidx.fragment.app.FragmentContainerView
        android:id = "@+id/nav_host_container"
        android:layout_width = "0dp"
        android:layout_height = "0dp"
        app:layout_constraintBottom_toTopOf = "@+id/bottom_nav"
        app:layout_constraintLeft_toLeftOf = "parent"
        app:layout_constraintRight_toRightOf = "parent"
        app:layout_constraintTop_toTopOf = "parent" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id = "@+id/bottom_nav"
        android:layout_width = "match_parent"
        android:layout_height = "wrap_content"
        app:layout_constraintBottom_toBottomOf = "parent"
        app:menu = "@menu/menu_bottom_nav" />

</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.kt

class MainActivity : AppCompatActivity() {

    private var currentNavController: LiveData<NavController>? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        supportFragmentManager.addOnBackStackChangedListener {
            val backStackEntryCount = supportFragmentManager.backStackEntryCount
            val fragments = supportFragmentManager.fragments
            val fragmentCount = fragments.size


            Toast.makeText(
                this,
                "MainActivity backStackEntryCount: $backStackEntryCount, fragmentCount: $fragmentCount, fragments: $fragments",
                Toast.LENGTH_SHORT
            ).show()
        }


        if (savedInstanceState == null) {
            setupBottomNavigationBar()
        } // Else, need to wait for onRestoreInstanceState
    }

    override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
        super.onRestoreInstanceState(savedInstanceState)
        // Now that BottomNavigationBar has restored its instance state
        // and its selectedItemId, we can proceed with setting up the
        // BottomNavigationBar with Navigation
        setupBottomNavigationBar()
    }

    /**
     * Called on first creation and when restoring state.
     */
    private fun setupBottomNavigationBar() {
        val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_nav)

        val navGraphIds = listOf(
            R.navigation.nav_graph_home,
            R.navigation.nav_graph_dashboard,
            R.navigation.nav_graph_notification
        )

        // Setup the bottom navigation view with a list of navigation graphs
        val controller = bottomNavigationView.setupWithNavController(
            navGraphIds = navGraphIds,
            fragmentManager = supportFragmentManager,
            containerId = R.id.nav_host_container,
            intent = intent
        )
        // Whenever the selected controller changes, setup the action bar.
        controller.observe(this, Observer { navController ->
            setupActionBarWithNavController(navController)
        })
        currentNavController = controller
    }

    override fun onSupportNavigateUp(): Boolean {
        return currentNavController?.value?.navigateUp() ?: false
    }
}

Макеты и классы фрагментов - это простые классы, поэтому я пропустил их. Вы можете проверить полный образец i built или Репозиторий Google, чтобы изучить расширение для расширенной навигации или другие примеры.

ты хоть представляешь это stackoverflow.com/questions/63052712/…

Sunil Chaudhary 07.08.2020 09:54

@SunilChaudhary, да, если вы проверяете репо для компонентов навигации или функцию расширения выше или по ссылке, вы можете увидеть, как это работает. Примеры в это репо показывают, как это можно сделать разными способами.

Thracian 07.08.2020 10:02

Первоначальный ответ здесь: https://stackoverflow.com/a/63645978/8956093

В Jetpack Navigation Componenet, если вы хотите выполнить какую-либо операцию, когда фрагмент выталкивается, вам необходимо переопределить следующие функции.

  1. Добавьте OnBackPressedCallback во фрагмент, чтобы запустить специальную операцию при нажатии кнопки «Назад» на панели навигации системы внизу.

     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
    
         onBackPressedCallback = object : OnBackPressedCallback(true) {
             override fun handleOnBackPressed() {
                 //perform your operation and call navigateUp
                findNavController().navigateUp()
             }
         }
     requireActivity().onBackPressedDispatcher.addCallback(onBackPressedCallback)
     }
    
  2. Добавьте onOptionsItemMenu во фрагмент для обработки нажатия стрелки назад в верхнем левом углу приложения.

     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
    
         setHasOptionsMenu(true)
     }
    
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
       if (item.itemId == android.R.id.home) {
           //perform your operation and call navigateUp
           findNavController().navigateUp()
           return true
       }
       return super.onOptionsItemSelected(item)
    }
    
  3. Если нет специального кода для запуска при нажатии back на фрагменте хоста, используйте onSupportNavigateUp в Activity.

     override fun onSupportNavigateUp(): Boolean {
       if (navController.navigateUp() == false){
         //navigateUp() returns false if there are no more fragments to pop
         onBackPressed()
       }
       return navController.navigateUp()
     }
    

Обратите внимание, что onSupportNavigateUp () не вызывается, если фрагмент содержит onOptionsItemSelected ()

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

В версии 2.4.0 навигационного пакета он наконец-то официально поддерживается!

https://developer.android.com/jetpack/androidx/releases/navigation#version_240_2

Мало того: после загрузки библиотеки навигации в эту версию эта функция - поведение по умолчанию. И в качестве примечания, теперь это поведение по умолчанию включает в себя то, что фрагменты не воссоздаются при переходе между ними, это казалось чем-то весьма востребованным.

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