Миграция в компонент "Навигация",Миграция в компонент "Навигация"

Компонент «Навигация» — это библиотека, которая может управлять сложной навигацией, анимацией перехода, глубокими ссылками и передачей аргументов, проверяемых во время компиляции, между экранами вашего приложения.

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

На высоком уровне миграция включает в себя следующие шаги:

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

  2. Интегрируйте компонент навигации . Для каждого действия создайте граф навигации, содержащий один или несколько фрагментов, управляемых этим действием. Замените фрагментные транзакции операциями компонента навигации.

  3. Добавить места назначения действий . Замените вызовы startActivity() действиями, использующими места назначения действий.

  4. Объединить действия . Объедините навигационные графики в тех случаях, когда несколько действий имеют общий макет.

Предварительные условия

В этом руководстве предполагается, что вы уже перенесли свое приложение на использование библиотек AndroidX . Если вы этого не сделали, перенесите свой проект на использование AndroidX, прежде чем продолжить.

Удалите логику пользовательского интерфейса, специфичную для экрана, из действий.

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

В контексте вашего приложения действия должны служить хостом для навигации и содержать логику и знания о том, как переходить между экранами, передавать данные и т. д. Однако управление деталями вашего пользовательского интерфейса лучше оставить небольшой, многократно используемой части вашего пользовательского интерфейса. Рекомендуемая реализация этого шаблона — фрагменты . См . раздел «Одно действие: почему, когда и как», чтобы узнать больше о преимуществах использования фрагментов. Навигация поддерживает фрагменты через зависимость навигационного фрагмента . Навигация также поддерживает пользовательские типы пунктов назначения .

Если ваше приложение не использует фрагменты, первое, что вам нужно сделать, — это перенести каждый экран вашего приложения на использование фрагмента. На данный момент вы не удаляете действие. Скорее, вы создаете фрагмент, представляющий экран, и разбиваете логику пользовательского интерфейса на части по ответственности.

Знакомство с фрагментами

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

В этом примере экраны списка и подробностей в настоящее время являются отдельными действиями.

Создайте новый макет для размещения пользовательского интерфейса.

Чтобы представить фрагмент, начните с создания нового файла макета для действия, в котором будет размещаться фрагмент. Это заменяет текущий макет представления контента действия.

Для простого представления вы можете использовать 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() вашего действия измените ссылку на файл макета в функции onCreate вашего действия, чтобы она указывала на этот новый файл макета:

Котлин

class ProductListActivity : AppCompatActivity() {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Replace setContentView(R.layout.product_list) with the line below
        setContentView(R.layout.product_list_host)
        ...
    }
}

Ява

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 , например:

Котлин

class ProductListFragment : Fragment() {
    // Leave empty for now.
}

Ява

public class ProductListFragment extends Fragment {
    // Leave empty for now.
}

Переместить логику активности во фрагмент

После определения фрагмента следующим шагом будет перемещение логики пользовательского интерфейса для этого экрана из активности в этот новый фрагмент. Если вы используете архитектуру, основанную на действиях, у вас, вероятно, есть много логики создания представлений, происходящей в функции onCreate() вашей активности.

Вот пример экрана на основе активности с логикой пользовательского интерфейса, который нам нужно переместить:

Котлин

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
    }
   ...
}

Ява

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
   }

Ваша активность также может контролировать, когда и как пользователь переходит к следующему экрану, как показано в следующем примере:

Котлин

    // 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)
    }

Ява

// 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() , при этом в активности остается только логика навигации:

Котлин

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)
        }
    }
    ...
}

Ява

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() вашей активности, которые не связаны с навигацией, вы можете переместить их в соответствующие одноименные функции во фрагменте.

Инициализируйте фрагмент в активности хоста

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

Котлин

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)
    }
}

Ява

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() сразу после установки представления содержимого:

Котлин

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()
    }
}

Ява

@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 получает свои аргументы непосредственно из дополнительных намерений действия:

Котлин

...

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()
}

...

Ява

...

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 . Чтобы добавить график, щелкните правой кнопкой мыши этот каталог и выберите «Создать» > «Файл ресурса навигации» .

Компонент навигации использует активность в качестве хоста для навигации и заменяет отдельные фрагменты на этот хост, когда пользователи перемещаются по вашему приложению. Прежде чем вы сможете приступить к визуальному оформлению навигации вашего приложения, вам необходимо настроить NavHost внутри действия, в котором будет размещаться этот график. Поскольку мы используем фрагменты, мы можем использовать реализацию NavHost компонента навигации по умолчанию, 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 указывает на граф навигации, связанный с этим узлом навигации. Установка этого свойства расширяет навигационный граф и устанавливает свойство графика в 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) .

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

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

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

