Компонент Navigation – это библиотека, которая позволяет управлять сложной навигацией, анимацией переходов, ссылками на контент и передачей аргументов между экранами приложения, проверяемых во время компиляции.
В этом документе приведены общие инструкции по переносу существующего приложения для использования компонента навигации.
В целом перенос состоит из следующих этапов:
Перенесите логику пользовательского интерфейса, относящуюся к экранам, из действий. Перенесите логику пользовательского интерфейса приложения из действий, чтобы каждое действие отвечало только за логику глобальных компонентов пользовательского интерфейса навигации, таких как
Toolbar, а реализация каждого экрана была делегирована фрагменту или пользовательскому целевому объекту.Интегрируйте компонент Navigation. Для каждого объекта activity создайте граф навигации, содержащий один или несколько фрагментов, управляемых этим объектом. Замените транзакции фрагментов операциями компонента Navigation.
Добавьте целевые действия. Замените вызовы
startActivity()действиями с целевыми действиями.Объединение действий. Объедините графы навигации, если у нескольких действий общий макет.
Требования
В этом руководстве предполагается, что вы уже перенесли свое приложение для использования библиотек AndroidX. Если вы этого не сделали, перенесите проект в AndroidX, прежде чем продолжить.
Перенесите логику интерфейса, связанную с экраном, из объектов Activity
Действия – это компоненты системного уровня, которые обеспечивают графическое взаимодействие между вашим приложением и Android. Действия регистрируются в манифесте приложения, чтобы Android знал, какие действия можно запустить. Класс Activity также позволяет приложению реагировать на изменения в Android, например когда интерфейс приложения переходит в активный режим или выходит из него, поворачивается и т. д. Кроме того, в действии можно передавать состояние между экранами.
В контексте вашего приложения действия должны служить хостом для навигации и содержать логику и знания о том, как переходить между экранами, передавать данные и так далее. Однако управление деталями пользовательского интерфейса лучше оставить меньшей, многократно используемой части вашего пользовательского интерфейса. Рекомендуемый способ реализации этого шаблона – фрагменты. Подробнее о преимуществах использования фрагментов читайте в статье Single Activity: Why, When, and How (Одно действие: зачем, когда и как). Навигация поддерживает фрагменты через зависимость navigation-fragment. Навигация также поддерживает пользовательские типы пунктов назначения.
Если в вашем приложении не используются фрагменты, сначала необходимо перенести каждый экран в приложении, чтобы использовать фрагмент. На этом этапе вы не удаляете действие. Вместо этого вы создаете фрагмент, представляющий экран, и разделяете логику пользовательского интерфейса по ответственности.
Представляем фрагменты
Чтобы проиллюстрировать процесс внедрения фрагментов, давайте начнем с примера приложения, состоящего из двух экранов: экрана списка товаров и экрана информации о товаре. Нажав на товар в списке, пользователь переходит на экран с подробной информацией о товаре.

