Jetpack组件之Navigation

Jetpack组件之Navigation

八归少年 2,292 2021-03-27

Android Jetpack组件系列文章:
Android Jetpack组件(一)LifeCycle
Android Jetpack组件(二)Navigation
Android Jetpack组件(三)ViewModel
Android Jetpack组件(四)LiveData
Android Jetpack组件(五)Room
Android JetPack组件(六)DataBinding
Android Jetpack组件(七)Paging
Android Jetpack组件(八)WorkManager
Android Jetpack组件(九)DataStore
Android Jetpack组件(十)App Startup
Android Jetpack组件(十一)Compose

首语

Android开发中流行单个Activity嵌套多个Fragment的UI架构模式,但是对Fragment的管理比较麻烦。Fragment的切换包括对AppBar的管理、Fragment间的切换动画以及Fragment间的参数传递。在此过程中实现代码比较复杂混乱。为此,Jetpack提供了Navigation组件,方便我们管理页面和AppBar。

优点

  1. 可视化的页面导航图,可以使用 Android Studio 的 Navigation Editor 来查看和编辑导航图。
  2. 通过destinationaction完成页面间的导航。
  3. 方便添加页面切换动画。
  4. 页面间类型安全的参数传递。
  5. 支持深层链接DeepLink
  6. 通过NavigationUI类,对菜单、底部导航、抽屉菜单导航进行统一的管理。

主要元素

  1. Navigation Graph。这是一种新型的 XML 资源文件,其中包含应用程序所有的页面,以及页面间的关系。
  2. NavHostFragment。这是一个特殊的 FragmentNavigation Graph 中的 Fragment 正是通过 NavHostFragment进行展示的。
  3. NavController。这是一个 Java/Kotlin 对象,用于在代码中完成 Navigation Graph 中具体的页面切换工作。

使用

使用Navigation时,我们需要添加如下依赖:

  def nav_version = "2.3.3"

  // Java language implementation
  implementation "androidx.navigation:navigation-fragment:$nav_version"
  implementation "androidx.navigation:navigation-ui:$nav_version"

  // Kotlin
  implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
  implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
  
  // Jetpack Compose Integration
  implementation "androidx.navigation:navigation-compose:1.0.0-alpha07"

创建 Navigation Graph

新建一个Android项目后,选择res文件夹—>New Android Resource File,Resource type选择navigation,填写文件名后回车,Navigation Grpah文件创建完成。我们切换到Design面板,可以看到面板左上角提示No NavHostFragments found,接下来添加NavHostFragment

添加NavHostFragment

NavHostFragment是一种特殊的Fragment,我们需要将它添加到Activity的布局文件中,作为其它Fragment的容器。

<!--app:defaultNavHost="true"表示Fragment会自动处理系统返回事件-->
<!--app:navGraph="@navigation/mobile_navigation"设置Fragment对应的导航图-->
<fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/mobile_navigation" />

我们再次回到Nav Graph文件的Design面板,可以看到我们刚才设置的NavHostFragment

创建destination

点击Navigation Graph文件的Design面板上的click to add a destination,可以选择现有的Fragment,也可以点击 Create new destination创建Fragment。添加完成以后,可以看到Navigation Graph文件的代码。

<?xml version="1.0" encoding="utf-8"?>
<!--app:startDestination="@id/homeFragment"设置起始目的地-->
<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/homeFragment">

    <fragment
        android:id="@+id/homeFragment"
        android:name="com.yhj.jetpackstudy.ui.home.HomeFragment"
        android:label="fragment_home"
        tools:layout="@layout/fragment_home" />
    <fragment
        android:id="@+id/dashboardFragment"
        android:name="com.yhj.jetpackstudy.ui.dashboard.DashboardFragment"
        android:label="fragment_dashboard"
        tools:layout="@layout/fragment_dashboard" />
</navigation>

连接destination

在Navigation Graph文件的Design面板中,将鼠标悬停在destination的右侧,会出现一个圆圈,点击圆圈并将光标拖动到导航destination的上面,松开鼠标,会生成一个指示线。切换到Code面板可以看到代码发生了变化。

<fragment
        android:id="@+id/homeFragment"
        android:name="com.yhj.jetpackstudy.ui.home.HomeFragment"
        android:label="fragment_home"
        tools:layout="@layout/fragment_home" >
        <action
            android:id="@+id/action_homeFragment_to_dashboardFragment4"
            app:destination="@id/dashboardFragment" />
</fragment>

使用NavController完成导航

页面的切换通常有两种方式:

 button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Navigation.findNavController(view).navigate(R.id.action_navigation_home_to_navigation_dashboard);
            }
        });
        
 button.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_navigation_home_to_navigation_dashboard));        

添加页面切换效果

在Navigation Graph文件的action下添加要执行的动画

 <action
        android:id="@+id/confirmationAction"
        app:destination="@id/confirmationFragment"
        app:enterAnim="@anim/slide_in_right"
        app:exitAnim="@anim/slide_out_left"
        app:popEnterAnim="@anim/slide_in_left"
        app:popExitAnim="@anim/slide_out_right" />

使用Safe Args插件传递参数

使用Safe Args Gradle插件,该插件可以生成简单的对象和构造器类,支持在destination之间进行类型安全的导航和参数传递。
在Project的build.gradle的dependencies下添加classpath。

 dependencies {
        classpath "com.android.tools.build:gradle:4.0.1"

        def nav_version = "2.3.3"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}

如需生成适合Java模块或Java和Kotlin混合模块的代码,可以在app的build.gradle下添加apply plugin。

apply plugin: "androidx.navigation.safeargs"

如需生成适用于 Kotlin 独有的模块的 Kotlin 代码,添加如下代码。

apply plugin: "androidx.navigation.safeargs.kotlin"

在Navigation Graph的添加如下<argument />标签内代码。

<fragment
        android:id="@+id/navigation_home"
        android:name="com.yhj.life.LifeFragment"
        android:label="fragment_life"
        tools:layout="@layout/fragment_life">

        <action
            android:id="@+id/action_navigation_home_to_navigation_dashboard"
            app:destination="@id/navigation_dashboard" />

         <argument
            android:name="user_name"
            android:defaultValue="@null"
            app:argType="string" />

        <argument
            android:name="age"
            android:defaultValue="0"
            app:argType="integer" />

</fragment>

argType除了支持常见的integerfloatlong等基本数据类型,还支持资源引用、自定义 Parcelable、自定义 Serializable和自定义 Enum
添加完成之后,在app java(generated)下面可以看到Safe Args插件为我们生成的代码,代码文件里包含参数生成的getter()setter()

  • 传递参数
Bundle bundle=new MainFragmentArgs().builder()
                .setUserName("yhj")
                .setAge(30)
                .build().toBundle();
Navigation.findNavController(view).navigate(R.id.action_navigation_home_to_navigation_dashboard);
  • 接收参数
 Bundle bundle=getArguments();
 if(bundle!=null){
       String userName=MainFragmentArgs.fromBundle(getArguments()).getUserName();
       int age=MainFragmentArgs.fromBundle(getArguments()).getAge();
 }

页面切换的过程中,通常会伴随着AppBar的变化,AppBar中的按钮也可能承担页面切换的工作,既然Navigation和AppBar都需要处理页面切换事件,为了方便管理,Jetpack引入了NavigationUI组件。
在Navigation Graph文件中可以通过android:label来设置AppBar的标题。

<navigation>
    <fragment
              android:label="Page title">
      ...
    </fragment>
</navigation>

应用栏类型

