Android Jetpack - Navigation 组件:进行应用程序导航

1. Navigation 组件的介绍

1.1 什么是 Navigation 组件

Navigation 组件是一种 Android Jetpack 库,它可以帮助开发者轻松地实现应用程序中的导航功能。导航组件包含多个类和组件,包括导航图、目的地、导航控制器等,可以帮助我们管理应用程序中的页面导航和任务导航。通过使用 Navigation 组件,我们可以更加方便地实现应用程序的导航功能,同时也可以提高应用程序的用户体验。在本篇文章中,我们将介绍如何使用 Navigation 组件来实现应用程序导航,并提供一些示例和更多的扩展功能。

1.2 Navigation 组件的优势
• 轻松实现应用程序中的导航,包括页面之间的转换和应用程序内部的导航。
• 提高应用程序的可维护性和可扩展性,因为它们使得应用程序的结构更加清晰,并且可以更容易地添加新的功能和页面。
• 可以提供一致的用户体验,因为它们使用了标准的导航模式和动画效果。
• 可以帮助开发人员更快地构建应用程序,因为它们提供了许多常见的导航模式和功能,可以直接使用或进行修改。
• 可以提高应用程序的可测试性,因为它们使得页面之间的导航和状态转换更加明确和可控。
1.3 Navigation 组件的组成
• NavHost:用来嵌入导航流程的容器,一般使用FragmentContainerView或者Fragment。
• NavController:负责在NavHost内部处理导航事务的控制器,用于执行页面跳转、管理返回栈等。
• NavGraph:描述Fragment之间导航关系的资源文件,在其中定义页面之间的转跳、动画等。一般放在res/navigation/目录下。

简而言之,Navigation组件通过在NavHost中使用NavGraph来描述Fragment导航路径与关系,然后由NavController来执行实际的导航工作,这样极大地简化了以往的页面跳转逻辑和回退栈管理流程。

2. Navigation 组件的基本使用

2.1 添加导航组件到项目中

在项目的 build.gradle 文件中添加以下依赖:

dependencies {
    def nav_version = "2.5.3"
    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
}

在布局文件中添加 NavHostFragment:

<androidx.fragment.app.FragmentContainerView
    android:id="@+id/nav_host_fragment"
    android:name="androidx.navigation.fragment.NavHostFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:navGraph="@navigation/nav_graph" />

正确获取 NavController 对象 :
在 Activity 内使用 NavController 时,应在onCreate()中获取:

val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
NavigationUI.setupActionBarWithNavController(this, navController)

而在 Fragment 内,应在onAttach()或onViewCreate()中获取:

val navHostFragment = parentFragment as NavHostFragment
val navController = navHostFragment.navController

NavHost需要指定app:navGraph属性来关联一个导航图NavGraph,这决定了其中Fragment页面之间的导航关系和跳转路径。

NavController是 Navigation组件的控制中心,用于在NavHost内执行导航操作。可以在Activity或Fragment中通过NavHostFragment的navController属性获取对应的NavController实例。
常见的导航操作有:

• 导航到目标目的地:navController.navigate(R.id.destination_id) 
• 回退一个目的地:navController.navigateUp() 或 navController.popBackStack() 
• 回退到根目的地:navController.popBackStack(R.id.root_destination, false)

NavController还负责维护Fragment的回退栈,以及在按返回按钮时正确出栈,这大大简化了之前管理Fragment事务的复杂度。 通过NavHost和NavController的配合,Navigation组件实现了在NavGraph中声明的导航逻辑和页面切换功能。这使Fragment之间的导航变得极为简单高效。开发者只需关注于定义NavGraph,并调用NavController中的导航方法即可实现页面跳转,其余的一切尽在Navigation组件的掌控之中。

2.2 创建导航图

在res文件夹下创建一个navigation文件夹,然后在该文件夹下创建一个nav_graph.xml文件,用于定义导航图的结构和内容:

<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/firstFragment">

    <fragment
        android:id="@+id/firstFragment"
        android:name="com.smallmarker.jetpackpractice.navigation.fragment.FirstFragment"
        android:label="First"
        tools:layout="@layout/fragment_first">
        <action
            android:id="@+id/action_firstFragment_to_secondFragment"
            app:destination="@+id/secondFragment" />
    </fragment>

    <fragment
        android:id="@+id/secondFragment"
        android:name="com.smallmarker.jetpackpractice.navigation.fragment.SecondFragment"
        android:label="Second"
        tools:layout="@layout/fragment_second" />

</navigation>

在导航图中,每个标签如下:

<navigation>:必须指定一个startDestination表示整个页面的起始Fragment。
<fragment>:用于定义目的地。现在表示这是个Fragment,当然这个还可以用其他标签。其中android:id指定目的地唯一标识符,android:name指定目的地全路径类名,android:label指定目的地在应用程序中显示的标签名称。
<action>:就是定义的动作,其中android:id指定动作唯一标识符,app:destination指定动作要执行的目的地的id。

navigation创建好了,就要和Activity进行关联了,有两种方式,第一种是在xml里关联,一种是在代码里关联。这里先说第一种在XML文件中创建导航图。
首先在Activity的xml中添加FragmentContainerView标签

<androidx.fragment.app.FragmentContainerView
    android:id="@+id/nav_host_fragment"
    android:name="androidx.navigation.fragment.NavHostFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:navGraph="@navigation/nav_graph" />

其中android:name是写死的,指定整个navigation的hostFragment;navGraph指定的是在navigation中写的xml。
添加FramgmentContainerView标签有时候会出错,这时只需将FragementContainerView换为Fragment即可。

2.3 使用

Navigation使用很简单,navigation和activity(确切的说是Fragment)绑定之后,使用两个方法就行,一个是navigate,就是跳转,一个是navigateUp,就是返回。
如果想要跳转到新页面时,在Fragment中使用:

NavHostFragment.findNavController(this).navigate(destinationID, bundle);

this是当前的fragment,destinationID就是要跳转的action id,这个action对应的fragment一定得是当前的Fragment,bundle是带过去的参数,内部是调用的setArguments。
使用navigation默认是会添加到返回栈的,如果想要返回上一级,使用:

NavHostFragment.findNavController(this).navigateUp();

navigation pop和push的时候对Fragment的操作是replace,所以Fragment生命周期会重新走一遍。

3. Navigation组件的进阶使用

3.1 动态改变startDestination

有时候我们的起始Fragment是动态的,并不一定是xml中指定的 startDestination,那么这个时候就需要去动态指定startDestination。startDestination对应的其实就是在navigation中定义的 fragment的id。
第一步: 先确定需要跳哪个Fragment,就是先确定一个startDestinationID
第二步: 然后通过Navigation.findNavController(this, R.id.nav_host_fragment) 找到 NavController 实例,R.id.nav_host_fragment 这个id是activity中指定的fragment的id,NavController可以通过Fragment拿到,两者有着很密切的联系,而且NavController是在push和pop时使用最多的类。
第三步:通过 NavController 得到NavInflater实例,然后将我们在 navigation里写的xml文件加载出来,有点类似于layout的加载,然后就可以给NavGraph设置startDestinationID了, 然后再把navGraph 和要传的参数设置给navController。

Bundle args = new Bundle();
int startDestinationID; // 起始Fragment
int partnerType; // 业务Type
switch (mType) {
 ...
 default:
 partnerType = mType;
 startDestinationID = R.id.inputPhoneFragment;
 }
args.putInt(Const.BundleKey.TYPE, partnerType);
args.putParcelable(Const.BundleKey.OBJECT, mMiddlewareModel);
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavInflater navInflater = navController.getNavInflater();
NavGraph navGraph = navInflater.inflate(R.navigation.login_nav_graph);
navGraph.setStartDestination(startDestinationID);
navController.setGraph(navGraph, args); 

这里的第二步和第三步,也是activity关联navigation的方法。
通过这种方法,不仅可以动态的设置startDestination 也可以动态的设置启动参数。

3.2 切换时使Fragment保存状态

当我们使用NavHostFragment 的时候通过 NavHostFragment.findNavController 拿到的是 NavController,navigation 和navigationUp就是NavController 中的方法,但是具体的路由实现是由一个个navigator完成的
在源码中给提供了这些navigator
在这里插入图片描述
我们这里只用到了FragmentNavigator 其他的再研究,其中KeepStateFragmentNavigator就是我们自定义的。
首先看一下FragmentNavigator 在切换时为什么不能保存状态,源码是这样的:

public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
          @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
     ...
      ft.replace(mContainerId, frag);
      ft.setPrimaryNavigationFragment(frag);
     ...
      ft.setReorderingAllowed(true);
      ft.commit();
      // The commit succeeded, update our view of the world
      if (isAdded) {
          mBackStack.add(destId);
          return destination;
      } else {
          return null;
      }
  }

在进行跳转时 直接使用了replace,所以导致当前页面会调用 onDestroyView,即fragment变为 inactive,当进行pop操作时,fragment重新进入 active状态时,会重新调用 onViewCreated 等方法,导致页面重新绘制,其实在这种情况下,我们可以直接用ViewModel和LiveData对数据进行保存,但是这次想尝试一下新的解决办法。在知道原因后就好办了,直接继承FragmentNavigator 把方法重写了不就行了,我确实也是这样做的。
上代码:

@Navigator.Name("keepFragment")
public class KeepStateFragmentNavigator extends FragmentNavigator {
    private static final String TAG = "KeepStateFragmentNavigator";
    private ArrayDeque<Integer> mBackStack = new ArrayDeque<>();
    private final FragmentManager mFragmentManager;
    private final int mContainerId;
    private Context mContext;
    public KeepStateFragmentNavigator(@NonNull Context context, @NonNull FragmentManager manager, int containerId) {
        super(context, manager, containerId);
        mFragmentManager = manager;
        mContainerId = containerId;
        mContext = context;
    }

    @Override
    public void onRestoreState(@Nullable Bundle savedState) {
        if (savedState != null) {
            int[] backStack = savedState.getIntArray("androidx-nav-fragment:navigator:backStackIds");
            if (backStack != null) {
                mBackStack.clear();
                for (int destId : backStack) {
                    mBackStack.add(destId);
                }
            }
        }
    }

    @Override
    @Nullable
    public Bundle onSaveState() {
        Bundle b = new Bundle();
        int[] backStack = new int[mBackStack.size()];
        int index = 0;
        for (Integer id : mBackStack) {
            backStack[index++] = id;
        }
        b.putIntArray("androidx-nav-fragment:navigator:backStackIds", backStack);
        return b;
    }


    @SuppressLint("LongLogTag")
    @Override
    public boolean popBackStack() {
        if (mBackStack.isEmpty()) {
            return false;
        }
        if (mFragmentManager.isStateSaved()) {
            Log.i(TAG, "Ignoring popBackStack() call: FragmentManager has already" + " saved its state");
            return false;
        }
        mFragmentManager.popBackStack(
                generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
                FragmentManager.POP_BACK_STACK_INCLUSIVE);
        mBackStack.removeLast();
        return true;
    }

    @SuppressLint("LongLogTag")
    @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 tag = String.valueOf(destination.getId());
        String className = destination.getClassName();
        if (className.charAt(0) == '.') {
            className = mContext.getPackageName() + className;
        }
        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);
        }
        final Fragment currentFragment = mFragmentManager.getPrimaryNavigationFragment();
        if (currentFragment != null) {
            ft.hide(currentFragment);
        }
        Fragment frag = mFragmentManager.findFragmentByTag(tag);
        if (frag == null) {
            frag = mFragmentManager.getFragmentFactory().instantiate(mContext.getClassLoader(), className);
            frag.setArguments(args);
            ft.add(mContainerId, frag, tag);
        } else {
            ft.show(frag);
        }
        ft.setPrimaryNavigationFragment(frag);
        final @IdRes int destId = destination.getId();
        final boolean initialNavigation = mBackStack.isEmpty();
        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.commitAllowingStateLoss();
        // The commit succeeded, update our view of the world
        if (isAdded) {
            mBackStack.add(destId);
            return destination;
        } else {
            return null;
        }
    }

    @NonNull
    private String generateBackStackName(int backStackIndex, int destId) {
        return backStackIndex + "-" + destId;
    }
}

代码有点长,其实有用的只有 fragment处理的那一部分,这里大部分代码都是在处理返回栈,其实可以直接通过反射,拿到父类的mBackStack,只重写navigate 方法就行。
在自己做项目的时候走了弯路,参考了NavHostFragment,发现了这个方法:

@Deprecated
 @NonNull
 protected Navigator<? extends FragmentNavigator.Destination> createFragmentNavigator() {
 	return new FragmentNavigator(requireContext(), getChildFragmentManager(), getContainerId());
 }

于是就继承了NavHostFragment 写了一个

public class KeepStateNavHostFragment extends NavHostFragment {

