Android Jetpack Navigation组件(六):编程交互

前言

本章将学习如何以编程的方式与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()方法,注意以下三点:

  1. 不要在XML文件中使用app:navGraph属性
  2. 不要调用NavHostFragment.create(@NavigationRes int)方法
  3. 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

2.工程代码

代码地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值