NavigationUI支持以下顶部应用栏类型:

  • Toolbar
    AppBarConfiguration用于AppBar的配置,NavController用于页面的导航和切换。通过 NavigationUI.setupWithNavController绑定起来。
    使用Toolbar时,Navigation组件会自动处理导航按钮的点击事件,因此无需覆盖onSupportNavigateUp()
 NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
 AppBarConfiguration appBarConfiguration =
            new AppBarConfiguration.Builder(navController.getGraph()).build();
 Toolbar toolbar = findViewById(R.id.toolbar);
 NavigationUI.setupWithNavController(
            toolbar, navController, appBarConfiguration);
  • CollapsingToolbarLayout
 CollapsingToolbarLayout layout = findViewById(R.id.collapsing_toolbar_layout);
 Toolbar toolbar = findViewById(R.id.toolbar);
 NavHostFragment navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
 NavController navController = navHostFragment.getNavController();
 AppBarConfiguration appBarConfiguration =new AppBarConfiguration.Builder(navController.getGraph()).build();
 NavigationUI.setupWithNavController(layout, toolbar, navController, appBarConfiguration);
  • ActionBar
 NavHostFragment navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
 NavController navController = navHostFragment.getNavController();
 appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build();
 NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);

需要覆盖onSupportNavigateUp()处理向上导航。

@Override
public boolean onSupportNavigateUp() {
        return NavigationUI.navigateUp(navController, appBarConfiguration) || super.onSupportNavigateUp();
}
  • AppBar的左侧抽屉菜单(DrawLayout+Navigation View)
AppBarConfiguration appBarConfiguration =
        new AppBarConfiguration.Builder(navController.getGraph())
            .setDrawerLayout(drawerLayout)
            .build();
NavHostFragment navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
NavController navController = navHostFragment.getNavController();
NavigationView navView = findViewById(R.id.nav_view);
NavigationUI.setupWithNavController(navView, navController);
  • AppBar上的菜单(menu)
    如果通过Activity的onCreateOptionsMenu()添加菜单,则可以通过覆盖ActivityonOptionsItemSelected()以调用onNavDestinationSelected(),从而将菜单项与目标页相关联。同时需要覆盖onSupportNavigateUp()处理向上导航。
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
    return NavigationUI.onNavDestinationSelected(item, navController)
            || super.onOptionsItemSelected(item);
}

当我们在Fragment中添加菜单跳转目标页时,需要覆盖目标页面onCreateOptionsMenu(),并在该方法中清除上个页面对应的menu。

@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
        menu.clear();
        super.onCreateOptionsMenu(menu, inflater);
}
  • BottomNavigationView
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
                R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications)
                .build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
//对于BottomNavigationView支持
NavigationUI.setupWithNavController(navView, navController);

导航事件监听

NavController提供了一个名为OnDestinationChangedListener的接口,对页面切换事件进行监听,该接口在页面发生切换或参数改变时调用。

navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {
            @Override
            public void onDestinationChanged(@NonNull NavController controller,
                                             @NonNull NavDestination destination, @Nullable Bundle arguments) {
                //处理事件
            }
});

在项目开发中,我们可能需要跳转到应用内指定的页面,Navigation组件提供了DeepLink(深层链接),通过它实现跳转到应用指定页面。它支持两种不同类型的深层链接:显式深层链接和隐式深层链接。

显式深层链接

显式深层链接使用PendingIntent跳转到指定页面,例如应用程序收到某个通知推送,用户点击此通知时,条抓到展示该通知的内容页面。
当用户通过显式深层链接打开您的应用时,任务返回堆栈会被清除,并被替换为相应的深层链接页面。当用户从深层链接页面按下返回按钮时,他们会返回到相应的导航堆栈。
我们使用NavDeepLinkBuilder类构造PendingIntent

PendingIntent pendingIntent = new NavDeepLinkBuilder(context)
    .setGraph(R.navigation.nav_graph)
    .setDestination(R.id.android)
    .setArguments(args)
    .createPendingIntent();

