Điều hướng đến một đích đến

Sử dụng bộ sưu tập để sắp xếp ngăn nắp các trang Lưu và phân loại nội dung dựa trên lựa chọn ưu tiên của bạn.

Việc chuyển đến một đích đến được thực hiện thông qua NavController, một đối tượng quản lý hoạt động điều hướng ứng dụng trong một NavHost. Mỗi NavHost sẽ có NavController tương ứng riêng. NavController cung cấp vài cách khác nhau để điều hướng đến đích đến. Bạn có thể xem nội dung mô tả chi tiết ở phần bên dưới.

Để truy xuất NavController cho một mảnh, hoạt động hoặc chế độ xem, sử dụng một trong các phương thức sau:

Kotlin:

Java:

Sau khi truy xuất NavController, bạn có thể gọi một trong các phương thức nạp chồng của navigate() để điều hướng giữa các đích đến. Mỗi phương thức nạp chồng sẽ hỗ trợ cho nhiều tình huống di chuyển như mô tả trong các phần sau.

Dùng Safe Args để di chuyển dữ liệu đảm bảo an toàn về loại

Bạn nên di chuyển giữa các đích đến bằng trình bổ trợ Safe Args Gradle. Trình bổ trợ này tạo các lớp đơn giản cho đối tượng và trình tạo để điều hướng theo hình thức an toàn về loại giữa các đích đến. Bạn nên sử dụng Safe Args để điều hướng cũng như truyền dữ liệu giữa các đích đến.

To add Safe Args to your project, include the following classpath in your top level build.gradle file:

Groovy

buildscript {
    repositories {
        google()
    }
    dependencies {
        def nav_version = "2.5.3"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}

Kotlin

buildscript {
    repositories {
        google()
    }
    dependencies {
        val nav_version = "2.5.3"
        classpath("androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version")
    }
}

You must also apply one of two available plugins.

To generate Java language code suitable for Java or mixed Java and Kotlin modules, add this line to your app or module's build.gradle file:

Groovy

plugins {
  id 'androidx.navigation.safeargs'
}

Kotlin

plugins {
    id("androidx.navigation.safeargs")
}

Alternatively, to generate Kotlin code suitable for Kotlin-only modules add:

Groovy

plugins {
  id 'androidx.navigation.safeargs.kotlin'
}

Kotlin

plugins {
    id("androidx.navigation.safeargs.kotlin")
}

You must have android.useAndroidX=true in your gradle.properties file as per Migrating to AndroidX.

Sau khi bật trình bổ trợ Safe Args, mã do bạn tạo sẽ chứa các lớp và phương thức cho từng thao tác mà bạn xác định, cũng như các lớp tương ứng với từng đích đến của hoạt động gửi và nhận.

Safe Args tạo một lớp cho mỗi đích ban đầu của một thao tác. Tên lớp được tạo sẽ thêm "Hướng" vào tên lớp đích ban đầu. Ví dụ: nếu đích ban đầu có tên là SpecifyAmountFragment, thì lớp được tạo sẽ có tên là SpecifyAmountFragmentDirections.

Lớp được tạo chứa một phương thức tĩnh cho mỗi tác vụ đã quy định trong đích đến ban đầu. Phương thức này lấy các thông số tác vụ đã quy định làm đối số và trả về đối tượng NavDirections mà bạn có thể chuyển trực tiếp đến navigate().

Ví dụ về Safe Args

Ví dụ: giả sử chúng ta có biểu đồ điều hướng với một tác vụ kết nối hai đích đến là SpecifyAmountFragmentConfirmationFragment. ConfirmationFragment lấy một thông số float duy nhất mà bạn cung cấp trong tác vụ.

Safe Args tạo một lớp SpecifyAmountFragmentDirections bằng một phương thức duy nhất là actionSpecifyAmountFragmentToConfirmationFragment() và một lớp bên trong có tên là ActionSpecifyAmountFragmentToConfirmationFragment. Lớp bên trong được lấy từ NavDirections và lưu trữ mã thao tác cũng như tham số float được liên kết. Sau đó, đối tượng NavDirections được trả về có thể được chuyển trực tiếp đến navigate(), như trong ví dụ sau:

Kotlin

override fun onClick(v: View) {
    val amount: Float = ...
    val action =
        SpecifyAmountFragmentDirections
            .actionSpecifyAmountFragmentToConfirmationFragment(amount)
    v.findNavController().navigate(action)
}

Java

@Override
public void onClick(View view) {
    float amount = ...;
    action =
        SpecifyAmountFragmentDirections
            .actionSpecifyAmountFragmentToConfirmationFragment(amount);
    Navigation.findNavController(view).navigate(action);
}

Để biết thêm thông tin về việc truyền dữ liệu giữa các đích đến bằng Safe Args, hãy xem phần Sử dụng Safe Args để truyền dữ liệu với an toàn kiểu.

Sử dụng mã nhận dạng để điều hướng

navigate(int) lấy mã tài nguyên của một tác vụ hoặc một đích đến. Đoạn mã sau đây cho biết cách chuyển đến ViewTransactionsFragment:

Kotlin

viewTransactionsButton.setOnClickListener { view ->
   view.findNavController().navigate(R.id.viewTransactionsAction)
}

Java

viewTransactionsButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Navigation.findNavController(view).navigate(R.id.viewTransactionsAction);
    }
});