	@NonNull
	@Override
	protected Navigator<? extends FragmentNavigator.Destination> createFragmentNavigator() {
		return new KeepStateFragmentNavigator(requireContext(), getChildFragmentManager(), getContainerId());
	}protected int getContainerId() {
		int id = getId();
		if (id != 0 && id != View.NO_ID) {
		return id;
	}
	// Fallback to using our own ID if this Fragment wasn't added via
	// add(containerViewId, Fragment)
	return R.id.nav_host_fragment_container;
	}
}

然在xml中 把 NavHostFragment 换成KeepStateNavHostFragment

<fragment
	android:id="@+id/nav_host_fragment"
	android:name="com.XXX.XXX.KeepStateNavHostFragment"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	app:navGraph="@navigation/login_nav_graph"
	app:defaultNavHost="true" />

在路由的时候这样使用即可,就是把NavHostFragment 换成KeepStateNavHostFragment

KeepStateNavHostFragment.findNavController(this).navigate(destinationID, bundle);

代码很简单,但是还有一个比较简单的方法可以通过NavController来添加任何navigation,这个方法就比较简单明了,注意KeepStateFragmentNavigator构造方法中的参数,第一个是Activity,第二个是hostFragment的childFragment,这个一定不能是activity的,否则会有bug,第三个则是Fragment 的id。
使用方法和默认的navigation一致

NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
Fragment navHostFragment = getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
navController.getNavigatorProvider().addNavigator(new KeepStateFragmentNavigator(this, navHostFragment.getChildFragmentManager(), R.id.nav_host_fragment));
NavHostFragment.findNavController(this).navigate(destinationID, bundle);

4. Navigation 组件的高级使用

4.1 深层链接

在Android中,深层链接是指将用户直接转到应用内特定目的地的链接。借助Navigation组件,你可以创建两种不同类型的深层链接:显式深层链接和隐式深层链接。
创建显式深层链接:
显式深层链接是深层链接的一个实例,该实例使用 PendingIntent 将用户转到应用内的特定位置。例如,你可以在通知或应用 widget 中显示显式深层链接。

    val pendingIntent = NavDeepLinkBuilder(it)
        .setGraph(R.navigation.nav_deep_link)
        .setDestination(R.id.deepLinkFragment)
        .setArguments(
            Bundle().apply {
                putInt("id", 1)
            }
        )
        .setComponentName(DeepLinkActivity::class.java)
        .createPendingIntent()
    val notification = NotificationCompat.Builder(it, "my_channel")
        .setContentTitle("Title")
        .setContentText("测试深层链接")
        .setSmallIcon(R.mipmap.ic_launcher)
        .setContentIntent(pendingIntent)
        .build()
    NotificationManagerCompat.from(it).notify(Random.nextInt(10), notification)

该示例使用 NavDeepLinkBuilder 类构造 PendingIntent, 添加到通知中并发送,点击通知跳转指定页面。
创建隐式深层链接:

<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_deep_link"
    app:startDestination="@id/deepLinkFragment">
  
    <fragment
        android:id="@+id/deepLinkFragment"
		android:name="com.smallmarker.jetpackpractice.navigation.fragment.DeepLinkFragment"
        android:label="DeepLink"
        tools:layout="@layout/fragment_deep_link">
  
        <deepLink app:uri="example://deepLink/{id}" />
    </fragment>
</navigation>

如需启用隐式深层链接,您还必须向应用的 manifest.xml 文件中添加内容。将一个 元素添加到指向现有导航图的 activity,如以下示例所示。

<activity
    android:name=".navigation.DeepLinkActivity"
    android:exported="true">
    <nav-graph android:value="@navigation/nav_deep_link" />
</activity>

在这个例子中,我们定义了一个深度链接,它的URI是"example://deepLink/{id}",其中{itemId}是一个参数。当用户在浏览器或其他应用中点击这个链接时,Android系统会自动打开我们的应用,并跳转到对应的页面,同时将参数传递给我们的应用。我们可以在目标页面中通过arguments来获取这个参数。

4.2 共享元素转场

共享元素转场可以实现在不同Activity或Fragment之间共享相同元素的动画效果,比如在列表页面点击某个item进入详情页面时,可以让这个item的图片或文字在两个页面之间平滑地过渡。以下是一个简单的实现示例:

  <!-- 在layout文件中定义共享元素的id -->
  <ImageView 
    android:id="@+id/image_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:transitionName="shared_element" />

到Fragment目的地的共享元素过渡

val extras = FragmentNavigatorExtras(view1 to "shared_element")
view.findNavController().navigate(R.id.confirmationAction, null, null, extras)

