学而实习之 不亦乐乎

Android 中 NavController 的使用

2023-04-14 20:57:06

一、NavController

  NavController 主要负责操作 Navigation 框架下的 Fragment 的跳转与退出、动画、监听当前 Fragment 信息等基本操作。它可以在 Fragment 里调用,也可以在 Activity 里也可以调用。灵活的使用它,可以帮你实现所有形式的页面跳转。除此之外你甚至还能使用 TabLayout 配合 Navigation 进行主页的分页设计。极端点你甚至还能在某个分页里再次添加 TabLayout 配合 Navigation 进行嵌套设计。

二、获取 NavController 实例

1、在Activity中获取

// 这个R.id.fragment就是Activity布局里fragment控件的id
NavController controller = Navigation.findNavController(DemoActivity.this, R.id.fragment); 

2、在Fragment中获取

NavController  navController = Navigation.findNavController(getView());

三、使用场景

请注意在 Activity 里操作 NavController 与在 Fragment 里执行没有区别的。这里只介绍了在 Activity 中的代码

1、在Activity根据业务需要使用 setGraph 切换不同的 Navigation

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_demo);  
    initNav(2);
}

private void initNav(int type){
    NavController controller = Navigation.findNavController(DemoActivity.this, R.id.fragment); //在Activity里获取NavController实例
    if (type == 1){
        controller.setGraph(R.navigation.demo_one_nav);  //设置xml文件
        return;
    }
    if (type == 2){
        Bundle bundle = new Bundle();
        bundle.putString("name", "demo");
        controller.setGraph(R.navigation.demo_two_nav, bundle); //设置xml文件的并传入数据,这个数据可以在启动的Fragment里获取到
        return;
    }
    DemoActivity.this.finish();
}

上面的第二种方式,在设置 xml 文件的同时还传入了数据,这个数据在 Fragment 里建议用下面的方式获取

@Override
public void onAttach(@NonNull Context context) { //在Fragment里重写onAttach,在这里获取可以保证数据不会为null
    super.onAttach(context);
    Log.e("触发", "onAttach: 传入数据=" + getArguments().getString("name"));
}

2、在 Activity 中使用 navigate 跳转到 Fragment

【1】使用Action的id跳转

注意这里的 navigate 的值是 action 的 id,而使用 action 的 id 跳转到 Fragment 是不能凭空在没有上一个 Fragment 的情况下跳转下一个 Fragment的,这是会报错的。

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_demo);
    NavController controller = Navigation.findNavController(DemoActivity.this, R.id.fragment);
    controller.setGraph(R.navigation.demo_one_nav);
    controller.navigate(R.id.action_oneFragment_to_twoFragment); //从默认启动的oneFragment跳转到TwoFragment

}

【2】、使用 fragment 的 id 直接跳转
使用在 navigation xml 文件的 fragment 的 id 可以直接跳转到某一个 Fragment 里,这种跳转方式可以不需要是否有上一个Fragment。例如我们需要直接跳转到 threeFragment

<fragment
        android:id="@+id/threeFragment"
        android:name="com.zh.fragmentdemo.ThreeFragment"
        android:label="fragment_three"
        tools:layout="@layout/fragment_three" />

activity 中代码如下:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_demo);
    NavController controller = Navigation.findNavController(DemoActivity.this, R.id.fragment);
    controller.setGraph(R.navigation.demo_one_nav);
    controller.navigate(R.id.threeFragment); //直接跳转到threeFragment

}

3、使用 navigate 带动画跳转或者弹出 Fragment

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_demo);
    NavController controller = Navigation.findNavController(DemoActivity.this, R.id.fragment);
    controller.setGraph(R.navigation.demo_one_nav);
    NavOptions navOptions = new NavOptions.Builder()
            .setEnterAnim(R.anim.from_right) //进入动画
            .setExitAnim(R.anim.to_left)    //退出动画
            .setPopEnterAnim(R.anim.to_left)    //弹出进入动画
            .setPopExitAnim(R.anim.from_right)  //弹出退出动画
            .build();
    controller.navigate(R.id.action_oneFragment_to_twoFragment, null , navOptions);
}

4、使用 popBackStack 弹出 Fragment

【方式一】弹出当前的Fragment

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_demo);
    NavController controller = Navigation.findNavController(DemoActivity.this, R.id.fragment);
    controller.setGraph(R.navigation.demo_one_nav);
    controller.navigate(R.id.action_oneFragment_to_twoFragment); //从oneFragment进入到twoFragment
    controller.navigate(R.id.action_twoFragment_to_threeFragment);  //从twoFragment进入到threeFragment
    controller.popBackStack(); //弹出threeFragment,回到twoFragment
    
}

【方式二】弹出到指定的 Fragment,并且设置布尔值是否连这个指定 Fragment 一起弹出