Có ba biến thể Navigation.createNavigateOnClickListener() của các nút. Những biến thể này khá hữu ích nếu bạn sử dụng ngôn ngữ lập trình Java. Nếu bạn sử dụng Kotlin, OnClickListener là một giao diện SAM, vì vậy bạn có thể sử dụng hàm lambda tạo vệt. Hàm này có thể ngắn và dễ đọc hơn so với việc gọi trực tiếpcreateNavigateOnClickListener().

Để xử lý các thành phần giao diện người dùng phổ biến khác, chẳng hạn như thanh ứng dụng trên cùng và thanh điều hướng dưới cùng, hãy xem Cập nhật thành phần giao diện người dùng bằng NavigationUI.

Khi xác định một thao tác trong biểu đồ điều hướng, tính năng Điều hướng sẽ tạo một lớp NavAction tương ứng, trong đó có chứa cấu hình được xác định cho thao tác đó, bao gồm những thông tin sau đây:

  • Đích đến: Mã tài nguyên của đích đến.
  • Đối số mặc định: android.os.Bundle chứa giá trị mặc định cho đích đến nếu được cung cấp.
  • Tuỳ chọn điều hướng: Tuỳ chọn điều hướng được biểu thị dưới dạng NavOptions. Lớp này chứa toàn bộ cấu hình đặc biệt để chuyển đổi qua lại từ đích đến mục tiêu, bao gồm cả cấu hình tài nguyên của ảnh động, hành vi bật lên lẫn việc bạn có nên khởi chạy đích đến ở chế độ trên cùng duy nhất hay không.

Hãy cùng tìm hiểu biểu đồ ví dụ gồm hai màn hình với tác vụ để điều hướng từ màn hình này sang màn hình khác:

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

    <fragment android:id="@+id/a"
              android:name="com.example.myapplication.FragmentA"
              android:label="a"
              tools:layout="@layout/a">
        <action android:id="@+id/action_a_to_b"
                app:destination="@id/b"
                app:enterAnim="@anim/nav_default_enter_anim"
                app:exitAnim="@anim/nav_default_exit_anim"
                app:popEnterAnim="@anim/nav_default_pop_enter_anim"
                app:popExitAnim="@anim/nav_default_pop_exit_anim"/>
    </fragment>

    <fragment android:id="@+id/b"
              android:name="com.example.myapplication.FragmentB"
              android:label="b"
              tools:layout="@layout/b">
        <action android:id="@+id/action_b_to_a"
                app:destination="@id/a"
                app:enterAnim="@anim/nav_default_enter_anim"
                app:exitAnim="@anim/nav_default_exit_anim"
                app:popEnterAnim="@anim/nav_default_pop_enter_anim"
                app:popExitAnim="@anim/nav_default_pop_exit_anim"
                app:popUpTo="@+id/a"
                app:popUpToInclusive="true"/>
    </fragment>