В этом примере список и подробные сведения – это отдельные действия.
Создайте новый макет для размещения пользовательского интерфейса
Чтобы добавить фрагмент, сначала создайте новый файл макета для действия, в котором будет размещен фрагмент. Это заменяет текущий макет представления контента действия.
Для простого режима можно использовать FrameLayout, как показано в следующем примере product_list_host:
<FrameLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_content"
android:layout_height="match_parent"
android:layout_width="match_parent" />
Атрибут id указывает на раздел контента, в который мы позже добавим фрагмент.
Затем в функции onCreate() объекта activity измените ссылку на файл макета в функции onCreate объекта activity, чтобы она указывала на этот новый файл макета:
Kotlin
class ProductListActivity : AppCompatActivity() { ... override fun onCreate(savedInstanceState: Bundle?) { ... // Replace setContentView(R.layout.product_list) with the line below setContentView(R.layout.product_list_host) ... } }
Java
public class ProductListActivity extends AppCompatActivity { ... @Override public void onCreate(@Nullable Bundle savedInstanceState) { ... // Replace setContentView(R.layout.product_list); with the line below setContentView(R.layout.product_list_host); ... } }
Существующий макет (в данном примере – product_list) используется в качестве корневого представления для создаваемого фрагмента.
Создание фрагмента
Создайте новый фрагмент для управления пользовательским интерфейсом экрана. Рекомендуется использовать
одно и то же имя хоста активности. В приведенном ниже фрагменте кода используется ProductListFragment:
Kotlin
class ProductListFragment : Fragment() { // Leave empty for now. }
Java
public class ProductListFragment extends Fragment { // Leave empty for now. }
Перенесите логику объекта activity во фрагмент
После определения фрагмента следующим шагом будет перенос логики пользовательского интерфейса для этого экрана из действия в этот новый фрагмент. Если вы переходите с архитектуры на основе действий, то, скорее всего, у вас есть много логики создания представлений в функции onCreate() действия.
Вот пример экрана на основе Activity с логикой пользовательского интерфейса, которую нужно перенести:
Kotlin
class ProductListActivity : AppCompatActivity() { // Views and/or ViewDataBinding references, Adapters... private lateinit var productAdapter: ProductAdapter private lateinit var binding: ProductListActivityBinding ... // ViewModels, System Services, other Dependencies... private val viewModel: ProductListViewModel by viewModels() ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // View initialization logic DataBindingUtil.setContentView(this, R.layout.product_list_activity) // Post view initialization logic // Connect adapters productAdapter = ProductAdapter(productClickCallback) binding.productsList.setAdapter(productAdapter) // Initialize view properties, set click listeners, etc. binding.productsSearchBtn.setOnClickListener {...} // Subscribe to state viewModel.products.observe(this, Observer { myProducts -> ... }) // ...and so on } ... }
Java
public class ProductListActivity extends AppCompatActivity { // Views and/or ViewDataBinding references, adapters... private ProductAdapter productAdapter; private ProductListActivityBinding binding; ... // ViewModels, system services, other dependencies... private ProductListViewModel viewModel; ... @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // View initialization logic DataBindingUtil.setContentView(this, R.layout.product_list_activity); // Post view initialization logic // Connect adapters productAdapter = new ProductAdapter(productClickCallback); binding.productsList.setAdapter(productAdapter); // Initialize ViewModels and other dependencies ProductListViewModel viewModel = new ViewModelProvider(this).get(ProductListViewModel.java); // Initialize view properties, set click listeners, etc. binding.productsSearchBtn.setOnClickListener(v -> { ... }); // Subscribe to state viewModel.getProducts().observe(this, myProducts -> ... ); // ...and so on }
Ваше действие также может управлять тем, когда и как пользователь переходит к следующему экрану, как показано в следующем примере:
Kotlin
// Provided to ProductAdapter in ProductListActivity snippet. private val productClickCallback = ProductClickCallback { product -> show(product) } fun show(product: Product) { val intent = Intent(this, ProductActivity::class.java) intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.id) startActivity(intent) }
Java
// Provided to ProductAdapter in ProductListActivity snippet. private ProductClickCallback productClickCallback = this::show; private void show(Product product) { Intent intent = new Intent(this, ProductActivity.class); intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.getId()); startActivity(intent); }
Внутри фрагмента вы распределяете эту работу между onCreateView() и onViewCreated(), при этом в действии остается только логика навигации:
Kotlin
class ProductListFragment : Fragment() { private lateinit var binding: ProductListFragmentBinding private val viewModel: ProductListViewModel by viewModels() // View initialization logic override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { binding = DataBindingUtil.inflate( inflater, R.layout.product_list, container, false ) return binding.root } // Post view initialization logic override fun onViewCreated(view: View, savedInstanceState: Bundle?) { // Connect adapters productAdapter = ProductAdapter(productClickCallback) binding.productsList.setAdapter(productAdapter) // Initialize view properties, set click listeners, etc. binding.productsSearchBtn.setOnClickListener {...} // Subscribe to state viewModel.products.observe(this, Observer { myProducts -> ... }) // ...and so on } // Provided to ProductAdapter private val productClickCallback = ProductClickCallback { product -> if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { (requireActivity() as ProductListActivity).show(product) } } ... }
Java
public class ProductListFragment extends Fragment { private ProductAdapter productAdapter; private ProductListFragmentBinding binding; // View initialization logic @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { binding = DataBindingUtil.inflate( inflater, R.layout.product_list_fragment, container, false); return binding.getRoot(); } // Post view initialization logic @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { // Connect adapters binding.productsList.setAdapter(productAdapter); // Initialize ViewModels and other dependencies ProductListViewModel viewModel = new ViewModelProvider(this) .get(ProductListViewModel.class); // Initialize view properties, set click listeners, etc. binding.productsSearchBtn.setOnClickListener(...) // Subscribe to state viewModel.getProducts().observe(this, myProducts -> { ... }); // ...and so on // Provided to ProductAdapter private ProductClickCallback productClickCallback = new ProductClickCallback() { @Override public void onClick(Product product) { if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { ((ProductListActivity) requireActivity()).show(product); } } }; ... }
Обратите внимание, что в ProductListFragment нет вызова setContentView() для заполнения и подключения макета. Во фрагменте onCreateView() инициализирует корневое представление. onCreateView() принимает экземпляр LayoutInflater, который можно использовать для заполнения корневого представления на основе файла ресурса макета. В этом примере повторно используется существующий макет product_list, который использовался действием, поскольку в самом макете ничего менять не нужно.
Если в функциях onStart(), onResume(), onPause() или onStop() есть логика интерфейса, не связанная с навигацией, вы можете перенести ее в соответствующие функции с тем же названием во фрагменте.
Инициализируйте фрагмент в объекте activity
После того, как вы переместили всю логику пользовательского интерфейса во фрагмент, в действии должна остаться только логика навигации.
Kotlin
class ProductListActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.product_list_host) } fun show(product: Product) { val intent = Intent(this, ProductActivity::class.java) intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.id) startActivity(intent) } }
Java
public class ProductListActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.product_list_host); } public void show(Product product) { Intent intent = new Intent(this, ProductActivity.class); intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.getId()); startActivity(intent); } }
Последний шаг – создать экземпляр фрагмента в onCreate() сразу после настройки представления контента:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.product_list_host) if (savedInstanceState == null) { val fragment = ProductListFragment() supportFragmentManager .beginTransaction() .add(R.id.main_content, fragment) .commit() } }
Java
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.product_list_host); if (savedInstanceState == null) { ProductListFragment fragment = new ProductListFragment(); getSupportFragmentManager() .beginTransaction() .add(R.id.main_content, fragment) .commit(); } }
Как показано в этом примере, FragmentManager автоматически сохраняет и восстанавливает фрагменты при изменении конфигурации, поэтому вам нужно добавлять фрагмент, только если savedInstanceState имеет значение null.
Передайте дополнительные параметры намерения фрагменту
Если ваше действие получает Extras через намерение, вы можете передать их непосредственно фрагменту в качестве аргументов.
В этом примере ProductDetailsFragment получает аргументы непосредственно из дополнительных данных намерения действия:
Kotlin
... if (savedInstanceState == null) { val fragment = ProductDetailsFragment() // Intent extras and Fragment Args are both of type android.os.Bundle. fragment.arguments = intent.extras supportFragmentManager .beginTransaction() .add(R.id.main_content, fragment) .commit() } ...
Java
... if (savedInstanceState == null) { ProductDetailsFragment fragment = new ProductDetailsFragment(); // Intent extras and fragment Args are both of type android.os.Bundle. fragment.setArguments(getIntent().getExtras()); getSupportFragmentManager() .beginTransaction() .add(R.id.main_content, fragment) .commit(); } ...
Теперь вы можете протестировать запуск приложения с первым экраном, обновленным для использования фрагмента. Продолжайте переносить остальные экраны, основанные на действиях, и тестируйте их после каждой итерации.
Интеграция компонента навигации
После перехода на архитектуру на основе фрагментов можно начать интеграцию компонента Navigation.
Сначала добавьте в проект последние зависимости Navigation, следуя инструкциям в примечаниях к выпуску библиотеки Navigation.
Создание графа навигации
Компонент Navigation представляет конфигурацию навигации вашего приложения в файле ресурсов в виде графа, аналогично тому, как представлены представления вашего приложения. Это помогает упорядочить навигацию по приложению вне базы кода и позволяет редактировать навигацию по приложению визуально.
Чтобы создать граф навигации, сначала создайте новую папку ресурсов с названием navigation. Чтобы добавить граф, нажмите правой кнопкой мыши на этот каталог и выберите New > Navigation resource file (Создать > Файл ресурсов навигации).

Компонент Navigation использует действие в качестве хоста для навигации и заменяет отдельные фрагменты в этом хосте по мере того, как пользователи перемещаются по приложению. Прежде чем вы сможете начать визуальное оформление навигации по приложению, вам необходимо настроить NavHost внутри действия, в котором будет размещен этот граф. Поскольку мы используем фрагменты, мы можем использовать реализацию NavHost по умолчанию для компонента Navigation – NavHostFragment.
NavHostFragment настраивается с помощью FragmentContainerView, размещенного внутри основного действия, как показано в следующем примере:
<androidx.fragment.app.FragmentContainerView
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/product_list_graph"
app:defaultNavHost="true"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Атрибут app:NavGraph указывает на граф навигации, связанный с этим хостом навигации. При этом будет развернут граф навигации и задано свойство graph для NavHostFragment. Атрибут app:defaultNavHost гарантирует, что NavHostFragment перехватит нажатие системной кнопки "Назад".
Если вы используете навигацию верхнего уровня, например DrawerLayout или BottomNavigationView, этот элемент FragmentContainerView заменяет основной элемент просмотра контента. Примеры можно найти в статье Обновление компонентов пользовательского интерфейса с помощью NavigationUI.
Для простого макета можно включить этот элемент FragmentContainerView в качестве дочернего для корневого элемента ViewGroup:
<FrameLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/main_content"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/product_list_graph"
app:defaultNavHost="true"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
Если вы нажмете на вкладку Дизайн внизу, то увидите график, похожий на приведенный ниже. В левом верхнем углу диаграммы в разделе Целевые страницы вы увидите ссылку на действие NavHost в виде layout_name (resource_id).

Нажмите кнопку со значком плюса
в верхней части страницы, чтобы добавить фрагменты на график.
Компонент Navigation называет отдельные экраны целевыми страницами. Пунктами назначения могут быть фрагменты, действия или пользовательские пункты назначения. В граф можно добавить любой тип пункта назначения, но пункты назначения действий считаются конечными, поскольку после перехода к ним вы работаете в отдельном хосте и графе навигации.
Компонент Navigation относится к способу, которым пользователи переходят из одного пункта назначения в другой, как к действиям. Действия также могут описывать анимацию перехода и поведение всплывающих окон.
Удалить транзакции фрагментов
Теперь, когда вы используете компонент Navigation, если вы перемещаетесь между экранами на основе фрагментов в рамках одного действия, вы можете удалить взаимодействия FragmentManager.
Если в вашем приложении используется несколько фрагментов в одном действии или навигация верхнего уровня, например макет панели навигации или нижняя панель навигации, то вы, вероятно, используете FragmentManager и FragmentTransactions, чтобы добавлять или заменять фрагменты в основном разделе контента пользовательского интерфейса. Теперь это можно заменить и упростить с помощью компонента Navigation, предоставив действия для связывания пунктов назначения в вашем графе, а затем выполнив навигацию с помощью NavController.
Ниже описаны несколько сценариев, с которыми вы можете столкнуться, и способы переноса для каждого из них.
Одно действие, управляющее несколькими фрагментами
Если у вас есть одно действие, которое управляет несколькими фрагментами, код действия может выглядеть следующим образом:
Kotlin
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Logic to load the starting destination // when the Activity is first created if (savedInstanceState == null) { val fragment = ProductListFragment() supportFragmentManager.beginTransaction() .add(R.id.fragment_container, fragment, ProductListFragment.TAG) .commit() } } // Logic to navigate the user to another destination. // This may include logic to initialize and set arguments on the destination // fragment or even transition animations between the fragments (not shown here). fun navigateToProductDetail(productId: String) { val fragment = new ProductDetailsFragment() val args = Bundle().apply { putInt(KEY_PRODUCT_ID, productId) } fragment.arguments = args supportFragmentManager.beginTransaction() .addToBackStack(ProductDetailsFragment.TAG) .replace(R.id.fragment_container, fragment, ProductDetailsFragment.TAG) .commit() } }
Java
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Logic to load the starting destination when the activity is first created. if (savedInstanceState == null) { val fragment = ProductListFragment() supportFragmentManager.beginTransaction() .add(R.id.fragment_container, fragment, ProductListFragment.TAG) .commit(); } } // Logic to navigate the user to another destination. // This may include logic to initialize and set arguments on the destination // fragment or even transition animations between the fragments (not shown here). public void navigateToProductDetail(String productId) { Fragment fragment = new ProductDetailsFragment(); Bundle args = new Bundle(); args.putInt(KEY_PRODUCT_ID, productId); fragment.setArguments(args); getSupportFragmentManager().beginTransaction() .addToBackStack(ProductDetailsFragment.TAG) .replace(R.id.fragment_container, fragment, ProductDetailsFragment.TAG) .commit(); } }
Внутри источника вы можете вызывать функцию навигации в ответ на какое-либо событие, как показано ниже:
Kotlin
class ProductListFragment : Fragment() { ... override fun onViewCreated(view: View, savedInstanceState: Bundle?) { // In this example a callback is passed to respond to an item clicked // in a RecyclerView productAdapter = ProductAdapter(productClickCallback) binding.productsList.setAdapter(productAdapter) } ... // The callback makes the call to the activity to make the transition. private val productClickCallback = ProductClickCallback { product -> (requireActivity() as MainActivity).navigateToProductDetail(product.id) } }
Java
public class ProductListFragment extends Fragment { ... @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { // In this example a callback is passed to respond to an item clicked in a RecyclerView productAdapter = new ProductAdapter(productClickCallback); binding.productsList.setAdapter(productAdapter); } ... // The callback makes the call to the activity to make the transition. private ProductClickCallback productClickCallback = product -> ( ((MainActivity) requireActivity()).navigateToProductDetail(product.getId()) ); }
Вместо этого можно обновить граф навигации, чтобы задать начальный пункт назначения и действия для связывания пунктов назначения, а также определить аргументы, где это необходимо:
<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/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="Product List"
tools:layout="@layout/product_list">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_detail" />
</fragment>
<fragment
android:id="@+id/product_detail"
android:name="com.example.android.persistence.ui.ProductDetailFragment"
android:label="Product Detail"
tools:layout="@layout/product_detail">
<argument
android:name="product_id"
app:argType="integer" />
</fragment>
</navigation>
Затем вы можете обновить информацию о действии:
Kotlin
class MainActivity : AppCompatActivity() { // No need to load the start destination, handled automatically by the Navigation component override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } }
Java
public class MainActivity extends AppCompatActivity { // No need to load the start destination, handled automatically by the Navigation component @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
Метод navigateToProductDetail() больше не требуется. В следующем разделе мы обновим ProductListFragment, чтобы использовать NavController для перехода к следующему экрану сведений о продукте.
Безопасная передача аргументов
В компоненте Navigation есть плагин Gradle под названием Safe Args, который генерирует простые классы объектов и построителей для типобезопасного доступа к аргументам, указанным для пунктов назначения и действий.
После применения плагина любые аргументы, определенные в пункте назначения в вашем графе навигации, заставляют фреймворк компонента Navigation генерировать класс Arguments, который предоставляет типобезопасные аргументы для целевого пункта назначения.
Определение действия заставляет плагин генерировать класс конфигурации Directions, который можно использовать, чтобы сообщить NavController, как направить пользователя к целевому пункту назначения. Когда действие указывает на пункт назначения, требующий аргументов, сгенерированный класс Directions включает методы конструктора, которым требуются эти параметры.
Внутри фрагмента используйте NavController и сгенерированный класс Directions, чтобы предоставить типобезопасные аргументы для целевого пункта назначения, как показано в следующем примере:
Kotlin
class ProductListFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { // In this example a callback is passed to respond to an item clicked in a RecyclerView productAdapter = ProductAdapter(productClickCallback) binding.productsList.setAdapter(productAdapter) } ... // The callback makes the call to the NavController to make the transition. private val productClickCallback = ProductClickCallback { product -> val directions = ProductListDirections.navigateToProductDetail(product.id) findNavController().navigate(directions) } }
Java
public class ProductListFragment extends Fragment { ... @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { // In this example a callback is passed to respond to an item clicked in a RecyclerView productAdapter = new ProductAdapter(productClickCallback); binding.productsList.setAdapter(productAdapter); } ... // The callback makes the call to the activity to make the transition. private ProductClickCallback productClickCallback = product -> { ProductListDirections.ViewProductDetails directions = ProductListDirections.navigateToProductDetail(product.getId()); NavHostFragment.findNavController(this).navigate(directions); }; }
Навигация верхнего уровня
Если в вашем приложении используется DrawerLayout, в действии может быть много логики конфигурации, которая управляет открытием и закрытием панели и переходом к другим пунктам назначения.
В результате у вас получится примерно следующее:
Kotlin
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val toolbar: Toolbar = findViewById(R.id.toolbar) setSupportActionBar(toolbar) val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) val navView: NavigationView = findViewById(R.id.nav_view) val toggle = ActionBarDrawerToggle( this, drawerLayout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close ) drawerLayout.addDrawerListener(toggle) toggle.syncState() navView.setNavigationItemSelectedListener(this) } override fun onBackPressed() { val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) if (drawerLayout.isDrawerOpen(GravityCompat.START)) { drawerLayout.closeDrawer(GravityCompat.START) } else { super.onBackPressed() } } override fun onNavigationItemSelected(item: MenuItem): Boolean { // Handle navigation view item clicks here. when (item.itemId) { R.id.home -> { val homeFragment = HomeFragment() show(homeFragment) } R.id.gallery -> { val galleryFragment = GalleryFragment() show(galleryFragment) } R.id.slide_show -> { val slideShowFragment = SlideShowFragment() show(slideShowFragment) } R.id.tools -> { val toolsFragment = ToolsFragment() show(toolsFragment) } } val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) drawerLayout.closeDrawer(GravityCompat.START) return true } } private fun show(fragment: Fragment) { val drawerLayout = drawer_layout as DrawerLayout val fragmentManager = supportFragmentManager fragmentManager .beginTransaction() .replace(R.id.main_content, fragment) .commit() drawerLayout.closeDrawer(GravityCompat.START) }
Java
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); DrawerLayout drawer = findViewById(R.id.drawer_layout); NavigationView navigationView = findViewById(R.id.nav_view); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawer.addDrawerListener(toggle); toggle.syncState(); navigationView.setNavigationItemSelectedListener(this); } @Override public void onBackPressed() { DrawerLayout drawer = findViewById(R.id.drawer_layout); if (drawer.isDrawerOpen(GravityCompat.START)) { drawer.closeDrawer(GravityCompat.START); } else { super.onBackPressed(); } } @Override public boolean onNavigationItemSelected(MenuItem item) { // Handle navigation view item clicks here. int id = item.getItemId(); if (id == R.id.home) { Fragment homeFragment = new HomeFragment(); show(homeFragment); } else if (id == R.id.gallery) { Fragment galleryFragment = new GalleryFragment(); show(galleryFragment); } else if (id == R.id.slide_show) { Fragment slideShowFragment = new SlideShowFragment(); show(slideShowFragment); } else if (id == R.id.tools) { Fragment toolsFragment = new ToolsFragment(); show(toolsFragment); } DrawerLayout drawer = findViewById(R.id.drawer_layout); drawer.closeDrawer(GravityCompat.START); return true; } private void show(Fragment fragment) { DrawerLayout drawerLayout = findViewById(R.id.drawer_layout); FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager .beginTransaction() .replace(R.id.main_content, fragment) .commit(); drawerLayout.closeDrawer(GravityCompat.START); } }
Добавив компонент Navigation в проект и создав граф навигации, добавьте в него все целевые страницы (например, Главная, Галерея, Слайд-шоу и Инструменты из примера выше). Убедитесь, что значения id пунктов меню соответствуют значениям id связанных с ними целевых страниц, как показано ниже:
<!-- activity_main_drawer.xml -->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">
<group android:checkableBehavior="single">
<item
android:id="@+id/home"
android:icon="@drawable/ic_menu_camera"
android:title="@string/menu_home" />
<item
android:id="@+id/gallery"
android:icon="@drawable/ic_menu_gallery"
android:title="@string/menu_gallery" />
<item
android:id="@+id/slide_show"
android:icon="@drawable/ic_menu_slideshow"
android:title="@string/menu_slideshow" />
<item
android:id="@+id/tools"
android:icon="@drawable/ic_menu_manage"
android:title="@string/menu_tools" />
</group>
</menu>
<!-- activity_main_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/main_graph"
app:startDestination="@id/home">
<fragment
android:id="@+id/home"
android:name="com.example.HomeFragment"
android:label="Home"
tools:layout="@layout/home" />
<fragment
android:id="@+id/gallery"
android:name="com.example.GalleryFragment"
android:label="Gallery"
tools:layout="@layout/gallery" />
<fragment
android:id="@+id/slide_show"
android:name="com.example.SlideShowFragment"
android:label="Slide Show"
tools:layout="@layout/slide_show" />
<fragment
android:id="@+id/tools"
android:name="com.example.ToolsFragment"
android:label="Tools"
tools:layout="@layout/tools" />
</navigation>
Если значения id из меню и графа совпадают, то вы можете подключить NavController для этого действия, чтобы автоматически управлять навигацией на основе элемента меню. NavController также управляет открытием и закрытием DrawerLayout и поведением кнопок "Вверх" и "Назад".
Затем вы можете обновить MainActivity, чтобы связать NavController с Toolbar и NavigationView.
Пример:
Kotlin
class MainActivity : AppCompatActivity() { val drawerLayout by lazy { findViewById<DrawerLayout>(R.id.drawer_layout) } val navController by lazy { (supportFragmentManager.findFragmentById(R.id.main_content) as NavHostFragment).navController } val navigationView by lazy { findViewById<NavigationView>(R.id.nav_view) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val toolbar = findViewById<Toolbar>(R.id.toolbar) setSupportActionBar(toolbar) // Show and Manage the Drawer and Back Icon setupActionBarWithNavController(navController, drawerLayout) // Handle Navigation item clicks // This works with no further action on your part if the menu and destination id’s match. navigationView.setupWithNavController(navController) } override fun onSupportNavigateUp(): Boolean { // Allows NavigationUI to support proper up navigation or the drawer layout // drawer menu, depending on the situation return navController.navigateUp(drawerLayout) } }
Java
public class MainActivity extends AppCompatActivity { private DrawerLayout drawerLayout; private NavController navController; private NavigationView navigationView; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); drawerLayout = findViewById(R.id.drawer_layout); NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.main_content); navController = navHostFragment.getNavController(); navigationView = findViewById(R.id.nav_view); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); // Show and Manage the Drawer and Back Icon NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout); // Handle Navigation item clicks // This works with no further action on your part if the menu and destination id’s match. NavigationUI.setupWithNavController(navigationView, navController); } @Override public boolean onSupportNavigateUp() { // Allows NavigationUI to support proper up navigation or the drawer layout // drawer menu, depending on the situation. return NavigationUI.navigateUp(navController, drawerLayout); } }
Этот же метод можно использовать как с навигацией на основе BottomNavigationView, так и с навигацией на основе меню. Дополнительные примеры можно найти в статье Как обновить компоненты пользовательского интерфейса с помощью NavigationUI.
Добавьте места проведения занятий
После того как вы настроите компонент Navigation для каждого экрана приложения и перестанете использовать FragmentTransactions для перехода между фрагментами, удалите вызовы startActivity.
Сначала найдите в приложении места, где у вас есть два отдельных графа навигации и вы используете startActivity для перехода между ними.
В этом примере есть два графа (A и B) и вызов startActivity() для перехода от A к B.

Kotlin
fun navigateToProductDetails(productId: String) { val intent = Intent(this, ProductDetailsActivity::class.java) intent.putExtra(KEY_PRODUCT_ID, productId) startActivity(intent) }
Java
private void navigateToProductDetails(String productId) { Intent intent = new Intent(this, ProductDetailsActivity.class); intent.putExtra(KEY_PRODUCT_ID, productId); startActivity(intent);
Затем замените их пунктом назначения в графе A, который представляет переход к объекту activity графа B. Если вам нужно передать аргументы в начальный пункт назначения графа B, вы можете указать их в определении пункта назначения объекта activity.
В примере ниже граф A определяет целевой объект activity, который принимает аргумент product_id вместе с действием. График B не содержит изменений.

XML-представление графов A и B может выглядеть следующим образом:
<!-- Graph A -->
<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/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="Product List"
tools:layout="@layout/product_list_fragment">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_details_activity" />
</fragment>
<activity
android:id="@+id/product_details_activity"
android:name="com.example.android.persistence.ui.ProductDetailsActivity"
android:label="Product Details"
tools:layout="@layout/product_details_host">
<argument
android:name="product_id"
app:argType="integer" />
</activity>
</navigation>
<!-- Graph B -->
<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"
app:startDestination="@id/product_details">
<fragment
android:id="@+id/product_details"
android:name="com.example.android.persistence.ui.ProductDetailsFragment"
android:label="Product Details"
tools:layout="@layout/product_details_fragment">
<argument
android:name="product_id"
app:argType="integer" />
</fragment>
</navigation>
Вы можете перейти к хост-активности графа B, используя те же механизмы, которые вы используете для перехода к пунктам назначения фрагментов:
Kotlin
fun navigateToProductDetails(productId: String) { val directions = ProductListDirections.navigateToProductDetail(productId) findNavController().navigate(directions) }
Java
private void navigateToProductDetails(String productId) { ProductListDirections.NavigateToProductDetail directions = ProductListDirections.navigateToProductDetail(productId); Navigation.findNavController(getView()).navigate(directions);
Передача аргументов целевого действия в фрагмент начального целевого объекта
Если целевое действие получает дополнительные данные, как в предыдущем примере, вы можете передать их непосредственно в начальный пункт назначения в качестве аргументов. Однако вам нужно вручную задать граф навигации хоста в методе onCreate() действия хоста, чтобы вы могли передать дополнительные данные намерения в качестве аргументов фрагменту, как показано ниже.
Kotlin
class ProductDetailsActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.product_details_host) val navHostFragment = supportFragmentManager.findFragmentById(R.id.main_content) as NavHostFragment val navController = navHostFragment.navController navController .setGraph(R.navigation.product_detail_graph, intent.extras) } }
Java
public class ProductDetailsActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.product_details_host); NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.main_content); NavController navController = navHostFragment.getNavController(); navController .setGraph(R.navigation.product_detail_graph, getIntent().getExtras()); } }
Данные можно извлечь из аргументов фрагмента Bundle с помощью сгенерированного класса args, как показано в следующем примере:
Kotlin
class ProductDetailsFragment : Fragment() { val args by navArgs<ProductDetailsArgs>() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val productId = args.productId ... } ...
Java
public class ProductDetailsFragment extends Fragment { ProductDetailsArgs args; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); args = ProductDetailsArgs.fromBundle(requireArguments()); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { int productId = args.getProductId(); ... } ...
Объединить действия
Вы можете объединить графы навигации, если у нескольких действий один и тот же макет, например простой FrameLayout, содержащий один фрагмент. В большинстве таких случаев вы можете просто объединить все элементы из каждого графа навигации и обновить все элементы целевых объектов действий до целевых объектов фрагментов.
В следующем примере объединены графики А и Б из предыдущего раздела:
Перед объединением:
<!-- Graph A -->
<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/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="Product List Fragment"
tools:layout="@layout/product_list">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_details_activity" />
</fragment>
<activity
android:id="@+id/product_details_activity"
android:name="com.example.android.persistence.ui.ProductDetailsActivity"
android:label="Product Details Host"
tools:layout="@layout/product_details_host">
<argument android:name="product_id"
app:argType="integer" />
</activity>
</navigation>
<!-- Graph B -->
<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/product_detail_graph"
app:startDestination="@id/product_details">
<fragment
android:id="@+id/product_details"
android:name="com.example.android.persistence.ui.ProductDetailsFragment"
android:label="Product Details"
tools:layout="@layout/product_details">
<argument
android:name="product_id"
app:argType="integer" />
</fragment>
</navigation>
После объединения:
<!-- Combined Graph A and B -->
<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/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="Product List Fragment"
tools:layout="@layout/product_list">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_details" />
</fragment>
<fragment
android:id="@+id/product_details"
android:name="com.example.android.persistence.ui.ProductDetailsFragment"
android:label="Product Details"
tools:layout="@layout/product_details">
<argument
android:name="product_id"
app:argType="integer" />
</fragment>
</navigation>
Если вы сохраните названия действий, то объединение пройдет без проблем и вам не придется менять существующую базу кода. Например, в этом случае navigateToProductDetail не меняется. Единственное отличие состоит в том, что это действие теперь представляет собой переход к целевому фрагменту в том же NavHost, а не к целевому объекту activity:
Kotlin
fun navigateToProductDetails(productId: String) { val directions = ProductListDirections.navigateToProductDetail(productId) findNavController().navigate(directions) }
Java
private void navigateToProductDetails(String productId) { ProductListDirections.NavigateToProductDetail directions = ProductListDirections.navigateToProductDetail(productId); Navigation.findNavController(getView()).navigate(directions);
Дополнительные ресурсы
Дополнительную информацию о навигации можно найти в следующих статьях:
- Обновление компонентов пользовательского интерфейса с помощью NavigationUI. Узнайте, как управлять навигацией с помощью верхней панели приложения, панели навигации и нижней панели навигации.
- Тестирование навигации. Узнайте, как тестировать рабочие процессы навигации в приложении.