Компонент Navigation включает класс NavigationUI. Этот класс содержит статические методы, которые управляют навигацией с помощью верхней панели приложения, панели навигации и нижней панели навигации.
Верхняя панель приложения
Верхняя панель приложения – это область в верхней части экрана, где показывается информация и действия, доступные на текущем экране.
NavigationUI содержит методы, которые автоматически обновляют контент на верхней панели приложения, когда пользователи перемещаются по нему. Например, NavigationUI использует ярлыки пунктов назначения из графа навигации, чтобы поддерживать заголовок верхней панели приложения в актуальном состоянии.
<navigation> <fragment ... android:label="Page title"> ... </fragment> </navigation>
При использовании NavigationUI с реализациями верхней панели приложения, описанными ниже, ярлык, который вы прикрепляете к пунктам назначения, может автоматически заполняться из аргументов, предоставленных пункту назначения, с использованием формата {argName} в вашем ярлыке.
NavigationUI поддерживает следующие типы верхних панелей приложений:
Подробнее о настройке панели приложения…
AppBarConfiguration
NavigationUI использует объект AppBarConfiguration для управления поведением кнопки навигации в левом верхнем углу области отображения приложения. Поведение кнопки навигации зависит от того, находится ли пользователь в разделе верхнего уровня.
Целевой сервис верхнего уровня – это корневой или самый высокий уровень в наборе иерархически связанных целевых сервисов. В пунктах назначения верхнего уровня кнопка "Вверх" на верхней панели приложения не отображается, поскольку выше них ничего нет. По умолчанию начальный пункт назначения вашего приложения является единственным пунктом назначения верхнего уровня.
Когда пользователь находится в разделе верхнего уровня, кнопка навигации превращается в значок панели навигации
, если в разделе используется DrawerLayout. Если в пункте назначения не используется DrawerLayout, кнопка навигации будет скрыта. Если пользователь находится в любом другом месте, кнопка навигации выглядит как кнопка "Вверх"
.
Чтобы настроить кнопку Navigation, используя только начальный пункт назначения в качестве пункта назначения верхнего уровня, создайте объект AppBarConfiguration и передайте соответствующий граф навигации, как показано ниже.
Kotlin
val appBarConfiguration = AppBarConfiguration(navController.graph)
Java
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build();
В некоторых случаях вместо стандартного начального пункта назначения может потребоваться определить несколько пунктов назначения верхнего уровня. Распространенный пример – использование BottomNavigationView, когда у вас могут быть дочерние экраны, которые не связаны друг с другом иерархически и могут иметь собственный набор связанных пунктов назначения. В таких случаях вы можете передать конструктору набор идентификаторов целевых страниц, как показано ниже:
Kotlin
val appBarConfiguration = AppBarConfiguration(setOf(R.id.main, R.id.profile))
Java
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(R.id.main, R.id.profile).build();
Как создать панель инструментов
Чтобы создать панель инструментов с помощью NavigationUI, сначала определите ее в основном действии, как показано ниже.
<LinearLayout> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" /> <androidx.fragment.app.FragmentContainerView android:id="@+id/nav_host_fragment" ... /> ... </LinearLayout>
Затем вызовите setupWithNavController() из метода onCreate() основного объекта activity, как показано в примере ниже.
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { setContentView(R.layout.activity_main) ... val navController = findNavController(R.id.nav_host_fragment) val appBarConfiguration = AppBarConfiguration(navController.graph) findViewById<Toolbar>(R.id.toolbar) .setupWithNavController(navController, appBarConfiguration) }
Java
@Override protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main); ... NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment); AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build(); Toolbar toolbar = findViewById(R.id.toolbar); NavigationUI.setupWithNavController( toolbar, navController, appBarConfiguration); }
Чтобы кнопка Navigation отображалась как кнопка "Вверх" для всех пунктов назначения, передайте пустой набор идентификаторов пунктов назначения для пунктов назначения верхнего уровня при создании AppBarConfiguration. Это может быть полезно, если, например, у вас есть вторая операция, которая должна показывать кнопку "Вверх" в Toolbar для всех целевых страниц. Это позволяет пользователю вернуться к родительскому действию, когда в обратном стеке нет других пунктов назначения. Вы можете использовать setFallbackOnNavigateUpListener(), чтобы управлять резервным поведением, когда navigateUp() не выполняет никаких действий, как показано в следующем примере:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { ... val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment val navController = navHostFragment.navController val appBarConfiguration = AppBarConfiguration( topLevelDestinationIds = setOf(), fallbackOnNavigateUpListener = ::onSupportNavigateUp ) findViewById<Toolbar>(R.id.toolbar) .setupWithNavController(navController, appBarConfiguration) }
Java
@Override protected void onCreate(Bundle savedInstanceState) { ... NavHostFragment navHostFragment = (NavHostFragment) supportFragmentManager.findFragmentById(R.id.nav_host_fragment); NavController navController = navHostFragment.getNavController(); AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder() .setFallbackOnNavigateUpListener(::onSupportNavigateUp) .build(); Toolbar toolbar = findViewById(R.id.toolbar); NavigationUI.setupWithNavController( toolbar, navController, appBarConfiguration); }
Добавьте CollapsingToolbarLayout
Чтобы добавить CollapsingToolbarLayout на панель инструментов, сначала определите панель инструментов и окружающий макет в своем объекте activity, как показано ниже.
<LinearLayout> <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" android:layout_height="@dimen/tall_toolbar_height"> <com.google.android.material.appbar.CollapsingToolbarLayout android:id="@+id/collapsing_toolbar_layout" android:layout_width="match_parent" android:layout_height="match_parent" app:contentScrim="?attr/colorPrimary" app:expandedTitleGravity="top" app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin"/> </com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.AppBarLayout> <androidx.fragment.app.FragmentContainerView android:id="@+id/nav_host_fragment" ... /> ... </LinearLayout>
Затем вызовите setupWithNavController() из метода onCreate основного объекта activity, как показано ниже.
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { setContentView(R.layout.activity_main) ... val layout = findViewById<CollapsingToolbarLayout>(R.id.collapsing_toolbar_layout) val toolbar = findViewById<Toolbar>(R.id.toolbar) val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment val navController = navHostFragment.navController val appBarConfiguration = AppBarConfiguration(navController.graph) layout.setupWithNavController(toolbar, navController, appBarConfiguration) }
Java
@Override protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main); ... CollapsingToolbarLayout layout = findViewById(R.id.collapsing_toolbar_layout); Toolbar toolbar = findViewById(R.id.toolbar); NavHostFragment navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment); NavController navController = navHostFragment.getNavController(); AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build(); NavigationUI.setupWithNavController(layout, toolbar, navController, appBarConfiguration); }
Панель действий
Чтобы добавить поддержку навигации на панель действий по умолчанию, вызовите setupActionBarWithNavController() из метода onCreate() основного объекта activity, как показано ниже. Обратите внимание, что вам нужно объявить AppBarConfiguration за пределами onCreate(), поскольку вы также используете его при переопределении onSupportNavigateUp():
Kotlin
private lateinit var appBarConfiguration: AppBarConfiguration ... override fun onCreate(savedInstanceState: Bundle?) { ... val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment val navController = navHostFragment.navController appBarConfiguration = AppBarConfiguration(navController.graph) setupActionBarWithNavController(navController, appBarConfiguration) }
Java
AppBarConfiguration appBarConfiguration; ... @Override protected void onCreate(Bundle savedInstanceState) { ... NavHostFragment navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment); NavController navController = navHostFragment.getNavController(); appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build(); NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration); }
Затем переопределите onSupportNavigateUp(), чтобы обрабатывать навигацию вверх:
Kotlin
override fun onSupportNavigateUp(): Boolean { val navController = findNavController(R.id.nav_host_fragment) return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp() }
Java
@Override public boolean onSupportNavigateUp() { NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment); return NavigationUI.navigateUp(navController, appBarConfiguration) || super.onSupportNavigateUp(); }
Поддержка вариантов панели приложения
Добавление верхней панели приложения в действие хорошо работает, когда макет панели приложения одинаков для каждого пункта назначения в приложении. Однако если верхняя панель приложения существенно меняется в зависимости от пункта назначения, рассмотрите возможность удаления верхней панели приложения из действия и определения ее в каждом фрагменте пункта назначения.
Например, в одном из разделов может использоваться стандартный элемент Toolbar, а в другом – AppBarLayout, чтобы создать более сложную панель приложения с вкладками, как показано на рисунке 2.
Toolbar. Справа – AppBarLayout с Toolbar и вкладками.Чтобы реализовать этот пример в целевых фрагментах с помощью NavigationUI, сначала определите панель приложения в каждом из макетов фрагментов, начиная с целевого фрагмента, в котором используется стандартная панель инструментов:
<LinearLayout>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
... />
...
</LinearLayout>
Затем определите целевой фрагмент, который использует панель приложения с вкладками:
<LinearLayout>
<com.google.android.material.appbar.AppBarLayout
... />
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
... />
<com.google.android.material.tabs.TabLayout
... />
</com.google.android.material.appbar.AppBarLayout>
...
</LinearLayout>
Логика настройки навигации одинакова для обоих фрагментов, за исключением того, что вы должны вызывать setupWithNavController() из метода onViewCreated() каждого фрагмента, а не инициализировать их из действия:
Kotlin
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val navController = findNavController() val appBarConfiguration = AppBarConfiguration(navController.graph) view.findViewById<Toolbar>(R.id.toolbar) .setupWithNavController(navController, appBarConfiguration) }
Java
@Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { NavController navController = Navigation.findNavController(view); AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build(); Toolbar toolbar = view.findViewById(R.id.toolbar); NavigationUI.setupWithNavController( toolbar, navController, appBarConfiguration); }
Свяжите пункты меню с целевыми страницами
NavigationUI также предоставляет вспомогательные средства для привязки пунктов назначения к компонентам пользовательского интерфейса, управляемым меню. NavigationUI содержит вспомогательный метод onNavDestinationSelected(), который принимает MenuItem вместе с NavController, где размещается связанный пункт назначения. Если id MenuItem совпадает с id пункта назначения, NavController может построить маршрут до этого пункта.
В качестве примера ниже приведены фрагменты XML, определяющие пункт меню и пункт назначения с общим id, details_page_fragment:
<?xml version="1.0" encoding="utf-8"?> <navigation xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android" ... > ... <fragment android:id="@+id/details_page_fragment" android:label="@string/details" android:name="com.example.android.myapp.DetailsFragment" /> </navigation>
<menu xmlns:android="http://schemas.android.com/apk/res/android"> ... <item android:id="@+id/details_page_fragment" android:icon="@drawable/ic_details" android:title="@string/details" /> </menu>
Если меню было добавлено через onCreateOptionsMenu() объекта Activity, вы можете связать пункты меню с целевыми страницами, переопределив onOptionsItemSelected() объекта Activity для вызова onNavDestinationSelected(), как показано в следующем примере:
Kotlin
override fun onOptionsItemSelected(item: MenuItem): Boolean { val navController = findNavController(R.id.nav_host_fragment) return item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item) }
Java
@Override public boolean onOptionsItemSelected(MenuItem item) { NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment); return NavigationUI.onNavDestinationSelected(item, navController) || super.onOptionsItemSelected(item); }
Теперь, когда пользователь нажимает на пункт меню details_page_fragment, приложение автоматически переходит к соответствующему пункту назначения с тем же id.
Как добавить панель навигации
Панель навигации – это элемент интерфейса, в котором находится главное меню навигации приложения.
Она появляется, когда пользователь нажимает на значок панели навигации
на панели приложения или проводит пальцем от левого края экрана.
Значок панели навигации отображается во всех разделах верхнего уровня, в которых используется DrawerLayout.
Чтобы добавить панель навигации, сначала объявите DrawerLayout в качестве корневого представления. Внутри DrawerLayout добавьте макет для основного контента пользовательского интерфейса и ещё одно представление, содержащее контент панели навигации.
Например, в следующем макете используется DrawerLayout с двумя дочерними представлениями: NavHostFragment для основного контента и NavigationView для содержимого панели навигации.
<?xml version="1.0" encoding="utf-8"?>
<!-- Use DrawerLayout as root container for activity -->
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<!-- Layout to contain contents of main body of screen (drawer will slide over this) -->
<androidx.fragment.app.FragmentContainerView
android:name="androidx.navigation.fragment.NavHostFragment"
android:id="@+id/nav_host_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
<!-- Container for contents of drawer - use NavigationView to make configuration easier -->
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true" />
</androidx.drawerlayout.widget.DrawerLayout>
Затем подключите DrawerLayout к графу навигации, передав его в AppBarConfiguration, как показано в следующем примере:
Kotlin
val appBarConfiguration = AppBarConfiguration(navController.graph, drawerLayout)
Java
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()) .setDrawerLayout(drawerLayout) .build();
Затем в основном классе activity вызовите метод setupWithNavController() из метода onCreate() основного класса activity, как показано ниже.
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { setContentView(R.layout.activity_main) ... val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment val navController = navHostFragment.navController findViewById<NavigationView>(R.id.nav_view) .setupWithNavController(navController) }
Java
@Override protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main); ... NavHostFragment navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment); NavController navController = navHostFragment.getNavController(); NavigationView navView = findViewById(R.id.nav_view); NavigationUI.setupWithNavController(navView, navController); }
Начиная с версии Navigation 2.4.0-alpha01, состояние каждого пункта меню сохраняется и восстанавливается при использовании setupWithNavController.
Нижняя панель навигации
NavigationUI также может обрабатывать нижнюю панель навигации. Когда пользователь выбирает пункт меню, NavController вызывает onNavDestinationSelected() и автоматически обновляет выбранный пункт на нижней панели навигации.
Чтобы создать нижнюю панель навигации в приложении, сначала определите ее в основном действии, как показано ниже.
<LinearLayout> ... <androidx.fragment.app.FragmentContainerView android:id="@+id/nav_host_fragment" ... /> <com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/bottom_nav" app:menu="@menu/menu_bottom_nav" /> </LinearLayout>
Затем в основном классе activity вызовите метод setupWithNavController() из метода onCreate() основного класса activity, как показано ниже.
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { setContentView(R.layout.activity_main) ... val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment val navController = navHostFragment.navController findViewById<BottomNavigationView>(R.id.bottom_nav) .setupWithNavController(navController) }
Java
@Override protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main); ... NavHostFragment navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment); NavController navController = navHostFragment.getNavController(); BottomNavigationView bottomNav = findViewById(R.id.bottom_nav); NavigationUI.setupWithNavController(bottomNav, navController); }
Начиная с версии Navigation 2.4.0-alpha01, состояние каждого пункта меню сохраняется и восстанавливается при использовании setupWithNavController.
Как отслеживать события навигации
Взаимодействие с NavController – основной способ навигации между разделами. NavController отвечает за замену содержимого NavHost новым целевым объектом. Во многих случаях элементы интерфейса, такие как верхняя панель приложения или другие постоянные элементы управления навигацией, например BottomNavigationBar, находятся за пределами NavHost и должны обновляться при переходе между пунктами назначения.
NavController предлагает интерфейс OnDestinationChangedListener, который вызывается, когда меняется текущий пункт назначения NavController или его аргументы. Новый прослушиватель можно зарегистрировать с помощью метода addOnDestinationChangedListener(). Обратите внимание, что при вызове addOnDestinationChangedListener(), если текущий пункт назначения существует, он немедленно отправляется вашему слушателю.
NavigationUI использует OnDestinationChangedListener, чтобы сделать эти распространенные компоненты пользовательского интерфейса совместимыми с навигацией. Однако вы также можете использовать OnDestinationChangedListener отдельно, чтобы любой пользовательский интерфейс или бизнес-логика знали о событиях навигации.
Например, у вас могут быть общие элементы интерфейса, которые вы хотите показывать в одних областях приложения и скрывать в других. Используя собственный OnDestinationChangedListener, вы можете выборочно показывать или скрывать эти элементы интерфейса в зависимости от целевого пункта назначения, как показано в следующем примере:
Kotlin
navController.addOnDestinationChangedListener { _, destination, _ -> if(destination.id == R.id.full_screen_destination) { toolbar.visibility = View.GONE bottomNavigationView.visibility = View.GONE } else { toolbar.visibility = View.VISIBLE bottomNavigationView.visibility = View.VISIBLE } }
Java
navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() { @Override public void onDestinationChanged(@NonNull NavController controller, @NonNull NavDestination destination, @Nullable Bundle arguments) { if(destination.getId() == R.id.full_screen_destination) { toolbar.setVisibility(View.GONE); bottomNavigationView.setVisibility(View.GONE); } else { toolbar.setVisibility(View.VISIBLE); bottomNavigationView.setVisibility(View.VISIBLE); } } });
Прослушиватели на основе аргументов
В качестве альтернативы вы также можете использовать аргументы со значениями по умолчанию в графе навигации, которые могут использоваться соответствующим контроллером пользовательского интерфейса для обновления его состояния. Например, вместо того чтобы основывать логику в OnDestinationChangedListener на идентификаторе пункта назначения, как в предыдущем примере, мы можем создать аргумент в NavGraph:
<?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" android:id="@+id/navigation\_graph" app:startDestination="@id/fragmentOne"> <fragment android:id="@+id/fragmentOne" android:name="com.example.android.navigation.FragmentOne" android:label="FragmentOne"> <action android:id="@+id/action\_fragmentOne\_to\_fragmentTwo" app:destination="@id/fragmentTwo" /> </fragment> <fragment android:id="@+id/fragmentTwo" android:name="com.example.android.navigation.FragmentTwo" android:label="FragmentTwo"> <argument android:name="ShowAppBar" android:defaultValue="true" /> </fragment> </navigation>
Этот аргумент не используется при переходе к пункту назначения, а служит для добавления дополнительной информации к пункту назначения с помощью defaultValue. В этом случае значение указывает, должна ли отображаться панель приложения при переходе к этому пункту назначения.
Теперь мы можем добавить OnDestinationChangedListener в Activity:
Kotlin
navController.addOnDestinationChangedListener { _, _, arguments -> appBar.isVisible = arguments?.getBoolean("ShowAppBar", false) == true }
Java
navController.addOnDestinationChangedListener( new NavController.OnDestinationChangedListener() { @Override public void onDestinationChanged( @NonNull NavController controller, @NonNull NavDestination destination, @Nullable Bundle arguments ) { boolean showAppBar = false; if (arguments != null) { showAppBar = arguments.getBoolean("ShowAppBar", false); } if(showAppBar) { appBar.setVisibility(View.VISIBLE); } else { appBar.setVisibility(View.GONE); } } } );
NavController вызывает этот обратный вызов при каждом изменении пункта назначения. Теперь Activity может обновлять состояние или видимость принадлежащих ему компонентов пользовательского интерфейса на основе аргументов, полученных в обратном вызове.
Преимущество такого подхода в том, что Activity видит только аргументы в графе навигации и не знает о ролях и обязанностях отдельных Fragment. Аналогично, отдельные фрагменты не знают о содержащем их классе Activity и компонентах пользовательского интерфейса, которыми он владеет.
Дополнительные ресурсы
Чтобы узнать больше о навигации, ознакомьтесь со следующими дополнительными ресурсами.
Образцы
- Пример базовой навигации с компонентами архитектуры Android
- Пример расширенной навигации с компонентами архитектуры Android
Практические работы
Записи в блоге
Видео
- 10 рекомендаций по переходу на Single Activity
- Single Activity: Why, When, and How (Android Dev Summit '18)
- Android Jetpack: управление навигацией в интерфейсе с помощью Navigation Controller (Google I/O, 2018 г.)