</navigation>

Khi đồ thị điều hướng tăng cường, các thao tác này sẽ được phân tích cú pháp và đối tượng NavAction tương ứng sẽ được tạo bằng cấu hình đã xác định trong biểu đồ. Ví dụ: action_b_to_a được xác định sẽ đi theo đường dẫn từ đích b đến a. Tác vụ này bao gồm các ảnh động với hành vi popTo giúp xóa toàn bộ đích đến khỏi ngăn xếp ngược. Tất cả chế độ cài đặt này đều được ghi lại dưới dạng NavOptions và gắn liền với NavAction.

Để theo dõi NavAction này, hãy sử dụng NavController.navigate(), truyền mã thao tác như minh hoạ trong ví dụ sau đây:

Kotlin

findNavController().navigate(R.id.action_b_to_a)

Java

NavigationHostFragment.findNavController(this).navigate(R.id.action_b_to_a);

Áp dụng NavOptions theo phương thức lập trình

Các ví dụ trước cho thấy cách chỉ định NavOptions trong XML của biểu đồ điều hướng. Tuy nhiên, các tuỳ chọn cụ thể có thể khác nhau tuỳ thuộc vào những hạn chế chưa xác định tại thời gian xây dựng. Trong trường hợp đó, bạn phải tạo và đặt NavOptions theo lập trình, như minh hoạ trong ví dụ sau đây:

Kotlin

findNavController().navigate(
    R.id.action_fragmentOne_to_fragmentTwo,
    null,
    navOptions { // Use the Kotlin DSL for building NavOptions
        anim {
            enter = android.R.animator.fade_in
            exit = android.R.animator.fade_out
        }
    }
)

Java

NavController navController = NavHostFragment.findNavController(this);
navController.navigate(
        R.id.action_fragmentOne_to_fragmentTwo,
        null,
        new NavOptions.Builder()
                .setEnterAnim(android.R.animator.fade_in)
                .setExitAnim(android.R.animator.fade_out)
                .build()
);

Ví dụ này sử dụng dạng navigate() mở rộng, đồng thời chứa thêm các đối số BundleNavOptions. Tất cả các biến thể của navigate() đều có phiên bản mở rộng chấp nhận đối số NavOptions.

Ngoài ra còn có thể áp dụng NavOptions theo phương thức lập trình khi điều hướng đến đường liên kết sâu:

Kotlin

findNavController().navigate(
    deepLinkUri,
    navOptions { // Use the Kotlin DSL for building NavOptions
        anim {
            enter = android.R.animator.fade_in
            exit = android.R.animator.fade_out
        }
    }
)

Java

NavController navController = NavHostFragment.findNavController(this);
navController.navigate(
        deepLinkUri,
        new NavOptions.Builder()
                .setEnterAnim(android.R.animator.fade_in)
                .setExitAnim(android.R.animator.fade_out)
                .build()
);

Biến thể này của navigate() sẽ lấy Uri cho đường liên kết sâu, cũng như thực thể NavOptions.

Điều hướng bằng DeepLinkRequest

Bạn có thể sử dụng navigate(NavDeepLinkRequest) để chuyển thẳng tới đích đến chứa đường liên kết sâu ngầm ẩn, như minh hoạ trong ví dụ sau đây:

Kotlin

val request = NavDeepLinkRequest.Builder
    .fromUri("android-app://androidx.navigation.app/profile".toUri())
    .build()
findNavController().navigate(request)

Java

NavDeepLinkRequest request = NavDeepLinkRequest.Builder
    .fromUri(Uri.parse("android-app://androidx.navigation.app/profile"))
    .build()
NavHostFragment.findNavController(this).navigate(request)

Ngoài Uri,NavDeepLinkRequest cũng hỗ trợ liên kết sâu với tác vụ và loại MIME. Để thêm tác vụ vào yêu cầu, hãy sử dụng fromAction() hoặc setAction(). Để thêm loại MIME vào yêu cầu, hãy sử dụng fromMimeType() hoặc setMimeType().