Удаление фрагментных транзакций

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

Если ваше приложение использует несколько фрагментов в рамках одного и того же действия или навигации верхнего уровня, например макет ящика или нижнюю навигацию, то вы, вероятно, используете FragmentManager и FragmentTransactions для добавления или замены фрагментов в разделе основного контента вашего пользовательского интерфейса. Теперь это можно заменить и упростить с помощью компонента навигации, предоставив действия для связывания пунктов назначения на вашем графике, а затем навигацию с помощью NavController .

Вот несколько сценариев, с которыми вы можете столкнуться, а также подходы к миграции для каждого сценария.

Одно действие, управляющее несколькими фрагментами

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

Котлин

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()
    }
}

Ява

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();
    }
}

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

Котлин

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)
    }
}

Ява

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>

Затем вы можете обновить свою активность:

Котлин

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)
    }
}

Ява

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 для перехода к следующему экрану сведений о продукте.

Безопасная передача аргументов

Компонент навигации имеет плагин Gradle под названием Safe Args , который генерирует простые классы объектов и конструкторов для безопасного по типам доступа к аргументам, указанным для пунктов назначения и действий.

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

Внутри фрагмента используйте NavController и созданный класс Directions , чтобы предоставить типобезопасные аргументы целевому месту назначения, как показано в следующем примере:

Котлин

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)
    }
}

Ява

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 , в вашей деятельности может быть много логики конфигурации, которая управляет открытием и закрытием ящика и переходом к другим местам назначения.

Результат вашей деятельности может выглядеть примерно так:

Котлин

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)
}

Ява

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);
    }
}

После того как вы добавили компонент «Навигация» в свой проект и создали график навигации, добавьте каждое из мест назначения контента из вашего графика (например, «Главная» , «Галерея» , «Слайд-шоу» и «Инструменты» из приведенного выше примера). Убедитесь, что значения 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 .

Пример см. в следующем фрагменте:

Котлин

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)
    }
}

Ява

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 .

Добавить направления активности

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

Сначала определите места в вашем приложении, где у вас есть два отдельных графа навигации и вы используете startActivity для перехода между ними.

Этот пример содержит два графа (A и B) и вызов startActivity() для перехода от A к B.

Котлин

fun navigateToProductDetails(productId: String) {
    val intent = Intent(this, ProductDetailsActivity::class.java)
    intent.putExtra(KEY_PRODUCT_ID, productId)
    startActivity(intent)
}

Ява

private void navigateToProductDetails(String productId) {
    Intent intent = new Intent(this, ProductDetailsActivity.class);
    intent.putExtra(KEY_PRODUCT_ID, productId);
    startActivity(intent);

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

В следующем примере диаграмма A определяет пункт назначения действия, который принимает аргумент product_id вместе с действием. График Б не содержит изменений.

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, используя те же механизмы, которые вы используете для перехода к местам назначения фрагментов:

Котлин

fun navigateToProductDetails(productId: String) {
    val directions = ProductListDirections.navigateToProductDetail(productId)
    findNavController().navigate(directions)
}

Ява

private void navigateToProductDetails(String productId) {
    ProductListDirections.NavigateToProductDetail directions =
            ProductListDirections.navigateToProductDetail(productId);
    Navigation.findNavController(getView()).navigate(directions);

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

Если целевое действие получает дополнительные функции, как в предыдущем примере, вы можете передать их начальному месту назначения непосредственно в качестве аргументов, но вам нужно вручную установить граф навигации вашего хоста внутри метода onCreate() хост-активности, чтобы вы могли передать намерение. дополнительные функции в качестве аргументов фрагмента, как показано ниже:

Котлин

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)
    }

}

Ява

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, как показано в следующем примере:

Котлин

class ProductDetailsFragment : Fragment() {

    val args by navArgs<ProductDetailsArgs>()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val productId = args.productId
        ...
    }
    ...

Ява

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 , содержащий один фрагмент. В большинстве этих случаев вы можете просто объединить все элементы из каждого графа навигации и обновить любые элементы назначения действия, чтобы фрагментировать пункты назначения.

Следующий пример объединяет графики 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 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 а не к месту назначения действия:

Котлин

fun navigateToProductDetails(productId: String) {
    val directions = ProductListDirections.navigateToProductDetail(productId)
    findNavController().navigate(directions)
}

Ява

private void navigateToProductDetails(String productId) {
    ProductListDirections.NavigateToProductDetail directions =
            ProductListDirections.navigateToProductDetail(productId);
    Navigation.findNavController(getView()).navigate(directions);

Дополнительные ресурсы

Дополнительную информацию о навигации см. в следующих разделах: