Android Jetpack组件(二)Navigation

Android Navigation + Fragment 制作APP主页面导航(步骤 + 源码) 添加位置如下图所示:添加好之后,点击Sync进行项目同步,同步时会自动下载这些依赖库并配置到你的项目中。添加完了依赖,就得先来简单介绍一下这个Navigation了,Navigation分为三大件:导航图、NavHost、NavController。为了方便我介绍下面的三个概念,这里假设有A、B、C三个Fragment。现在要从A切换到B:读取这个切换目标及路径:包含A、B、C的容器,用于显示Fragment。:在得知切换目标时,控制NavHost去显示B这个Fragment。 阅读详情

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

优点

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

主要元素

  1. Navigation Graph。这是一种新型的 XML 资源文件,其中包含应用程序所有的页面,以及页面间的关系。
  2. NavHostFragment。这是一个特殊的 Fragment,Navigation 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除了支持常见的integer、float和long等基本数据类型,还支持资源引用、自定义 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();
 }

NavigationUI

页面切换的过程中,通常会伴随着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()。
 NavHostFragment fragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
 navController = fragment.getNavController();
 appBarConfiguration = new AppBarConfiguration.Builder(this.navController.getGraph()).build();
 NavigationUI.setupWithNavController(
                binding.toolbar, this.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()添加菜单,则可以通过覆盖Activity的onOptionsItemSelected()以调用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) {
                //处理事件
            }
});

DeepLink

在项目开发中,我们可能需要跳转到应用内指定的页面,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页面切换。

Android Navigation 导航切换fragment用法 现在,您可以在您的代码中使用导航控制器来进行导航。例如,在Fragment中使用findNavController()方法获取导航控制器,并使用navigate()方法导航到其他Fragment。这样就可以通过使用导航组件实现在Fragment之间的导航了。您可以根据自己的需求在导航图中定义多个Fragment,以及在代码中设置不同的导航操作。在你的res目录下的navigation文件夹中创建一个nav_graph.xml文件,并定义您的导航图。在您的Activity中,设置导航控制器并处理导航事件。 阅读详情

相关推荐

Android Jetpack架构组件之Navigation

1、前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面。 Android Architecture组件是Android Jetpack的一部分,它们是一组库,旨在帮助开发者设计健壮、可测试和可维护的应用程序,包含一下组件: 带你领略Android Jet...

浅谈Android 6837

Navigation初始化NavHostFragment过程

Navigation初始化NavHostFragment过程Navigation初始化NavHostFragment过程1. MainActivity的初始化2. NavHostFragment的初始化2.1. 初始化NavHostController实例2.2. 调用NavHostController实例解析mGraphId2.2.1. Navigation初始化NavHostFragment过程 在使用Navigation库是,其固定模式是在Activity的布局文件中会先放一个NavHostFragm

weixin_40888127的博客 7788

使用Jetpack组件Navigation实现Android开发中页面跳转

目录一、前言1.概述2.导航图的创建(官网)二、基本使用1.依赖配置2.具体实例:使用Navigation实现页面的跳转。2.1.class的创建2.2 、页面布局文件的创建2.3 向 Activity 添加 NavHost2.4导航文件的配置清单2.5、按钮的布局 布局预览2.6、最终实现页面跳转3.导航图的预览4.最终实现预览导航是指支持用户导航、进入和退出应用中不同内容片断的交互。Android Jetpack的导航组件可以帮助实现导航,无论是简单的按钮点击,还是应用栏和抽屉式导航栏等更为复杂的模式,

qq_52967092的博客 2554

Navigation——Fragment创建新的实例问题详解

通过以上的方法,可以实现我们想要的效果,但是我认为这只是一个临时的解决方案,修改源码这种方式,并不是一个特别好的解决方案。如果有其他更好的方法,方案,欢迎给我公共号「朝阳杨大爷」给我留言,讨论。

chuyouyinghe的专栏 791

Android Jetpack架构组件(五)之Navigation