Để NavDeepLinkRequest khớp với đích đến ngầm của đường liên kết sâu thì URI, tác vụ và MIME phải khớp với NavDeepLink trong trang đích. URI phải khớp mẫu, các tác vụ phải khớp chính xác và loại MIME phải kết nối với nhau (ví dụ: "image/jpg" khớp với "image/*").

Không giống như cách điều hướng bằng mã thao tác hoặc mã đích đến, bạn có thể chuyển đến bất kỳ đường liên kết sâu nào trong biểu đồ, cho dù đích đến đó có hiển thị hay không. Bạn có thể điều hướng đến một đích đến trên biểu đồ hiện tại hoặc trên biểu đồ hoàn toàn khác.

Khi điều hướng bằng NavDeepLinkRequest, ngăn xếp lui sẽ không được đặt lại. Hành vi này không giống như điều hướng liên kết sâu khác, trong đó ngăn xếp lui được thay thế khi điều hướng. popUpTopopUpToInclusive vẫn xoá đích đến khỏi ngăn xếp lui giống như khi điều hướng bằng mã nhận dạng.

Thao tác và ngăn xếp lui

Android duy trì một ngăn xếp lui chứa các đích đến mà bạn đã truy cập. Đích đến đầu tiên được đặt trên ngăn xếp khi người dùng mở ứng dụng. Mỗi lệnh gọi đến phương thức navigate() sẽ đặt một đích đến khác lên trên ngăn xếp. Thao tác nhấn vào Up (Mũi tên lên) hoặc Back (Quay lại) sẽ gọi NavController.navigateUp()NavController.popBackStack(), lần lượt xoá phương thức (hoặc pop (bật lên)) đích đến trên đầu khỏi ngăn xếp.

NavController.popBackStack() trả về một boolean cho biết thuộc tính này có quay lại thành công một điểm khác hay không. Trường hợp phổ biến nhất là khi phương thức này trả về false cũng là lúc bạn thao tác để đích đến bắt đầu của biểu đồ bật lên.

Khi phương thức này trả về false, NavController.getCurrentDestination() sẽ trả về null. Bạn chịu trách nhiệm việc điều hướng đến đích đến mới hoặc xử lý cửa sổ bật lên bằng cách gọi finish() trên Tác vụ của bạn, như minh họa trong ví dụ sau:

Kotlin

...

if (!navController.popBackStack()) {
    // Call finish() on your Activity
    finish()
}

Java

...

if (!navController.popBackStack()) {
    // Call finish() on your Activity
    finish();
}

đích đến của hộp thoại triển khai giao diện FloatingWindow, cho biết lớp phủ này che các đích đến khác trong ngăn xếp lui. Do đó, một hoặc nhiều đích FloatingWindow chỉ có thể hiển thị ở phía trên cùng của ngăn xếp lui điều hướng. Khi điều hướng đến một đích đến không triển khai FloatingWindow, hệ thống sẽ tự động bật tất cả các đích đến FloatingWindow ở phía trên cùng của ngăn xếp. Nhờ vậy, đích đến hiện tại luôn hiển thị đầy đủ phía trên các đích đến khác trong ngăn xếp lui.

Ví dụ: nếu ngăn xếp lui chỉ bao gồm các đích đến không nổi và người dùng di chuyển tới một đích đến Dialog, thì ngăn xếp lui sẽ có dạng tương tự như hình 1:

ngăn xếp lui có đích đến của hộp thoại ở trên cùng
Hình 1. ngăn xếp lui có đích đến là Dialog ở trên cùng.

Sau đó, nếu người dùng di chuyển đến một đích khác của Dialog, thì đích này sẽ được thêm vào đầu ngăn xếp lui, như trong hình 2:

ngăn xếp lui có đích đến của hai hộp thoại ở trên cùng
Hình 2. Ngăn xếp lui có hai đích đến Dialog ở trên.

Nếu sau đó người dùng di chuyển tới một đích đến không nổi, thì đích đến FloatingWindow sẽ bật lên từ đầu ngăn xếp lui trước rồi mới chuyển tới đích đến mới, như minh hoạ trong hình 3:

