Thành phần Điều hướng cung cấp nhiều cách để tạo và tương tác theo phương pháp có lập trình với một số phần tử điều hướng nhất định.
Tạo NavHostFragment
Bạn có thể sử dụng NavHostFragment.create()
để theo cách lập trình tạo NavHostFragment
với một tài nguyên biểu đồ cụ thể, như trong ví dụ dưới đây:
Kotlin
val finalHost = NavHostFragment.create(R.navigation.example_graph) supportFragmentManager.beginTransaction() .replace(R.id.nav_host, finalHost) .setPrimaryNavigationFragment(finalHost) // equivalent to app:defaultNavHost="true" .commit()
Java
NavHostFragment finalHost = NavHostFragment.create(R.navigation.example_graph); getSupportFragmentManager().beginTransaction() .replace(R.id.nav_host, finalHost) .setPrimaryNavigationFragment(finalHost) // equivalent to app:defaultNavHost="true" .commit();
Lưu ý rằng setPrimaryNavigationFragment(finalHost)
cho phép NavHost
chặn hệ thống nhấn nút Quay lại. Bạn cũng có thể triển khai hành vi này trong XML của NavHost
bằng cách thêm app:defaultNavHost="true"
. Nếu đang triển khai hành vi tuỳ chỉnh của nút Quay lại và không muốn NavHost
chặn hành vi nhấn nút Quay lại, bạn có thể chuyển null
đến setPrimaryNavigationFragment()
.
Tham chiếu đến một đích đến bằng NavBackStackEntry
Từ Navigation 2.2.0, bạn có thể nhận tham chiếu đến NavBackStackEntry
cho bất kỳ đích đến nào trong ngăn xếp điều hướng khi gọi NavController.getBackStackEntry()
, đồng thời chuyển mã nhận dạng đích đến vào đó. Nếu ngăn xếp lui chứa nhiều phiên bản của đích đến đã định, thì getBackStackEntry()
sẽ trả về phiên bản trên cùng từ ngăn xếp.
NavBackStackEntry
được trả về cung cấp một Lifecycle
, mộtViewModelStore
và SavedStateRegistry
tại cấp độ đích đến. Các đối tượng này hợp lệ trong toàn thời gian tồn tại của đích đến trên ngăn xếp lui. Khi vị trí có liên quan được kéo ra khỏi ngăn xếp lui, Lifecycle
sẽ bị huỷ, trạng thái không còn được lưu và mọi đối tượng ViewModel
đều bị xoá.
Những thuộc tính này mang đến cho bạn một Lifecycle
và một cửa hàng cho các đối tượng cũng như lớp ViewModel
hoạt động với trạng thái đã lưu bất kể bạn sử dụng loại đích đến nào. Điều này đặc biệt hữu ích khi làm việc với các loại đích đến mà không tự động có Lifecycle
liên kết, chẳng hạn như các đích đến tuỳ chỉnh.
Ví dụ: bạn có thể quan sát Lifecycle
của NavBackStackEntry
giống như khi bạn quan sát Lifecycle
của một mảnh hoặc hoạt động. Ngoài ra, NavBackStackEntry
là một LifecycleOwner
, có nghĩa là bạn có thể sử dụng mã này khi quan sát LiveData
hoặc với các thành phần nhận biết được vòng đời khác, như trong ví dụ sau:
Kotlin
myViewModel.liveData.observe(backStackEntry, Observer { myData -> // react to live data update })
Java
myViewModel.getLiveData().observe(backStackEntry, myData -> { // react to live data update });
Trạng thái vòng đời sẽ tự động cập nhật bất cứ khi nào bạn gọi navigate()
.
Trạng thái vòng đời cho đích đến không nằm ở đầu ngăn xếp lui sẽ di chuyển từ RESUMED
sang STARTED
nếu đích đến vẫn hiển thị trong đích đến FloatingWindow
, chẳng hạn như đích đến hộp thoại hoặc đến STOPPED
.
Trả lại kết quả về đích đến trước đó
Trong Navigation 2.3 trở lên, NavBackStackEntry
cấp quyền truy cập cho SavedStateHandle
.
SavedStateHandle
là một bản đồ khoá-giá trị có thể dùng để lưu trữ và truy xuất dữ liệu. Các giá trị này vẫn tồn tại cho đến khi bị buộc tắt, bao gồm cả thay đổi cấu hình và vẫn sẵn sàng hoạt động qua cùng đối tượng đó. Bằng cách sử dụng SavedStateHandle
đã cho, bạn có thể truy cập và chuyển dữ liệu giữa các đích đến.
Điều này đặc biệt hữu ích khi là cơ chế để lấy lại dữ liệu từ một đích đến sau khi được kéo khỏi ngăn xếp.
Để chuyển dữ liệu quay lại Đích đến A từ Đích đến B, trước tiên, thiết lập Đích đến A để nghe kết quả trên SavedStateHandle
của đích đến đó.
Để thực hiện việc này, truy xuất NavBackStackEntry
bằng API getCurrentBackStackEntry()
rồi observe
LiveData
do SavedStateHandle
cung cấp.
Kotlin
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val navController = findNavController(); // We use a String here, but any type that can be put in a Bundle is supported navController.currentBackStackEntry?.savedStateHandle?.getLiveData<String>("key")?.observe( viewLifecycleOwner) { result -> // Do something with the result. } }
Java
@Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { NavController navController = NavHostFragment.findNavController(this); // We use a String here, but any type that can be put in a Bundle is supported MutableLiveData<String> liveData = navController.getCurrentBackStackEntry() .getSavedStateHandle() .getLiveData("key"); liveData.observe(getViewLifecycleOwner(), new Observer<String>() { @Override public void onChanged(String s) { // Do something with the result. } }); }
Trong Đích đến B, bạn phải set
kết quả trên SavedStateHandle
của Đích đến A bằng cách sử dụng API getPreviousBackStackEntry()
.
Kotlin
navController.previousBackStackEntry?.savedStateHandle?.set("key", result)
Java
navController.getPreviousBackStackEntry().getSavedStateHandle().set("key", result);
Nếu chỉ muốn xử lý kết quả một lần, bạn phải gọi remove()
trên SavedStateHandle
để xoá kết quả. Nếu bạn không xoá kết quả, LiveData
sẽ tiếp tục trả về kết quả gần đây nhất cho mọi phiên bản Observer
mới.
Điều cần cân nhắc khi sử dụng hộp thoại đích đến
Khi bạn navigate
tới một đích đến mà có chế độ xem toàn bộ của NavHost
(chẳng hạn như đích đến <fragment>
), thì đích đến trước đã ngừng vòng đời của mình, do vậy ngăn mọi lệnh gọi lại đến LiveData
do SavedStateHandle
cung cấp.
Tuy nhiên, khi di chuyển đến một đích đến hộp thoại, đích đến trước cũng sẽ hiển thị trên màn hình và do đó cũng STARTED
mặc dù không phải là đích đến hiện tại. Điều này có nghĩa là các lệnh gọi đến getCurrentBackStackEntry()
từ bên trong phương thức vòng đời (chẳng hạn như onViewCreated()
) sẽ trả về NavBackStackEntry
của đích đến hộp thoại sau khi thay đổi cấu hình hoặc bị buộc tắt và tái tạo (do hộp thoại được khôi phục bên trên đích đến khác). Vì vậy, bạn nên sử dụng getBackStackEntry()
cùng mã nhận dạng đích đến để đảm bảo luôn sử dụng đúng NavBackStackEntry
.
Điều này cũng có nghĩa là bất kỳ Observer
nào bạn đặt cho kết quả LiveData
sẽ được kích hoạt ngay cả khi các đích đến hộp thoại vẫn còn trên màn hình. Nếu chỉ muốn kiểm tra kết quả khi đích đến hộp thoại đang đóng và đích đến bên dưới trở thành đích đến hiện tại, thì bạn có thể quan sát Lifecycle
được liên kết với NavBackStackEntry
và chỉ truy xuất kết quả khi nó trở thành RESUMED
.
Kotlin
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val navController = findNavController(); // After a configuration change or process death, the currentBackStackEntry // points to the dialog destination, so you must use getBackStackEntry() // with the specific ID of your destination to ensure we always // get the right NavBackStackEntry val navBackStackEntry = navController.getBackStackEntry(R.id.your_fragment) // Create our observer and add it to the NavBackStackEntry's lifecycle val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_RESUME && navBackStackEntry.savedStateHandle.contains("key")) { val result = navBackStackEntry.savedStateHandle.get<String>("key"); // Do something with the result } } navBackStackEntry.lifecycle.addObserver(observer) // As addObserver() does not automatically remove the observer, we // call removeObserver() manually when the view lifecycle is destroyed viewLifecycleOwner.lifecycle.addObserver(LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_DESTROY) { navBackStackEntry.lifecycle.removeObserver(observer) } }) }
Java
@Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); NavController navController = NavHostFragment.findNavController(this); // After a configuration change or process death, the currentBackStackEntry // points to the dialog destination, so you must use getBackStackEntry() // with the specific ID of your destination to ensure we always // get the right NavBackStackEntry final NavBackStackEntry navBackStackEntry = navController.getBackStackEntry(R.id.your_fragment); // Create our observer and add it to the NavBackStackEntry's lifecycle final LifecycleEventObserver observer = new LifecycleEventObserver() { @Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { if (event.equals(Lifecycle.Event.ON_RESUME) && navBackStackEntry.getSavedStateHandle().contains("key")) { String result = navBackStackEntry.getSavedStateHandle().get("key"); // Do something with the result } } }; navBackStackEntry.getLifecycle().addObserver(observer); // As addObserver() does not automatically remove the observer, we // call removeObserver() manually when the view lifecycle is destroyed getViewLifecycleOwner().getLifecycle().addObserver(new LifecycleEventObserver() { @Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { if (event.equals(Lifecycle.Event.ON_DESTROY)) { navBackStackEntry.getLifecycle().removeObserver(observer) } } }); }
Chia sẻ dữ liệu liên quan đến giao diện người dùng giữa các đích đến bằng ViewModel
Ngăn xếp lui Điều hướng không chỉ lưu trữ một NavBackStackEntry
cho từng đích đến riêng lẻ mà còn cho từng biểu đồ điều hướng mẹ có chứa đích đến riêng lẻ. Điều này cho phép bạn truy xuất NavBackStackEntry
trong phạm vi biểu đồ điều hướng. NavBackStackEntry
trong phạm vi biểu đồ điều hướng cung cấp một cách để tạo ViewModel
thuộc phạm vi của biểu đồ điều hướng, cho phép bạn chia sẻ dữ liệu liên quan đến giao diện người dùng giữa các đích đến của biểu đồ. Mọi đối tượng ViewModel
được tạo theo cách này sẽ tồn tại cho đến khi NavHost
được liên kết và ViewModelStore
của nó bị xoá hoặc cho đến khi biểu đồ điều hướng được kéo ra khỏi ngăn xếp lui.
Ví dụ sau cho thấy cách truy xuất ViewModel
được định phạm vi trong một biểu đồ điều hướng:
Kotlin
val viewModel: MyViewModel by navGraphViewModels(R.id.my_graph)
Java
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.my_graph); MyViewModel viewModel = new ViewModelProvider(backStackEntry).get(MyViewModel.class);
Nếu đang sử dụng Điều hướng 2.2.0 trở về trước, bạn cần cung cấp nhà máy riêng của mình để sử dụng Trạng thái đã lưu với ViewModel, như trong ví dụ sau:
Kotlin
val viewModel: MyViewModel by navGraphViewModels(R.id.my_graph) { SavedStateViewModelFactory(requireActivity().application, requireParentFragment()) }
Java
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.my_graph); ViewModelProvider viewModelProvider = new ViewModelProvider( backStackEntry.getViewModelStore(), new SavedStateViewModelFactory( requireActivity().getApplication(), requireParentFragment())); MyViewModel myViewModel = provider.get(myViewModel.getClass());
Để biết thêm thông tin về ViewModel
, vui lòng xem nội dung Tổng quan về ViewModel.
Sửa đổi biểu đồ điều hướng tăng cường
Bạn có thể sửa đổi biểu đồ điều hướng mở rộng theo phương thức động trong thời gian chạy.
Ví dụ: nếu bạn có BottomNavigationView
được liên kết với một NavGraph
, thì đích đến mặc định của NavGraph
sẽ ấn định thẻ được chọn khi khởi động ứng dụng. Tuy nhiên, bạn có thể phải ghi đè hành vi này, chẳng hạn như khi một tuỳ chọn người dùng chỉ định một thẻ ưa thích cần được tải khi khởi động ứng dụng. Ngoài ra, ứng dụng của bạn có thể cần thay đổi thẻ bắt đầu dựa trên hành vi trước đây của người dùng. Bạn có thể hỗ trợ các trường hợp này bằng cách linh động chỉ định đích đến mặc định của NavGraph
.
Nên cân nhắc NavGraph
này.
<?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/home"> <fragment android:id="@+id/home" android:name="com.example.android.navigation.HomeFragment" android:label="fragment_home" tools:layout="@layout/fragment_home" /> <fragment android:id="@+id/location" android:name="com.example.android.navigation.LocationFragment" android:label="fragment_location" tools:layout="@layout/fragment_location" /> <fragment android:id="@+id/shop" android:name="com.example.android.navigation.ShopFragment" android:label="fragment_shop" tools:layout="@layout/fragment_shop" /> <fragment android:id="@+id/settings" android:name="com.example.android.navigation.SettingsFragment" android:label="fragment_settings" tools:layout="@layout/fragment_settings" /> </navigation>
Khi biểu đồ này được tải, thuộc tính app:startDestination
chỉ định rằng HomeFragment
sẽ được hiển thị. Để linh động ghi đè đích đến bắt đầu, làm như sau:
- Trước tiên, mở rộng
NavGraph
bằng cách thủ công. - Ghi đè đích đến bắt đầu.
- Cuối cùng, đính kèm biểu đồ vào
NavController
theo cách thủ công.
Kotlin
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment val navController = navHostFragment.navController val navGraph = navController.navInflater.inflate(R.navigation.bottom_nav_graph) navGraph.startDestination = R.id.shop navController.graph = navGraph binding.bottomNavView.setupWithNavController(navController)
Java
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager() .findFragmentById(R.id.nav_host_fragment); NavController navController = navHostFragment.getNavController(); NavGraph navGraph = navController.getNavInflater().inflate(R.navigation.bottom_nav_graph); navGraph.setStartDestination(R.id.shop); navController.setGraph(navGraph); NavigationUI.setupWithNavController(binding.bottomNavView, navController);
Giờ đây, khi ứng dụng của bạn khởi động, ShopFragment
sẽ hiển thị thay vì HomeFragment
.
Khi sử dụng liên kết sâu, NavController
sẽ tự động tạo một ngăn xếp lui cho đích đến là liên kết sâu. Nếu người dùng di chuyển đến liên kết sâu và sau đó quay lại, thì họ sẽ đến đích đến bắt đầu vào lúc nào đó. Việc ghi đè đích đến bắt đầu bằng cách sử dụng kỹ thuật trong ví dụ trước sẽ đảm bảo rằng đích đến bắt đầu chính xác được thêm vào ngăn xếp lui được tạo.
Lưu ý rằng kỹ thuật này cũng cho phép ghi đè các khía cạnh khác của NavGraph
theo yêu cầu. Phải thực hiện mọi sửa đổi với biểu đồ trước lệnh gọi đến setGraph()
để đảm bảo rằng cấu trúc chính xác được sử dụng khi xử lý liên kết sâu, khôi phục trạng thái và di chuyển đến đích đến bắt đầu của biểu đồ.