到Activity目的地的共享元素过渡

val option = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, imageView, "shared_element")
findNavController().navigate(R.id.shareElementDialog, null, null, ActivityNavigatorExtras(option))

共享元素以程序化方式提供,而不是通过导航XML文件提供。activity和fragment目的地各自都有Navigator.Extras接口的一个子类,它接受导航的附加选项,包括共享元素。你可以在调用navigate()时传递这些 Extras。

4.3 导航图的动态构建

动态构建导航图可以在运行时根据不同的条件创建不同的导航图,例如用户登录状态不同、权限不同等情况下展示不同的导航结构。
下面是一个简单的动态构建导航图的示例:

  val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
  val navController = navHostFragment.navController
  val graph = navInflater.inflate(R.navigation.dynamic_nav_graph)
  if (isLoggedIn) {
      graph.startDestination = R.id.homeFragment
  } else {
      graph.startDestination = R.id.loginFragment
  }
  if (hasAdminPermissions) {
      val adminNode = NavGraphNavigator(navController.navigatorProvider.getNavigator(NavGraphNavigator::class.java))
          .createDestination()
      adminNode.id = R.id.adminFragment
      adminNode.setClassName("com.example.app.AdminFragment")
      graph.addDestination(adminNode)
      graph.addEdge(R.id.homeFragment, R.id.adminFragment)
  }
  navController.graph = graph

在上面的示例中,我们首先获取到了当前的NavController和NavInflater,然后通过NavInflater.inflate方法来加载我们的动态导航图。接着,我们根据不同的条件设置了导航图的起始目的地,并且在有管理员权限的情况下动态添加了一个目的地,并且添加了一条边来连接这个目的地和主页。最后,我们将构建好的导航图设置到NavController中即可。

5. 导航组件的最佳实践

5.1 使用标签

为每个模块定义单独的NavGraph。在大型项目中,最好为每个功能模块定义自己的NavGraph,然后在根NavGraph中使用标签将每个模块的NavGraph组合起来:

  <navigation 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/root_navigation">
  
    <include 
        android:id="@+id/home_navigation"
        app:layout="@navigation/home_navigation" />
        
    <include 
        android:id="@+id/profile_navigation"
        app:layout="@navigation/profile_navigation" />  
  </navigation>
5.2 使用 ViewModel 和 LiveData

在ViewModel中使用LiveData对象来处理导航事件:

class MainViewModel : ViewModel() {

    private val navigateTo = MutableLiveData<NavDirections>()

    fun getNavigateTo(): LiveData<NavDirections> {
        return navigateTo
    }

    fun setNavigateTo(directions: NavDirections) {
        navigateTo.value = directions
    }
}

在 Fragment 中观察 LiveData 对象并处理导航事件:

    class MainFragment : Fragment() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            viewModel.getNavigateTo().observe(this) {
                Navigation.findNavController(requireView()).navigate(it)
            }
        }

        fun clickJump() {
            viewModel.setNavigateTo(MainFragmentDirections.actionMainToNavigationActivity())
        }
    }

注意这里我们使用了Safe Args实现类型安全的导航,在目的地之间导航,官方也是建议使用Safe Args Gradle插件。此插件可生成简单的对象和构建器类,以便在目的地之间实现类型安全的导航。我们强烈建议你在导航以及在目的地之间传递数据时使用Safe Args。 如需将Safe Args添加到你的项目,请在顶层 build.gradle 文件中包含以下 classpath:

buildscript {
    dependencies {
        def nav_version = "2.5.3"
        classpath("androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version")
    }
}

然后再将以下行添加到应用或模块的build.gradle文件中:

plugins {
    id("androidx.navigation.safeargs.kotlin")
}

6. 总结

6.1 导航组件的优势和适用场景:
优势适用场景
提供一致的导航体验需要在应用中引入多个页面的场景
简化导航逻辑需要在应用中进行复杂的导航操作的场景
可自定义外观和行为需要根据应用需求自定义导航栏的场景
支持深层链接需要在应用中支持深层链接的场景
5.2 导航组件的最佳实践

在使用导航组件时,应该尽量减少手动操作Fragment事务,而是使用导航组件提供的API进行操作,以避免出现不必要的错误。

• 在设计导航图时,应该尽量将功能相似的页面放在同一个导航图中,以便于管理和维护。
• 在使用Safe Args插件传递参数时,应该尽量使用安全的类型,以避免出现类型转换错误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值