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

均匀分布产生高斯分布

均匀分布产生高斯分布 文章目录均匀分布产生高斯分布简介方法和证明代码效果引用 简介 方法和证明 代码 效果 引用

gy_Rick的博客 5317

【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

HAL库硬件SPI驱动ST7735 TFT屏幕:从零实现高效刷屏与图形绘制

本文详细介绍了如何使用STM32的HAL库硬件SPI驱动ST7735 TFT屏幕,从硬件连接到SPI配置优化,再到图形绘制与文本显示的实现。重点分享了DMA传输、局部刷新等高级优化技巧,显著提升刷屏效率,帮助开发者快速掌握屏幕驱动技术并应用于实际项目。

pink的博客 741

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

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

stephen_sun_的博客 2484

群晖NAS变身私有Git服务器:DS218+部署与TortoiseGit客户端实战指南

本文详细介绍了如何将群晖NAS(如DS218+)变身为私有Git服务器,并配置TortoiseGit客户端实现高效代码管理。通过硬件检查、系统更新、Git Server套件安装及权限配置等步骤,帮助开发者在局域网内搭建安全、高速的代码托管环境,特别适合小团队协作。

weixin_29252171的博客 223

AppStore逆向分析:从网络抓包到自动化模拟的完整实践

在移动应用生态中,理解客户端与服务器之间的通信协议是进行自动化测试、数据分析和系统集成的关键技术基础。通过分析网络请求与响应,可以深入理解认证、会话管理和数据交换的原理,这对于构建稳定的自动化流程至关重要。其技术价值在于能够实现合法场景下的流程模拟,例如应用元数据监控、沙盒环境测试或内部工具开发。在实际应用场景中,开发者常需处理登录态维持、请求签名验证及风控策略应对等挑战。本文聚焦于AppStore这一封闭生态,详细拆解了其登录、查询等核心环节的通信机制,并探讨了使用Python结合抓包工具进行安全逆向分析

weixin_42524171的博客 926

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

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

滨江昔岸的博客 8431

pytorchDataLoader中调整数据长度不一致问题(lstm等)

数据长度不一致 用pytorch做rnn的时候,如果输入数据不一样长,可以用两种方式解决。 一种是自定义collate方法, #自定义collate_fn dataloader.DataLoader(dataset,4,True,collate_fn=my_collate) 然后里面写数据和标签载入方式即可 def my_collate(batch): data = [item[0] for item in batch] target = [item[1] for item in batc

weixin_48174100的博客 7770

Android FragmentContainerView 新手详解(Java 版)

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

u013862108的博客 1106

随机子空间集成方法原理与scikit-learn实践

集成学习通过组合多个基学习器的预测结果来提升模型性能,是机器学习中的核心方法之一。随机子空间方法作为集成学习的重要变体,其核心原理是通过对特征空间进行随机子采样来增加模型多样性。这种方法特别适合处理高维数据,能有效缓解维度灾难问题,常与决策树、K近邻等对特征选择敏感的基学习器配合使用。在scikit-learn中,可以通过BaggingClassifier和BaggingRegressor实现随机子空间集成,关键参数包括max_features和n_estimators。实际应用中,该方法在金融风控、医疗诊

weixin_33773084的博客 377

linux 内核OOM问题定位-SLAB_DEBUG

首先看slabinfo按的size排序查看 slub object,可以查看 space 总占用大小,及objects个数判断泄漏点 为kmalloc-128。那个泄露多看/sys/kernel/debug的slab/kmalloc*-的alloc-traces和free-traces。SLAB或者SLUB有一个用就可以,改menuconfig没有clean会覆盖之前的更改,现在看一下编译的过程。或者配置defconfig,配置完之后需要clean后再编译。1,配置menuconfig。

陌上花开缓缓归以的博客 899

【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

20 Embedding 与向量检索:语义为什么能变成距离

Embedding 是 RAG 语义检索的数学基础。本文用工程视角讲清文本如何变成向量,为什么语义相近会表现为距离相近,Top-K 检索如何工作,以及在模型选择、同模型约束、精确匹配和召回评估上的常见坑。

老于的笔记的专栏 6203

带你领略Android Jetpack组件的魅力

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

浅谈Android 3万+

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

Android Jetpack架构组件之Navigation入门

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

斌林诚上的博客 4854

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

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

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

JMW1407的博客 1万+

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

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、付费专栏及课程。

余额充值