一、 Navigation简介 1.1 Navigation诞生背景 采用单个Activity嵌套多个Fragment的UI架构模式,已经被大多数的Android工程师所接受。但是,对于Fragment的管理一直是一件比较麻烦的事情,工程师需要通过FragmentManager和FragmentTransaction来管理Fragment之间的切换。 在Android中,页面的切换和管理包括应用程序Appbar的管理、Fragment的动画切换以及Fragment之间的参数传递等内容。并且,纯代码的方式使用

xiangzhihong8的专栏 9060

【教程】安装 iterm2 打造漂亮且高性能的 mac 终端

最近电脑重新安装系统,开发环境和软件都需要重新安装和配置,其中终端工具是必不可少的,正好利用这个机会,介绍一下 macOS 终端神器 iTerm2 的安装和配置,并推荐几款好用的插件和工具。在IT领域中,工具的选择和使用对于开发者和运维人员来说至关重要。而在众多终端模拟器中,ITerm2无疑是一个出色的选择,希望本篇的文章能对你有些帮助,感兴趣的朋友可以登录官网查看官方文档。我是刘大大,持续分享AI相关领域的知识和工具,喜欢点点关注。

刘大大AI的博客 3399

【Jetpack】Navigation 导航组件 ② ( Navigation 核心要点说明 | 创建 Navigation Graph | 创建 NavHostFragment | 完整代码示例 )

一、Navigation 导航组件开发核心要点说明 1、Navigation 各子部件的创建顺序 2、导入 Navigation 依赖 3、创建 Fragment 及布局文件 4、创建 Navigation Graph 5、创建 NavHostFragment 6、创建 NavController 7、Fragment 实现导航操作 二、Navigation 导航组件完整代码示例 1、Fragment 完整源码 FragmentA 源码 FragmentA 布局文件

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 6388

Yocto下载加速秘籍:用清华镜像+PREMIRRORS配置让构建速度翻倍

本文详细解析了如何通过配置Yocto的PREMIRRORS优先级策略,并利用清华大学等国内镜像源,显著加速构建过程中的源码下载。文章提供了具体的配置示例和验证方法,帮助开发者解决网络瓶颈,实现构建速度翻倍,提升开发效率。

game4的博客 275

Android Jetpack Navigation组件(二):设计导航图

目录前言一、导航图元素类型1.<navigation>元素2.目的地元素3.<action>元素二、嵌套导航图1.使用<navigation>元素2.使用<include>元素3.导航到嵌套导航图三、最终效果和工程代码1.最终效果2.工程代码 前言 通过上一章的学习,我们已经对导航组件的使用有了一个基本的了解,现在再来学习如何设计导航图吧。 一、导航图元素类型 1.<navigation>元素 <navigation>元素表示一个导航图

stephen_sun_的博客 2484

基于FPGA的DDR学习——1.基础知识

⚪DDR2(Double Data Rate2)SDRAM是由JEDEC(电子设备工程联合委员会)开发的第三代SDRAM内存技术标准,1.8V工作电压,240线接口,提供了相较于DDR SDRAM更高的运行效能与更低的电压,同样采用在时钟的上升/下降沿同时进行数据传输的基本方式,但拥有两倍于上一代DDR内存预读取能力(即4bit数据预取能力),其常见的频率规范有DDR2 400\533\667\800\1066\1333等,总线频率553MHZ的DDR2内存只需133MHZ的工作频率。

qq_44932488的博客 2070

HTPA 32x32d热成像传感器实战指南:从替代MLX90640到精准测温

本文为HTPA 32x32d热成像传感器提供了从硬件连接到精准测温的完整实战指南。针对MLX90640替代需求,详细解析了HTPA传感器的核心优势、IIC通信要点、EEPROM校准数据读取与解析、独特的块数据读取模式,并逐步拆解了五大温度补偿计算流程,旨在帮助开发者克服高开发门槛,实现稳定可靠的非接触式测温应用。

weixin_29279101的博客 313

Navigation返回重新执行onCreateView解决办法

