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

Việc chuyển đến một đích đến được thực hiện thông qua NavController, là một đối tượng quản lý điều hướng trong ứ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 đối tượng và trình tạo đơn giản cho phép điều hướng theo loại an toàn 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.

Để thêm Safe Args vào dự án, hãy đưa classpath sau vào tệp build.gradle cấp cao nhất của bạn:

Groovy

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

Kotlin

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

Bạn cũng phải áp dụng 1 trong 2 trình bổ trợ có sẵn.

Để tạo mã ngôn ngữ Java phù hợp với mô-đun Java và Kotlin, hãy thêm dòng này vào tệp build.gradle của ứng dụng hoặc mô-đun:

Groovy

plugins {
  id 'androidx.navigation.safeargs'
}

Kotlin

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

Ngoài ra, để tạo mã Kotlin phù hợp với các mô-đun chỉ Kotlin, hãy thêm:

Groovy

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

Kotlin

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

Bạn phải có android.useAndroidX=true trong tệp gradle.properties theo hướng dẫn Di chuyển sang 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 đã xác định cũng như các lớp tương ứng với từng đích 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ã tác vụ cũng như thông 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 tác vụ 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 tác vụ đó, bao gồm những thông tin sau:

  • Đí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.
  • Tùy chọn điều hướng: Tùy 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 ra lẫn việc bạn có nên khởi chạy đích đến ở chế độ đơn hàng đầu 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 tác vụ này sẽ được phân tích cú pháp và tạo đối tượng NavAction tương ứng 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. Các tùy chọn cài đặt này đượ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(), chuyển mã của tác vụ như trong ví dụ sau:

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 tùy chọn cụ thể có thể thay đổi tùy thuộc vào những hạn chế chưa xác định tại thời điểm tạo. Trong trường hợp đó, phải tạo NavOptions và đặt theo lập trình, như hiển thị trong ví dụ sau:

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

Có thể sử dụng navigate(NavDeepLinkRequest) để trực tiếp chuyển đến đích chứa đường liên kết sâu ngầm ẩn như trong ví dụ sau:

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 sử dụng mã tác vụ hoặc mã đích đến để điều hướng, bạn có thể chuyển đến bất kỳ đường liên kết sâu nào trong biểu đồ, bất kể đí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 xóa đí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 Lên hoặc Quay lại sẽ gọi NavController.navigateUp()NavController.popBackStack(), lần lượt xóa phương thức (hoặc pop) đích đến hàng đầ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 làm bật điểm bắt đầu của biểu đồ.

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. Điều này để đảm bảo đích đến hiện tại luôn hiển thị đủ 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 đến một đích Dialog, thì ngăn xếp lui có thể sẽ giống với 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 đến một đích không nổi, thì trước tiên đích FloatingWindow sẽ bật ra từ đầu ngăn xếp lui trước khi chuyển đến đích mới, như minh họa 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 thao tác bằng một tác vụ, bạn có thể tùy ý mở bật thêm các đích đến khác từ ngăn xếp lui. Ví dụ: nếu ứng dụng có quy trình đăng nhập ban đầu, thì khi người dùng đã đăng nhập, bạn nên hiển thị tất cả các trang đích liên quan đến hoạt động đăng nhập của ngăn xếp lui để nút Quay lại không đưa người dùng trở về luồng đăng nhập.

Để chèn đích đến khi điều hướng từ đích này đến đích khác, vui lòng thêm thuộc tínhapp:popUpTo vào phần tử <action> đã liên kết. app:popUpTo yêu cầu Thư viện điều hướng bật ra một số đích đến của ngăn xếp lui như một phần của lệnh gọ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ị đích đến chỉ định trong app:popUpTo cũng nên được xóa khỏi ngăn xếp lui.

Ví dụ về cửa sổ bật lên: 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ó ba đí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 phải di chuyển liên tục thông 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 tác vụ đưa từ đích đến C đến điểm A, như trong ví dụ sau:

<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 trang đích A, chúng ta cũng vẫn popUpTo A, nghĩa là khi đang điều hướng chúng ta xóa B và C khỏi ngăn xếp. 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 để điều hướng đến một đích đến, bản Điều hướng 2.4.0-alpha01 trở lên cho phép bạn tùy ý lưu trạng thái của tất cả các đích đến bật ra khỏi ngăn xếp lui. Để bật tùy 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.