也可以通过navController.createDeepLink()创建。

Navigation.findNavController(this, R.id.nav_host_fragment)
                .createDeepLink()
                .setGraph(R.navigation.mobile_navigation)
                .setDestination(R.id.action_navigation_home_to_navigation_dashboard2)
                .setArguments(bundle)
                .createPendingIntent();

需要注意的是,如果提供的上下文不是Activity,构造函数会使用PackageManager.getLaunchIntentForPackage()作为默认Activity启动(如有)。

//NavDeepLinkBuilder的构造函数
 public NavDeepLinkBuilder(@NonNull Context context) {
        mContext = context;
        if (mContext instanceof Activity) {
            mIntent = new Intent(mContext, mContext.getClass());
        } else {
            Intent launchIntent = mContext.getPackageManager().getLaunchIntentForPackage(
                    mContext.getPackageName());
            mIntent = launchIntent != null ? launchIntent : new Intent();
        }
        mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
 }

隐式深层链接

隐式深层链接指的是应用中特定页面的URI,如用户点击某个链接。
在触发隐式深层链接时,返回堆栈的状态取决于是否使用 Intent.FLAG_ACTIVITY_NEW_TASK 标志启动隐式Intent

  • 如果该标志已设置,任务返回堆栈就会被清除,并被替换为相应的深层链接页面。与显式深层链接一样。
  • 如果该标记未设置,您仍会位于触发隐式深层链接时所在的上一个应用的任务堆栈中。在这种情况下,如果按下返回按钮,您会返回到上一个应用;如果按下向上按钮,就会在导航图中的父级目的地上启动应用的任务。
    在Navigation Graph文件中为页面添加<deepLink />标签。
 <deepLink app:uri="www.yanghujun.com/{params}?arg={arg}"/>

注意:

  • 没有架构的 URI 会被假定为 http 或 https。
  • 形式为 {params} 的路径参数占位符与一个或多个字符相匹配。
  • 可以使用查询参数占位符代替路径参数,也可以将查询参数占位符与路径参数结合使用。
  • 使用默认值或可为 null 的值所定义的变量的查询参数占位符无需匹配。
  • 多余的查询参数不会影响深层链接 URI 匹配。
    启用隐式深层链接,还需要向应用的manifest文件中添加nav-graph标签。
<nav-graph android:value="@navigation/mobile_navigation"/>

构建项目时,Navigation 组件会将 <nav-graph /> 标签替换为生成的 <intent-filter /> 标签,以匹配导航图中的所有深层链接。

定制FragmentNavigator

