Thành phần điều hướng (Navigation component) là một thư viện có thể quản lý chức năng điều hướng phức tạp, ảnh động chuyển đổi, liên kết sâu cũng như việc truyền đối số đã kiểm tra khi biên dịch qua các màn hình trong ứng dụng.
Tài liệu này đóng vai trò là hướng dẫn chung cho việc chuyển sang sử dụng Thành phần điều hướng cho một ứng dụng hiện có.
Nhìn chung, quá trình di chuyển bao gồm các bước sau:
Di chuyển logic giao diện người dùng theo màn hình cụ thể ra khỏi hoạt động – Di chuyển logic giao diện người dùng của ứng dụng ra khỏi hoạt động (activity), đảm bảo rằng mỗi hoạt động chỉ sở hữu logic của các thành phần giao diện người dùng điều hướng chung, chẳng hạn như
Toolbar
, trong khi uỷ quyền hoạt động triển khai từng màn hình cho một mảnh hoặc đích đến tuỳ chỉnh.Tích hợp Thành phần điều hướng – Đối với mỗi hoạt động, hãy xây dựng một biểu đồ điều hướng chứa ít nhất một mảnh mà hoạt động đó quản lý. Thay thế giao tác mảnh bằng thao tác Thành phần điều hướng.
Thêm đích đến cho hoạt động (activity destination) – Thay thế lệnh gọi
startActivity()
bằng thao tác sử dụng đích đến cho hoạt động.Kết hợp các hoạt động – Kết hợp các biểu đồ điều hướng trong trường hợp nhiều hoạt động có chung một bố cục.
Điều kiện tiên quyết
Hướng dẫn này giả định rằng bạn đã chuyển sang sử dụng các thư viện AndroidX cho ứng dụng. Nếu bạn chưa làm như vậy, hãy chuyển dự án sang sử dụng AndroidX trước khi tiếp tục.
Di chuyển logic giao diện người dùng theo màn hình cụ thể ra khỏi hoạt động
Hoạt động (activity) là các thành phần cấp hệ thống hỗ trợ tương tác đồ hoạ giữa ứng dụng và Android. Hoạt động được đăng ký trong tệp kê khai của ứng dụng để Android biết được nên khởi chạy những hoạt động nào. Lớp hoạt động (activity class) cho phép ứng dụng phản ứng với các thay đổi trong Android, chẳng hạn như khi giao diện người dùng của ứng dụng vào hoặc ra khỏi chế độ nền trước, xoay, v.v. Hoạt động cũng có thể là một nơi để chia sẻ trạng thái (state) giữa các màn hình.
Đối với ứng dụng của bạn, các hoạt động nên đóng vai trò là máy chủ lưu trữ để điều hướng, đồng thời nên có logic và kiến thức về cách chuyển đổi giữa các màn hình, truyền dữ liệu, v.v. Tuy nhiên, việc quản lý thông tin chi tiết về giao diện người dùng sẽ phù hợp hơn với một phần nhỏ hơn và dễ sử dụng lại trên giao diện người dùng. Cách triển khai đề xuất cho mẫu này là mảnh (fragment). Hãy xem bài viết Hoạt động đơn: Lý do, thời điểm và cách thức để tìm hiểu thêm về các ưu điểm khi sử dụng mảnh. Thành phần điều hướng hỗ trợ mảnh qua phần phụ thuộc navigation-fragment. Thành phần điều hướng cũng hỗ trợ các loại đích tuỳ chỉnh.
Nếu ứng dụng của bạn không sử dụng mảnh, điều đầu tiên bạn cần làm là di chuyển từng màn hình trong ứng dụng để dùng mảnh. Bạn không xoá hoạt động tại thời điểm này. Thay vào đó, bạn sẽ tạo mảnh để đại diện cho màn hình và chia nhỏ logic giao diện người dùng theo trách nhiệm.
Đưa mảnh vào ứng dụng
Để minh hoạ quá trình đưa mảnh vào ứng dụng, hãy bắt đầu bằng ví dụ về một ứng dụng bao gồm hai màn hình: màn hình danh sách sản phẩm và thông tin chi tiết về sản phẩm. Khi nhấp vào một sản phẩm trong màn hình danh sách, người dùng sẽ được chuyển đến màn hình thông tin chi tiết để tìm hiểu thêm về sản phẩm đó.
Trong ví dụ này, các màn hình danh sách và thông tin chi tiết hiện là các hoạt động riêng biệt.
Tạo bố cục mới để lưu trữ giao diện người dùng
Để đưa mảnh vào ứng dụng, hãy bắt đầu bằng cách tạo một tệp bố cục mới cho hoạt động để lưu trữ mảnh đó. Thao tác này sẽ thay thế bố cục thành phần hiển thị nội dung hiện tại của hoạt động.
Để có một khung hiển thị đơn giản, bạn có thể sử dụng FrameLayout
, như trong ví dụ product_list_host
sau:
<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" />
Thuộc tính id
đề cập đến phần nội dung mà sau đó chúng ta sẽ thêm mảnh.
Tiếp theo, trong hàm onCreate()
của hoạt động, hãy sửa đổi mã tham chiếu tệp bố cục trong hàm onCreate của hoạt động để trỏ đến tệp bố cục mới này:
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); ... } }
Bố cục hiện tại (product_list
trong ví dụ này) được dùng làm khung hiển thị gốc cho mảnh bạn sắp tạo.
Tạo mảnh
Hãy tạo mảnh mới để quản lý giao diện người dùng cho màn hình. Bạn nên đảm bảo nhất quán với tên máy chủ lưu trữ hoạt động. Đoạn mã sau đây sử dụng ProductListFragment
, ví dụ:
Kotlin
class ProductListFragment : Fragment() { // Leave empty for now. }
Java
public class ProductListFragment extends Fragment { // Leave empty for now. }
Di chuyển logic hoạt động sang mảnh
Khi đã có phần định nghĩa mảnh, bước tiếp theo là di chuyển logic giao diện người dùng cho màn hình từ hoạt động này sang mảnh mới này. Nếu xuất phát từ một cấu trúc dựa trên hoạt động, có thể bạn sẽ có nhiều logic tạo khung hiển thị diễn ra trong hàm onCreate()
của hoạt động.
Sau đây là ví dụ về một màn hình dựa trên hoạt động có chứa logic giao diện người dùng mà chúng ta cần di chuyển:
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 }
Cũng có thể hoạt động của bạn kiểm soát thời điểm và cách người dùng chuyển đến màn hình tiếp theo, như trong ví dụ sau:
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); }
Bên trong mảnh, bạn phân phối công việc này giữa onCreateView()
và onViewCreated()
, chỉ với logic điều hướng vẫn còn trong hoạt động:
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); } } }; ... }
Trong ProductListFragment
, hãy lưu ý rằng không có lệnh gọi tới setContentView()
để tăng cường và kết nối bố cục này. Trong mảnh, onCreateView()
sẽ khởi chạy thành phần hiển thị gốc. onCreateView()
lấy một thực thể của LayoutInflater
có thể dùng để tăng cường khung hiển thị gốc dựa trên tệp tài nguyên bố cục. Ví dụ này sử dụng lại bố cục product_list
hiện có của hoạt động vì không cần thay đổi bố cục cho chính bố cục đó.
Nếu có logic giao diện người dùng bất kỳ nằm trong các chức năng onStart()
, onResume()
, onPause()
hoặc onStop()
của hoạt động không liên quan đến thành phần điều hướng, thì bạn có thể chuyển các chức năng đó sang các chức năng tương ứng cùng tên trên mảnh.
Khởi động mảnh trong hoạt động của máy chủ lưu trữ
Khi bạn đã di chuyển tất cả logic giao diện người dùng xuống mảnh, trong hoạt động sẽ chỉ còn lại logic điều hướng.
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); } }
Bước cuối cùng là tạo thực thể của mảnh trong onCreate()
, ngay sau khi đặt khung hiển thị nội dung:
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(); } }
Như trong ví dụ này, FragmentManager
tự động lưu và khôi phục các mảnh dựa trên các thay đổi về cấu hình, vì vậy, bạn chỉ cần thêm mảnh nếu giá trị của savedInstanceState
là rỗng (null).
Truyền mã bổ sung ý định vào mảnh
Nếu hoạt động của bạn nhận được Extras
qua một ý định (intent), thì bạn có thể trực tiếp truyền mã này sang mảnh dưới dạng đối số.
Trong ví dụ này, ProductDetailsFragment
trực tiếp nhận được đối số qua các mã bổ sung ý định (intent extra) của hoạt động:
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(); } ...
Đến đây, bạn có thể kiểm thử quá trình chạy ứng dụng, trong đó màn hình đầu tiên được cập nhật để sử dụng mảnh. Tiếp tục di chuyển các màn hình dựa trên hoạt động còn lại, dành thời gian kiểm thử sau mỗi lần lặp lại.
Tích hợp Thành phần điều hướng
Khi sử dụng cấu trúc dựa trên mảnh, bạn có thể bắt đầu tích hợp Thành phần điều hướng.
Trước tiên, hãy thêm các phần phụ thuộc điều hướng gần đây nhất vào dự án, theo hướng dẫn trong Ghi chú phát hành thư viện điều hướng.
Tạo biểu đồ điều hướng
Thành phần điều hướng thể hiện cấu hình điều hướng của ứng dụng trong tệp tài nguyên dưới dạng biểu đồ, giống như khung hiển thị của ứng dụng. Việc này giúp sắp xếp hợp lý hoạt động điều hướng của ứng dụng bên ngoài bộ mã cơ sở, đồng thời giúp bạn chỉnh sửa hoạt động điều hướng trong ứng dụng một cách trực quan.
Để tạo biểu đồ điều hướng, hãy bắt đầu bằng cách tạo một thư mục tài nguyên mới có tên navigation
. Để thêm biểu đồ, hãy nhấp chuột phải vào thư mục này rồi chọn New > Navigation resource file (Mới > Tệp tài nguyên điều hướng).
Thành phần điều hướng sử dụng một hoạt động làm máy chủ lưu trữ để điều hướng và hoán đổi từng mảnh riêng lẻ vào máy chủ đó khi người dùng di chuyển trong ứng dụng. Trước khi có thể bắt đầu tạo bố cục bằng hình ảnh cho thành phần điều hướng của ứng dụng, bạn cần định cấu hình NavHost
bên trong hoạt động sẽ lưu trữ biểu đồ này. Vì đang sử dụng mảnh, nên chúng ta có thể dùng cách triển khai NavHost
mặc định của Thành phần điều hướng là NavHostFragment
.
NavHostFragment
được định cấu hình qua FragmentContainerView
nằm trong một hoạt động lưu trữ, như trong ví dụ sau:
<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" />
Thuộc tính app:NavGraph
trỏ đến biểu đồ điều hướng được liên kết với máy chủ điều hướng này. Thao tác đặt thuộc tính này sẽ tăng cường biểu đồ điều hướng và đặt thuộc tính biểu đồ trên NavHostFragment
. Thuộc tính app:defaultNavHost
đảm bảo rằng NavHostFragment
sẽ chặn nút Quay lại của hệ thống.
Nếu bạn đang sử dụng thành phần điều hướng cấp cao, chẳng hạn như DrawerLayout
hoặc BottomNavigationView
, thì FragmentContainerView
sẽ thay thế phần tử khung hiển thị nội dung chính của bạn. Hãy xem ví dụ trong nội dung Sử dụng NavigationUI để cập nhật thành phần giao diện người dùng.
Đối với một bố cục đơn giản, bạn có thể đưa phần tử FragmentContainerView
này vào làm phần tử con của thư mục gốc 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>
Nếu nhấp vào thẻ Design (Thiết kế) ở dưới cùng, thì bạn sẽ thấy một biểu đồ tương tự như biểu đồ dưới đây. Ở phía trên bên trái của biểu đồ, trong phần Destinations (Đích đến), bạn có thể thấy một mã tham chiếu đến hoạt động NavHost
ở dạng layout_name (resource_id)
.
Nhấp vào nút dấu cộng gần phía trên cùng để thêm các mảnh của bạn vào biểu đồ này.
Thành phần điều hướng chính là các màn hình riêng lẻ dùng làm đích đến (destination). Đích đến có thể là mảnh, hoạt động hoặc đích đến tuỳ chỉnh. Bạn có thể thêm mọi loại đích đến vào biểu đồ, nhưng hãy lưu ý rằng đích đến cho hoạt động được coi là đích đến cuối, vì sau khi điều hướng đến đích đến của hoạt động, bạn sẽ hoạt động bên trong một máy chủ lưu trữ và biểu đồ riêng.
Thành phần điều hướng cũng chính là cách người dùng di chuyển từ đích đến này tới đích đến khác dưới dạng hành động (action). Hành động cũng có thể mô tả ảnh động chuyển đổi và hành vi bật ra.
Xoá giao tác mảnh
Giờ đây bạn đang sử dụng Thành phần điều hướng. Nếu đang di chuyển giữa các màn hình dựa trên mảnh trong cùng một hoạt động, thì bạn có thể xoá các lượt tương tác FragmentManager
.
Nếu ứng dụng của bạn đang sử dụng nhiều mảnh trong cùng một hoạt động hoặc thành phần điều hướng cấp cao (chẳng hạn như bố cục ngăn hoặc ngăn điều hướng ở dưới cùng), thì có thể bạn đang sử dụng FragmentManager
và FragmentTransactions
để thêm hoặc thay thế mảnh trong phần nội dung chính của giao diện người dùng. Việc này hiện có thể được thay thế và đơn giản hoá qua Thành phần điều hướng bằng cách cung cấp các hành động để liên kết các đích đến trong biểu đồ rồi điều hướng bằng NavController
.
Sau đây là một vài tình huống mà bạn có thể gặp phải cũng như cách di chuyển trong từng tình huống.
Một hoạt động quản lý nhiều mảnh
Nếu bạn có một hoạt động quản lý nhiều mảnh, thì mã hoạt động của bạn có thể có dạng như sau:
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(); } }
Bên trong đích của nguồn, bạn có thể gọi hàm điều hướng để phản hồi một sự kiện nào đó, như trình bày dưới đây:
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()) ); }
Bạn có thể thay thế bằng cách cập nhật biểu đồ điều hướng để đặt hành động và đích đến bắt đầu nhằm liên kết các đích đến cũng như xác định các đối số khi cần:
<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>
Sau đó, bạn có thể cập nhật hoạt động:
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); } }
Hoạt động không còn phải dùng phương thức navigateToProductDetail()
nữa. Trong phần tiếp theo, chúng ta cập nhật ProductListFragment
để sử dụng NavController
nhằm di chuyển đến màn hình thông tin chi tiết sản phẩm tiếp theo.
Truyền đối số một cách an toàn
Thành phần điều hướng có một trình bổ trợ Gradle tên là Safe Args. Trình bổ trợ này tạo ra các lớp xây dựng và đối tượng đơn giản để truy cập kiểu an toàn (type-safe) vào các đối số được chỉ định cho đích đến và hành động.
Sau khi áp dụng trình bổ trợ, mọi đối số được xác định tại một đích đến trong biểu đồ điều hướng sẽ đều khiến khung Thành phần điều hướng tạo ra một lớp Arguments
cung cấp đối số kiểu an toàn cho đích đến mục tiêu.
Việc xác định một hành động sẽ khiến trình bổ trợ tạo ra một lớp cấu hình Directions
có thể dùng để thông báo cho NavController
biết cách điều hướng người dùng đến đích đến mục tiêu. Khi một hành động trỏ đến một đích đến cần đến các đối số, lớp Directions
đã tạo sẽ có cả các phương thức hàm khởi tạo cần đến các tham số đó.
Bên trong mảnh, hãy sử dụng NavController
và lớp Directions
đã tạo để cung cấp các đối số kiểu an toàn cho đích đến, như trong ví dụ sau:
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); }; }
Thành phần điều hướng cấp cao
Nếu ứng dụng của bạn sử dụng DrawerLayout
, thì có thể bạn sẽ thấy nhiều logic cấu hình trong hoạt động quản lý thao tác mở và đóng ngăn cũng như điều hướng đến các đích đến khác.
Hoạt động bạn tạo ra có thể có dạng như sau:
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); } }
Sau khi bạn đã thêm Thành phần điều hướng vào dự án và tạo biểu đồ điều hướng, hãy thêm từng đích nội dung trên biểu đồ của bạn (chẳng hạn như Home (Trang chủ), Gallery (Thư viện ảnh), SlideShow (Trình chiếu) và Tools (Công cụ) trong ví dụ trên). Hãy đảm bảo rằng giá trị id
của mục trong trình đơn khớp với giá trị id
của đích đến được liên kết, như trình bày dưới đây:
<!-- 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>
Khi khớp các giá trị id
trên trình đơn và biểu đồ, bạn cũng có thể kết nối NavController
cho hoạt động này để xử lý chế độ tự động điều hướng dựa trên mục trong trình đơn. NavController
cũng xử lý thao tác mở và đóng DrawerLayout
cũng như xử lý hành vi của nút Lên (Up) và Quay lại (Back) một cách thích hợp.
Sau đó, bạn có thể cập nhật MainActivity
để kết nối NavController
với Toolbar
và NavigationView
.
Hãy xem đoạn mã minh hoạ sau:
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); } }
Bạn có thể sử dụng chính kỹ thuật này cho cả thành phần điều hướng dựa trên BottomNavigationView và thành phần điều hướng dựa trên trình đơn (menu). Hãy xem ví dụ trong nội dung Sử dụng NavigationUI để cập nhật thành phần giao diện người dùng.
Thêm đích đến cho hoạt động
Sau khi mỗi màn hình trong ứng dụng đã được kết nối để sử dụng Thành phần điều hướng và bạn không cần sử dụng FragmentTransactions
để chuyển đổi giữa các đích đến dựa trên mảnh nữa, bước tiếp theo là loại bỏ các lệnh gọi startActivity
.
Trước tiên, hãy xác định các vị trí trong ứng dụng nơi có hai biểu đồ điều hướng riêng biệt và bạn đang sử dụng startActivity
để chuyển đổi giữa các vị trí đó.
Ví dụ này cho thấy hai biểu đồ (A và B) và một lệnh gọi startActivity()
để chuyển đổi từ A sang 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);
Tiếp theo, hãy thay thế các vị trí này bằng một đích đến cho hoạt động trong Biểu đồ A. Biểu đồ này đại diện cho quá trình điều hướng đến hoạt động lưu trữ của Biểu đồ B. Nếu có các đối số để truyền đến đích đến bắt đầu của Biểu đồ B, thì bạn có thể chỉ định chúng trong phần định nghĩa đích đến cho hoạt động.
Trong ví dụ sau, Biểu đồ A xác định đích đến cho hoạt động có đối số product_id
cùng với một hành động. Biểu đồ B không chứa thay đổi nào.
XML của Biểu đồ A và Biểu đồ B có thể được thể hiện như sau:
<!-- 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ạn có thể chuyển đến hoạt động lưu trữ của Biểu đồ B bằng chính cơ chế mà bạn sử dụng để di chuyển đến các đích đến của mảnh:
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);
Truyền đối số đích đến cho hoạt động sang mảnh đích đến bắt đầu
Nếu hoạt động đích đến nhận được mã bổ sung (extra) như trong ví dụ trước, thì bạn có thể trực tiếp truyền những dữ liệu này đến đích đến bắt đầu dưới dạng đối số. Tuy nhiên, bạn phải đặt biểu đồ điều hướng của máy chủ theo cách thủ công bên trong onCreate()
để có thể truyền mã bổ sung ý định (intent extra) dưới dạng đối số sang mảnh, như trình bày dưới đây:
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 = navHostFramgent.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()); } }
Bạn có thể lấy dữ liệu ra khỏi đối số mảnh Bundle
bằng cách sử dụng lớp đối số đã tạo, như trong ví dụ sau:
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(); ... } ...
Kết hợp các hoạt động
Bạn có thể kết hợp các biểu đồ điều hướng trong trường hợp nhiều hoạt động có cùng một bố cục, chẳng hạn như một FrameLayout
đơn giản chứa một mảnh đơn lẻ. Trong hầu hết trường hợp như vậy, bạn chỉ cần kết hợp tất cả phần tử trên mỗi biểu đồ điều hướng rồi cập nhật mọi phần tử đích đến cho hoạt động thành các đích đến cho mảnh.
Ví dụ sau đây kết hợp các Biểu đồ A và B ở phần trước:
Trước khi kết hợp:
<!-- 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>
Sau khi kết hợp:
<!-- 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>
Việc giữ nguyên tên hành động trong khi hợp nhất có thể giúp quá trình này diễn ra liền mạch mà không đòi hỏi bạn phải chỉnh sửa mã cơ sở hiện tại. Ví dụ: navigateToProductDetail
vẫn được giữ nguyên ở đây. Điểm khác biệt duy nhất là thao tác này giờ đây đại diện cho quá trình điều hướng đến một đích đến cho mảnh trong cùng NavHost
thay vì một đích đến cho hoạt động:
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);
Tài nguyên khác
Để biết thêm thông tin liên quan đến thành phần điều hướng, hãy xem các chủ đề sau:
- Sử dụng NavigationUI để cập nhật thành phần giao diện người dùng – Tìm hiểu cách quản lý hoạt động điều hướng bằng thanh ứng dụng trên cùng, ngăn điều hướng và thành phần điều hướng dưới cùng
- Kiểm thử Thành phần điều hướng – Tìm hiểu cách kiểm thử quy trình điều hướng cho ứng dụng