Navigation返回重启解决办法第一个方案(利用生命周期保障程序运行):第二个方案(未销毁数据重用) Navigation作为Android jetpack新推出的组件使用起来非常的方便,甚至可以做到一个Activity配合Navigation完成整个应用的开发,但是有的小伙伴可能会发现在使用Navigation时点击返回键后页面重构,重新执行了onCreateView方法,那遇到这种问题怎么...

滨江昔岸的博客 8431

定制PYNQ的overlay

一、用HLS生成一个定制IP(加速器)(1)首先打开vivado hls,本人的版本是2017.2,点击创建一个新工程。(2)设置工程名和路径,顶层函数设置为add,创建一个新的C++程序,名字叫做adder.cpp,不用在testbench中添加文件。选择板子的时候搜索xc7z020clg400-1,这是PYNQ的板子号,选择完成。             (3)在左侧的菜单栏里选择Sourc...

bramblewalls的博客 1万+

Android FragmentContainerView 新手详解(Java 版)

本文介绍了Android开发中FragmentContainerView的基本概念和用法。作为官方推荐的Fragment专用容器,它比传统FrameLayout具有更好的生命周期管理、动画支持和兼容性。文章重点解析了与Navigation组件结合使用的典型XML配置,包括基础布局属性、核心标识属性(如id和name)以及Navigation专属属性(defaultNavHost和navGraph)。同时提供了两种核心用法场景:主流Navigation结合方案和传统手动管理方案,并强调了相关依赖配置的重要性。

u013862108的博客 1106

WinCC ActiveX控件“许可证缺失”故障排查:从注册表修复到系统兼容性全解析

本文深入解析WinCC ActiveX控件报错“未找到许可证信息”的根本原因与解决方案。核心在于系统注册表中控件的设计时许可证密钥丢失或损坏。文章提供了从手动修复注册表、排查64位系统兼容性,到预防性环境部署的全流程专业指南,帮助工程师彻底解决这一常见故障。

oil88的博客 646

基于深度确定性策略梯度的永磁同步电机强化学习控制方法

本文提出了一种基于深度确定性策略梯度(DDPG)的永磁同步电机(PMSM)电流内环智能控制方法。首先建立了PMSM在d-q坐标系下的数学模型,分析了传统矢量控制双闭环系统中PI控制器的局限性。针对电流内环控制问题,设计了包含电流跟踪误差、转速信息和历史控制量的状态空间,将电压输出作为动作空间,并构建了兼顾跟踪精度和响应速度的奖励函数。通过DDPG算法训练智能体,使其能够自适应调整控制策略。仿真结果表明,与固定参数的PI控制器相比,DDPG控制器在动态响应速度和抗干扰能力方面具有明显优势,为PMSM高性能控制

huanghm88的专栏 104

【Jetpack】Navigation 导航组件 ① ( Navigation 引入 | Navigation 特点 | Navigation 重要组件 | Navigation 使用流程 )

一、Navigation 引入 二、Navigation 特点 三、Navigation 重要组件 四、Navigation 使用流程 1、创建 Fragment 2、创建 Navigation Graph 组件 3、处理 Navigation Graph 组件报错信息 " failed to add navigation dependency " 4、编辑 Navigation Graph 组件 - 创建 action 跳转 5、创建 NavHostFragment 组件 6、在 Activity 中获取

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 9202

使用 Simulink + Simscape Electrical 对 级联H桥多电平整流器并网功率分配 进行建模与仿真

摘要:本文详细介绍了基于Simulink对级联H桥(CHB)多电平整流器并网功率分配进行建模的方法。内容包括系统组成、功率分配意义、Simulink建模流程、PWM控制策略实现、功率分配算法设计以及仿真分析。重点阐述了主电路搭建、双环控制结构(外环功率分配+内环电流控制)和载波层叠PWM调制技术,并提供了功率分配算法示例。通过优化参数和控制算法,可提升系统性能。该建模方法为电力电子变换器和新能源并网系统研究提供了有效工具。

amy_mhd的博客 251