阅读FragmentNavigator的源码时,可以看到页面切换的时候使用的是replace(),这会造成Fragment生命周期的重启,界面数据重新加载,不能复用。源码如下。

	@SuppressWarnings("deprecation") /* Using instantiateFragment for forward compatibility */
    @Nullable
    @Override
    public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
        if (mFragmentManager.isStateSaved()) {
            Log.i(TAG, "Ignoring navigate() call: FragmentManager has already"
                    + " saved its state");
            return null;
        }
        String className = destination.getClassName();
        if (className.charAt(0) == '.') {
            className = mContext.getPackageName() + className;
        }
        final Fragment frag = instantiateFragment(mContext, mFragmentManager,
                className, args);
        frag.setArguments(args);
        final FragmentTransaction ft = mFragmentManager.beginTransaction();

        int enterAnim = navOptions != null ? navOptions.getEnterAnim() : -1;
        int exitAnim = navOptions != null ? navOptions.getExitAnim() : -1;
        int popEnterAnim = navOptions != null ? navOptions.getPopEnterAnim() : -1;
        int popExitAnim = navOptions != null ? navOptions.getPopExitAnim() : -1;
        if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
            enterAnim = enterAnim != -1 ? enterAnim : 0;
            exitAnim = exitAnim != -1 ? exitAnim : 0;
            popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
            popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
            ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
        }

        ft.replace(mContainerId, frag);
        ft.setPrimaryNavigationFragment(frag);

        final @IdRes int destId = destination.getId();
        final boolean initialNavigation = mBackStack.isEmpty();
        // TODO Build first class singleTop behavior for fragments
        final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
                && navOptions.shouldLaunchSingleTop()
                && mBackStack.peekLast() == destId;

        boolean isAdded;
        if (initialNavigation) {
            isAdded = true;
        } else if (isSingleTopReplacement) {
            // Single Top means we only want one instance on the back stack
            if (mBackStack.size() > 1) {
                // If the Fragment to be replaced is on the FragmentManager's
                // back stack, a simple replace() isn't enough so we
                // remove it from the back stack and put our replacement
                // on the back stack in its place
                mFragmentManager.popBackStack(
                        generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
                        FragmentManager.POP_BACK_STACK_INCLUSIVE);
                ft.addToBackStack(generateBackStackName(mBackStack.size(), destId));
            }
            isAdded = false;
        } else {
            ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
            isAdded = true;
        }
        if (navigatorExtras instanceof Extras) {
            Extras extras = (Extras) navigatorExtras;
            for (Map.Entry<View, String> sharedElement : extras.getSharedElements().entrySet()) {
                ft.addSharedElement(sharedElement.getKey(), sharedElement.getValue());
            }
        }
        ft.setReorderingAllowed(true);
        ft.commit();
        // The commit succeeded, update our view of the world
        if (isAdded) {
            mBackStack.add(destId);
            return destination;
        } else {
            return null;
        }
    }

通过show()/hide()方式可以实现Fragment的复用,同时也不会重启生命周期。可以自定义FragmentNavigator重写navigate()来达到以上目的。
首先自定义的FragmentNavigator要和FragmentNavigator一样,添加@Navigator.Name("新的Navigator名字")注解。

 	@Nullable
    @Override
    public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
        if (mManager.isStateSaved()) {
            Log.i(TAG, "Ignoring navigate() call: FragmentManager has already"
                    + " saved its state");
            return null;
        }
        String className = destination.getClassName();
        if (className.charAt(0) == '.') {
            className = mContext.getPackageName() + className;
        }
        //实例化过的要重用
        //final Fragment frag = instantiateFragment(mContext, mManager,
        //       className, args);
        //frag.setArguments(args);
        final FragmentTransaction ft = mManager.beginTransaction();

        int enterAnim = navOptions != null ? navOptions.getEnterAnim() : -1;
        int exitAnim = navOptions != null ? navOptions.getExitAnim() : -1;
        int popEnterAnim = navOptions != null ? navOptions.getPopEnterAnim() : -1;
        int popExitAnim = navOptions != null ? navOptions.getPopExitAnim() : -1;
        if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
            enterAnim = enterAnim != -1 ? enterAnim : 0;
            exitAnim = exitAnim != -1 ? exitAnim : 0;
            popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
            popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
            ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
        }

        //当前正在显示的fragment
        Fragment fragment = mManager.getPrimaryNavigationFragment();
        if (fragment != null) {
            //隐藏展示下个页面
            ft.hide(fragment);
        }

        Fragment frag = null;
        String tag = String.valueOf(destination.getId());

        //查找下个fragment,没有则创建
        frag = mManager.findFragmentByTag(tag);
        if (frag != null) {
            ft.show(frag);
        } else {
            frag = instantiateFragment(mContext, mManager, className, args);
            frag.setArguments(args);
            ft.add(mContainerId, frag, tag);
        }
        //ft.replace(mContainerId, frag);
        ft.setPrimaryNavigationFragment(frag);

        final @IdRes int destId = destination.getId();