注意要重视 public boolean popBackStack(@IdRes int destinationId, boolean inclusive) 这个方法。因为此方法可以实现清空中间导航栈堆的需求,举例假如 现在有 A -> B -> C -> D 这四个导航Fragment,如果我们现在当前导航到D并且想回到A,如果使用navigate()方法回到A,你就会发现用A Fragment里按返回键,不是直接退出而是直接到D  Fragment。 如果是使用popBackStack(@IdRes int destinationId, boolean inclusive)方法,就不会回到D Fragment

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_demo);
    NavController controller = Navigation.findNavController(DemoActivity.this, R.id.fragment);
    controller.setGraph(R.navigation.demo_one_nav);
    controller.navigate(R.id.action_oneFragment_to_twoFragment); //从oneFragment进入到twoFragment
    controller.navigate(R.id.action_twoFragment_to_threeFragment); //从twoFragment进入到threeFragment
    controller.popBackStack(R.id.twoFragment, true); //弹出到twoFragment,第二个参数的布尔值如果为true则表示参数一的Fragment一起弹出,这个时候我们就会回到oneFragment

}

另外,如果你发现目标Fragment不存在跳转失败后,你可以参考以下这种实现,直接创建指定目标的Fragment

boolean jumpStatus = Navigation.findNavController(getView()).popBackStack(R.id.scenesTimingFragment, false);
if (!jumpStatus){
    Navigation.findNavController(getView()).navigate(R.id.scenesTimingFragment);
}

5、navigateUp() 向上导航

Navigation.findNavController(getView()).navigateUp();

navigateUp 也是执行返回上一级Fragment 的功能。 popBackStack 和 navigateUp() 的区别是什么呢?

navigateUp向上返回的功能其实也是调用popBackStack的。 但是,navigateUp的源码里多了一层判断,源码如下:就是判断这个Navigation是否是最后一个Fragment,并且这个Navigation与里面的Fragment是不是有可能是其他Navigation跳转过来的。如果是其他Navigation跳转过来的就会回到之前的Navigation上。并且销毁当前Navigation的Activity。

public boolean navigateUp() {
    if (getDestinationCountOnBackStack() == 1) {
        // If there's only one entry, then we've deep linked into a specific destination
        // on another task so we need to find the parent and start our task from there
        NavDestination currentDestination = getCurrentDestination();
        int destId = currentDestination.getId();
        NavGraph parent = currentDestination.getParent();
        while (parent != null) {
            if (parent.getStartDestination() != destId) {
                TaskStackBuilder parentIntents = new NavDeepLinkBuilder(this)
                        .setDestination(parent.getId())
                        .createTaskStackBuilder();
                parentIntents.startActivities();
                if (mActivity != null) {
                    mActivity.finish();
                }
                return true;
            }
            destId = parent.getId();
            parent = parent.getParent();
        }
        // We're already at the startDestination of the graph so there's no 'Up' to go to
        return false;
    } else {
        return popBackStack();
    }
}

6、使用 getCurrentDestination 获取当前导航目的地

NavController controller = Navigation.findNavController(DemoActivity.this, R.id.fragment);
NavDestination navDestination = controller.getCurrentDestination(); //获取当前目的地的信息
Log.e(TAG, "onCreate: NavigatorName = " + navDestination.getNavigatorName());
Log.e(TAG, "onCreate: id = " + navDestination.getId());
Log.e(TAG, "onCreate: Parent = " + navDestination.getParent());

7、添加或者移除导航目的地监听

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_demo);
    NavController controller = Navigation.findNavController(DemoActivity.this, R.id.fragment);
    NavController.OnDestinationChangedListener listener = new NavController.OnDestinationChangedListener() {
        @Override
        public void onDestinationChanged(@NonNull NavController controller, @NonNull NavDestination destination, @Nullable Bundle arguments) {
            Log.e(TAG, "onDestinationChanged: id = " + destination.getId());
        }
    };
    controller.addOnDestinationChangedListener(listener); //添加监听
    controller.setGraph(R.navigation.demo_one_nav);
    controller.navigate(R.id.action_oneFragment_to_twoFragment);
    controller.navigate(R.id.action_twoFragment_to_threeFragment);
    controller.removeOnDestinationChangedListener(listener); //移除监听
}

8、使用 getNavInflater

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_demo);
    NavController controller = Navigation.findNavController(DemoActivity.this, R.id.fragment);
    NavGraph navGraph = controller.getNavInflater().inflate(R.navigation.demo_one_nav); //获得NavGraph 就是navigation,可以操作各种Fragment的增加或移除功能,一个是代码操作一个是xml操作
    controller.setGraph(navGraph); //其实用这种方式setGraph(navGraph)与直接setGraph(R.navigation.demo_one_nav);一样
}

9、Fragment里嵌套Navigation

代码如下:

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    mTabLayout = (TabLayout) view.findViewById(R.id.tab_layout);
    mAddBtn = (ImageView) view.findViewById(R.id.add_btn);
    
    //请注意,这个 R.id.scenes_fragment是Fragment布局里的fragment
    mNavController = Navigation.findNavController(getActivity(), R.id.scenes_fragment);

}