đích đến của hộp thoại sẽ bật ra, và đích đến mới được thêm vào
Hình 3. Đích đến Dialog bật ra và đích đến mới sẽ được thêm vào.

popUpTo và popUpToInclusive

Khi điều hướng bằng thao tác, bạn có thể tuỳ ý bật các đích đến khác lên khỏi ngăn xếp lui. Ví dụ: nếu ứng dụng có luồng đăng nhập ban đầu, thì khi người dùng đã đăng nhập, bạn nên bật mọi đích đến liên quan tới hoạt động đăng nhập lên khỏi ngăn xếp lui để nút Back (Quay lại) không đưa người dùng trở về luồng đăng nhập đó.

Để bật lên các đích đến khi di chuyển từ đích đến này tới đích đến khác, vui lòng thêm thuộc tính app:popUpTo vào phần tử <action> đã liên kết. app:popUpTo yêu cầu thư viện Navigation (Điều hướng) bật lên một số đích đến khỏi ngăn xếp lui như một phần của lệnh gọi tới navigate(). Giá trị thuộc tính là mã nhận dạng của đích đến gần đây nhất sẽ nằm trên ngăn xếp.

Bạn cũng có thể thêm app:popUpToInclusive="true" để biểu thị rằng đích đến đã chỉ định trong app:popUpTo cũng nên được xoá khỏi ngăn xếp lui.

Ví dụ về popUpTo: logic hình tròn

Giả sử ứng dụng có 3 đích đến là A, B và C, cùng với các tác vụ dẫn từ A đến B, B đến C và C quay lại A. Biểu đồ điều hướng tương ứng được hiển thị trong hình 4:

Hình 4. Một biểu đồ điều hướng hình tròn có 3 đích đến là A, B và C.

Với mỗi tác vụ điều hướng, một đích đến sẽ được thêm vào ngăn xếp lui. Nếu bạn phải di chuyển liên tục qua luồng này, thì ngăn xếp lui sẽ chứa nhiều tập hợp các đích đến (A, B, C, A, B, C, A, v.v.). Để tránh việc lặp lại này, bạn có thể chỉ định app:popUpToapp:popUpToInclusive trong thao tác đưa từ đích đến C tới đích đến A, như minh hoạ trong ví dụ sau đây:

<fragment
    android:id="@+id/c"
    android:name="com.example.myapplication.C"
    android:label="fragment_c"
    tools:layout="@layout/fragment_c">

    <action
        android:id="@+id/action_c_to_a"
        app:destination="@id/a"
        app:popUpTo="@+id/a"
        app:popUpToInclusive="true"/>
</fragment>

Sau khi đến đích C, ngăn xếp lui chứa một phiên bản của từng đích đến (A, B, C). Khi quay lại đích đến A, chúng ta cũng vẫn popUpTo A, nghĩa là xoá B và C khỏi ngăn xếp trong quá trình điều hướng. Chúng ta cũng loại bỏ ô A đầu tiên khỏi ngăn xếp vớiapp:popUpToInclusive="true" một cách hiệu quả. Lưu ý rằng nếu không sử dụngapp:popUpToInclusive, ngăn xếp lui của bạn sẽ chứa hai phiên bản của đích đến A.

popUpToSaveState và restoreSaveState

Khi sử dụng app:popUpTo để di chuyển tới một đích đến, thành phần Navigation 2.4.0-alpha01 trở lên cho phép bạn tuỳ ý lưu trạng thái của mọi đích đến bật lên khỏi ngăn xếp lui. Để bật tuỳ chọn này, hãy thêm app:popUpToSaveState="true" vào phần tử <action> đã liên kết:

<action
  android:id="@+id/action_c_to_a"
  app:destination="@id/a"
  app:popUpTo="@+id/a"
  app:popUpToInclusive="true"
  app:popUpToSaveState="true"/>

Khi điều hướng đến một đích đến, bạn cũng có thể thêm app:restoreSaveState="true" để tự động khôi phục trạng thái liên kết với đích đến trong app:destination.