//        mBackStack管理的是fragment回退的堆栈,源码中是private的无法获取,通过反射的方式获取
        ArrayDeque<Integer> mBackStack = null;
        try {
            Field field = FragmentNavigator.class.getDeclaredField("mBackStack");
            field.setAccessible(true);
            mBackStack = (ArrayDeque<Integer>) field.get(this);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }

        final boolean initialNavigation = mBackStack.isEmpty();
        // TODO Build first class singleTop behavior for fragments
        final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
                && navOptions.shouldLaunchSingleTop()
                && mBackStack.peekLast() == destId;

        boolean isAdded;
        if (initialNavigation) {
            isAdded = true;
        } else if (isSingleTopReplacement) {
            // Single Top means we only want one instance on the back stack
            if (mBackStack.size() > 1) {
                // If the Fragment to be replaced is on the FragmentManager's
                // back stack, a simple replace() isn't enough so we
                // remove it from the back stack and put our replacement
                // on the back stack in its place
                mManager.popBackStack(
                        generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
                        FragmentManager.POP_BACK_STACK_INCLUSIVE);
                ft.addToBackStack(generateBackStackName(mBackStack.size(), destId));
            }
            isAdded = false;
        } else {
            ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
            isAdded = true;
        }
        if (navigatorExtras instanceof Extras) {
            Extras extras = (Extras) navigatorExtras;
            for (Map.Entry<View, String> sharedElement : extras.getSharedElements().entrySet()) {
                ft.addSharedElement(sharedElement.getKey(), sharedElement.getValue());
            }
        }
        ft.setReorderingAllowed(true);
        ft.commit();
        // The commit succeeded, update our view of the world
        if (isAdded) {
            mBackStack.add(destId);
            return destination;
        } else {
            return null;
        }
    }

    private String generateBackStackName(int backStackindex, int destid) {
        return backStackindex + "-" + destid;
    }

然后在Activity中创建自定义的FragmentNavigator并设置给NavController,需要注意的是我们要通过自定义的FragmentNavigator手动来创建Destination(目的地),布局中app:navGraph=" "设置无效。

Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
navController = NavHostFragment.findNavController(fragment);
FixFragmentNavigator fragmentNavigator = new FixFragmentNavigator(this, getSupportFragmentManager(), fragment.getId());
navController.getNavigatorProvider().addNavigator(fragmentNavigator);
NavGraph navDestinations = initNavGraph(navController.getNavigatorProvider(), fragmentNavigator);
navController.setGraph(navDestinations);
//结合BottomNavigationView切换页面
navView.setOnNavigationItemSelectedListener(item -> {
            navController.navigate(item.getItemId());
            return true;
        });

private NavGraph initNavGraph(NavigatorProvider provider, FixFragmentNavigator fragmentNavigator) {
        NavGraph navGraph = new NavGraph(new NavGraphNavigator(provider));

        //用自定义的导航器来创建目的地
        FragmentNavigator.Destination destination1 = fragmentNavigator.createDestination();
        destination1.setId(R.id.navigation_home);
        destination1.setClassName(HomeFragment.class.getCanonicalName());
        navGraph.addDestination(destination1);


        FragmentNavigator.Destination destination2 = fragmentNavigator.createDestination();
        destination2.setId(R.id.navigation_dashboard);
        destination2.setClassName(DashboardFragment.class.getCanonicalName());
        navGraph.addDestination(destination2);

        FragmentNavigator.Destination destination3 = fragmentNavigator.createDestination();
        destination3.setId(R.id.navigation_notifications);
        destination3.setClassName(NotificationsFragment.class.getCanonicalName());
        navGraph.addDestination(destination3);

        navGraph.setStartDestination(destination1.getId());

        return navGraph;
    }

最后,我们还需要处理onBackPressed(),因为返回Navigation会操作回退栈,切换到之前显示的页面,我们需要销毁当前页面则要进行拦截。

    @Override
    public void onBackPressed() {
        finish();
    }

这样,我们就完成了定制的Fragment页面切换。


Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://www.yanghujun.com/archives/navigation