目录
前言
本章将学习如何以编程的方式与Navigation组件交互。
一、创建NavHostFragment
可以不在XML文件中指定NavHostFragment,而是在运行时动态创建NavHostFragment。
activity_main.xml:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity:
public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 通过NavHostFragment.create()方法创建NavHostFragment
NavHostFragment finalHost = NavHostFragment.create(R.navigation.nav_graph);
getSupportFragmentManager().beginTransaction()
.replace(R.id.nav_host_fragment, finalHost)
.setPrimaryNavigationFragment(finalHost) // equivalent to app:defaultNavHost="true"
.commit();
}
}
二、动态设置导航图
常用的场景是通过动态设置导航图去动态设置startDestination。
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
.findFragmentById(R.id.nav_host_fragment);
NavController navController = navHostFragment.getNavController();
// 第一步:inflate导航图
NavGraph navGraph = navController.getNavInflater().inflate(R.navigation.nav_graph);
// 第二步:设置startDestination 会覆盖在XML中指定的startDestination
navGraph.setStartDestination(R.id.bFragment);
// 第三步:通过NavController设置导航图 必须最后再调用本方法
navController.setGraph(navGraph);
使用上述方法时,为了避免多次调用NavController.setGraph()方法,注意以下三点:
- 不要在XML文件中使用
app:navGraph
属性 - 不要调用
NavHostFragment.create(@NavigationRes int)
方法 - Don’t use any other APIs that rely solely on the R.navigation ID to inflate and set your graph
第三点是我直接从官方文档copy过来的,因为我担心理解错了。
直白翻译是:不要使用任何其他仅仅依赖导航图ID的 API 来加载和设置导航图。
三、NavBackStackEntry
引用NavBackStackEntry类的注释:
Representation of an entry in the back stack of a NavController
翻译:NavBackStackEntry是NavController管理的返回栈的元素。
-
这个返回栈与Fragment返回栈是联动的
-
NavBackStackEntry关联了一个目的地,当目的地在返回栈时,NavBackStackEntry可以提供限定于目的地的Lifecycle、ViewModelStore和SavedStateRegistry。
-
可以通过NavController获取指定目的地的NavBackStackEntry。
1.返回结果给前目的地
通过NavBackStackEntry的SavedStateHandle的LiveData返回结果给前目的地
假设我们想从B目的地(BFragment)返回数据给A目的地(AFragment)。
第一步:在A目的地(AFragment)通过LiveData监听数据
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
NavController navController = NavHostFragment.findNavController(this);
// 获取当前NavBackStackEntry的SavedStateHandle的LiveData
MutableLiveData<String> liveData = navController.getCurrentBackStackEntry()
.getSavedStateHandle()
.getLiveData("key");
liveData.observe(getViewLifecycleOwner(), new Observer<String>() {
@Override
public void onChanged(String result) {
// 处理监听到的数据
Toast.makeText(getContext(), "result: " + result, Toast.LENGTH_SHORT).show();
}
});
}
第二步:在B目的地(BFragment)通过LiveData返回数据
// navController.getPreviousBackStackEntry()获取的是前一个目的地的NavBackStackEntry
navController.getPreviousBackStackEntry().getSavedStateHandle().set("key", result);
注意:如果只处理一次结果,必须要调用SavedStateHandle的remove()
方法清除LiveData,否则你重新observe会收到之前的数据
// 清除LiveData
navController.getCurrentBackStackEntry().getSavedStateHandle().remove("key");
如果B目的地是一个DialogFragment,那么A目的地应该这样写:
NavController navController = NavHostFragment.findNavController(this);
// 通过目的地id获取NavBackStackEntry。如果返回栈有两个相同id的目的地,那么返回最上面的。
// 因为configuration change之后getCurrentBackStackEntry()方法获取的是DialogFragment的NavBackStackEntry。
final NavBackStackEntry navBackStackEntry = navController.getBackStackEntry(R.id.aFragment);
// 创建Observer在NavBackStackEntry's lifecycle ON_RESUME时获取返回结果,这里没有用LiveData
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("keyDialog")) {
String result = navBackStackEntry.getSavedStateHandle().get("keyDialog");
// 处理监听到的数据
Toast.makeText(getContext(), "result: " + result, Toast.LENGTH_SHORT).show();
// 清除LiveData
navController.getCurrentBackStackEntry().getSavedStateHandle().remove("keyDialog");
}
}
};
// 当前View Lifecycle处于STARTED状态,如果使用DialogFragment设置LiveData的数据会导致前目的地立马更新界面,而不是等待DialogFragment弹出再更新界面
navBackStackEntry.getLifecycle().addObserver(observer);
// 在view destroy后removeObserver
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);
}
}
});
2.获取导航图范围的ViewModel
由于NavBackStackEntry实现了ViewModelStoreOwner
接口,所以可以直接通过它获取ViewModel
// 通过目的地id获取NavBackStackEntry。导航图是特殊目的地,NavGraph继承了NavDestination
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.nav_graph);
MyViewModel viewModel = new ViewModelProvider(backStackEntry).get(MyViewModel.class);
导航图内的目的地之间可以通过导航图范围的ViewModel共享数据。
四、最终效果和工程代码
1.最终效果
NavigationProgrammatically