带你领略Android Jetpack组件的魅力

1、前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面。 Android Architecture组件是Android Jetpack的一部分,它们是一组库,旨在帮助开发者设计健壮、可测试和可维护的应用程序,包含一下组件:  带你领略Android Je...

浅谈Android 3万+

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

Android Jetpack架构组件之Navigation入门

——沉默不是因为词穷,而是因为心空。现实有多残酷,你就应该有多坚强。 前言 一、简介 (1)是什么 (2)有什么用 (3)有什么优点 ​二、基本使用 (1)添加依赖 (2)创建navigation视图 (3)Activity中添加NavHost (4)代码实现导航功能 三、组件分析 四、进阶 五、内容推荐 前言 ——这篇主要是梳理一下Jetpack架构...

斌林诚上的博客 4854

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

Android:安卓学习笔记之navigation的简单理解和使用

Jetpack提供了一个名为Navigation的组件,旨在方便开发者管理Fragment页面和Appbar

JMW1407的博客 1万+

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Android Jetpack 组件库 ->Jetpack Navigation (上)

Jetpack Navigation 通过声明式导航图类型安全传参和深度 UI 集成,显著简化了复杂导航逻辑的开发,尤其适合单 Activity 架构。其可视化工具和标准化 API 降低了维护成本,是构建现代 Android 应用的首选导航方案。Jetpack Navigation 官方文档。

Mark Wu 的博客 1120

Android Jetpack组件:Navigation与Room

Navigation 组件是 Android Jetpack 的一部分,它提供了一种用于在应用内管理不同界面导航的方式。通过使用 Navigation,开发者可以轻松实现界面间的跳转,同时保持代码的简洁和清晰。Room 是一个提供数据库访问抽象层的持久层框架,它将 Java 对象映射到数据库中的表。通过使用 Room,开发者可以轻松实现数据持久化,同时保持代码的简洁和高效。本文详细介绍了 Android Jetpack 组件中的 Navigation 和 Room。

2501_92486623的博客 1017

Android Jetpack Navigation 导航组件的使用

Android Jetpack:Navigation 导航组件的使用 官方介绍 导航组件是一套用于应用内导航的库,工具和指南。该组件将应用程序的所有导航信息集中在导航图中,为实现从简单按钮点击到复杂导航UI模式的所有操作提供了强大的框架。 Android Studio 3.3包含导航编辑器,可以显示导航图。其他功能和优点包括: 自动处理片段交互 正确处理上方和后方 动画和过渡的默认行为 深度链接...

Journey的博客 4146

Android Jetpack组件使用指南

Android Jetpack是一套由多个库组成的工具集,旨在帮助开发者更快地构建高质量的应用程序。它涵盖了从架构、界面、测试到工具等多个方面,旨在简化开发流程,提高开发效率。本文详细介绍了Android Jetpack组件的使用方法,包括架构组件、界面组件、测试组件和工具组件。通过掌握这些组件,开发者可以更快地构建高质量的应用程序。在实际开发过程中,开发者应根据项目需求灵活选择和使用这些组件,以提高开发效率和维护性。

2501_92487565的博客 2065

Android Jetpack组件:Navigation使用

Navigation组件旨在帮助开发者轻松实现Android应用中的导航功能。通过使用Navigation,开发者可以简化应用的架构,提高代码的可维护性,同时为用户提供流畅的导航体验。支持多种导航类型:包括 fragment、activity、dialog 等。易于集成:与ViewModel、LiveData等组件无缝集成。强大的图形编辑器:在Android Studio中提供可视化编辑功能。Android Jetpack组件中的Navigation使用,为开发者提供了一个高效、简洁的导航解决方案。

2501_92487231的博客 886
上一篇: Android Jetpack组件(一)LifeCycle
下一篇: Android Jetpack组件(三)ViewModel
八归少年
八归少年 新星创作者: 操作系统技术领域 新星创作者: 操作系统技术领域
博客等级 码龄9年 9011粉丝 63原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

八归少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值