概述 Navigation是采用一个Activity和多个Fragment形式设计的Ui架构模式,但是众所周知,Fragment的管理一直是个麻烦事,需要通过FragmentManager和FragmentTransaction来管理Fragment之间的切换。所以Google提供了一套Navigation用来管理Fragment相互间的跳转等逻辑。我们先看下Navigation的优势:
处理 Fragment 事务。
默认情况下,正确处理往返操作。
为动画和转换提供标准化资源。
实现和处理深层链接。
包括导航界面模式(例如抽屉式导航栏和底部导航),用户只需完成极少的额外工作。
Safe Args - 可在目标之间导航和传递数据时提供类型安全的 Gradle 插件。
ViewModel支持 - 您可以将ViewModel的范围限定为导航图,以在图表的目标之间共享与界面相关的数据。
正式介绍前,我们需要了解Navigation是由哪几部分组成的,现在我们看一下:
NavHostFragment :一种特殊的Fragment,用于承载导航内容的容器。
Navigation Graph :一个包含所有导航和页面关系相关的 XML资源。
NavController :管理应用导航的对象,实现Fragment之间的跳转等操作。
Navigation使用 Navigation Graph 首先我们在build.gradle 文件中新增navigation的依赖:
1 2 3 implementation("androidx.navigation:navigation-fragment-ktx:$lifecycle_version" ) implementation("androidx.navigation:navigation-ui-ktx:$lifecycle_version" )
然后我们需要新建一个Navigation,点击Project->Res->New Android Resource File,Resource Type选择Navigation,如下图:
打开新建的nav_graph.xml文件,在Design界面可以看到目前还没有内容,可以依次点击[New Destination]图标,然后点击[Create new destination],即可快速创建新的Fragment,这里作次示例,因为本身之前我已经新建好多个Fragment了,就不单独示范了。
我们可以点击视图的小圆点,建议页面的绑定关系。我这里已经建立好多个页面的绑定关系,同时你可以切换至code模块,本人代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <?xml version="1.0" encoding="utf-8"?> <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/jetpack_nav" app:startDestination ="@id/navigationFragment" > <fragment android:id="@+id/navigationFragment" android:name="com.silence.apmprojetct.NavigationFragment" android:label="fragment_navigation" tools:layout="@layout/fragment_navigation"> <action android:id="@+id/action_navigationFragment_to_blankFragment" app:destination="@id/blankFragment" /> <action android:id="@+id/action_navigationFragment_to_threeFragment" app:destination="@+id/threeFragment" /> </fragment > <fragment android:id="@+id/blankFragment" android:name="com.silence.apmprojetct.SecondFragment" android:label="fragment_blank" tools:layout="@layout/fragment_second"> <action android:id="@+id/action_blankFragment_to_threeFragment" app:destination="@id/threeFragment" /> </fragment > <fragment android:id="@+id/threeFragment" android:name="com.silence.apmprojetct.ThreeFragment" android:label="fragment_three" tools:layout="@layout/fragment_three"> <action android:id="@+id/action_threeFragment_to_navigationFragment" app:destination="@+id/navigationFragment" app:enterAnim="@anim/nav_default_enter_anim" app:exitAnim="@anim/nav_default_exit_anim" app:popEnterAnim="@anim/nav_default_pop_enter_anim" app:popExitAnim="@anim/nav_default_pop_exit_anim" /> </fragment > </navigation >
我们看下几个标签的含义:
navigation:根标签,通过startDestination配置指定默认的启动页面。
fragment: fragment标签代表一个fragment。
action:action标签定义了页面跳转的行为,destination标签定义跳转的目标页,里面还有启动模式的相关设置等等。
NavHostFragment 而讲到这里,我们只概括前面三个模块其中之一Navigation Graph。我们知道Fragment需要一个Activity才能正常运行,但是我们如何在我们的Activity指定我们上面startDestination呢。这就需要用到了NavHostFragment。我们看下我们Activity的布局代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:tools ="http://schemas.android.com/tools" android:layout_width ="match_parent" android:layout_height ="match_parent" xmlns:app ="http://schemas.android.com/apk/res-auto" tools:context =".NavigationActivity" > <fragment android:id="@+id/fragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:navGraph="@navigation/jetpack_nav" /> </FrameLayout >
我们可以看到布局里定义了一个Fragment,并且它使用的就是NavHostFragment,可以看到navGraph属性绑定了我们刚刚写的Navigation文件。而defaultNavHost就是做到了自动实现了页面间的返回操作。如果没有这个属性,点击返回的时候就是直接关闭当前Activity了。当然,其内部Fragment的点击事件我们可以自己控制。
然后我们可以运行程序,可以看到默认展示的是NavigationFragment页面,这是因为NavigationActivity的布局文件中配置了NavHostFragment,并且给NavHostFragment指定了默认展示的页面为NavigationFragment。
NavController NavController主要用来管理fragment之间的跳转,每个NavHost均有自己的相应NavController。然后可以使用它的navigate或者navigateUp方法来进行页面之间的路由操作。比如我这边的NavigationFragment需要跳转SecondFragment。我们可以这么写:
1 2 3 4 5 6 7 8 9 10 override fun onCreateView ( inflater: LayoutInflater , container: ViewGroup ?, savedInstanceState: Bundle ? ) : View? { val view = inflater.inflate(R.layout.fragment_navigation, container, false ) view.findViewById<TextView>(R.id.next).setOnClickListener { findNavController().navigate(R.id.action_navigationFragment_to_blankFragment) } return view }
也就是通过navigate指向前面我们在action定义的id。即可完成跳转。
参数传递 Bundle数据传递 我们可以创建一个Bundle对象,然后使用navigate()将它传递给目的地,代码如下:
1 2 3 val bundle = Bundle()bundle.putString(TITLE, getString(R.string.three_fragment_navigation_bundle_label)) findNavController().navigate(R.id.action_blankFragment_to_threeFragment, bundle)
使用 Safe Args 传递安全的数据 这是官方为了navigation数据传递安全提供的一个数据传递方式,首先我们需要在根目录的build.gradle文件中添加如下依赖:
1 2 3 4 dependencies { def nav_version = "2.3.3" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" }
然后我们在模块的build.gradle文件中新增如下:
1 2 3 4 apply plugin: "androidx.navigation.safeargs.kotlin" apply plugin: 'androidx.navigation.safeargs'
然后我们ReBuild项目,可以看到在build目录中生成了如下文件:
然后我们修改我们的代码,比如我需要在ThreeFragment页面新增一个参数,那么我们需要在navigation文件中新增如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 <fragment android:id="@+id/threeFragment" android:name="com.silence.apmprojetct.ThreeFragment" android:label="fragment_three" tools:layout="@layout/fragment_three"> <action android:id="@+id/action_threeFragment_to_navigationFragment" app:destination="@+id/navigationFragment" /> <argument android:name="key" app:argType="string" android:defaultValue="safeArgs" /> </fragment >
然后我们在需要跳转的时候修改下代码,比如我这里SecondFragment会跳转至ThreeFragment。那么我们只需要这么写:
1 2 3 4 5 6 7 8 view.findViewById<TextView>(R.id.next).setOnClickListener { var secondFragment = SecondFragmentDirections.actionBlankFragmentToThreeFragment("safe传递的数据" ) findNavController().navigate(secondFragment) }
我们在看下如何在ThreeFragment里接收数据:
1 2 3 view.findViewById<TextView>(R.id.title).text = arguments?.let { ThreeFragmentArgs.fromBundle(it).key }
其他场景 堆栈处理 前面我们说到了正常的页面启动,以及如何挟带参数跳转。现在有个场景,比如我A-》B-》C之后,然后直接回到A。并且清栈,也就是在A页面返回就直接关闭当前的Activity了。我们看一下,目前我们的代码执行后效果会如何:
可以看出来我们每次打开新的fragment就相当于新开了一个堆栈。我们可记得我们配置页面的时候有一个action属性。我们把action的代码改成如下:
1 2 3 4 5 6 7 8 9 10 11 <fragment android:id="@+id/threeFragment" android:name="com.silence.apmprojetct.ThreeFragment" android:label="fragment_three" tools:layout="@layout/fragment_three"> <action android:id="@+id/action_threeFragment_to_navigationFragment" app:destination="@+id/navigationFragment" app:popUpTo="@+id/navigationFragment" app:popUpToInclusive="true" /> </fragment >
我们运行一下,在看一下效果:
哎,可以了。
其实action还有一个属性launchSingleTop的设置,这个看具体场景使用即可。
指定startDestination 我们想一下,如何指定startDestination的Fragment呢。上面介绍过,activity要绑定NavHostFragment。而NavHostFragment要指定startDestination。在我们在navigaiton指定了fragment后,可以修改么。或者说,我可以动态改变他的startDestination嘛。其实是可以的。我们知道startDestination是由navGraph指定的。那么我们只需要修改activity的代码,就可以做到了。具体示例如下:
1 2 3 4 5 6 7 8 9 10 override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_navigation) val startDestinationID = R.id.blankFragment val navController = Navigation.findNavController(this , R.id.fragment) val navInflater = navController.navInflater val navGraph = navInflater.inflate(R.navigation.jetpack_nav) navGraph.startDestination = startDestinationID navController.graph = navGraph }
那我们执行一下,看下效果:
我这边指定了SecondFragment成功了。那具体指定哪一个,由你自己的业务场景而定。
总结 本文我们主要介绍了Jetpack中的导航组件Navigation的组件,从普通的跳转,已经挟带参数跳转,甚至一些特殊场景的跳转逻辑。关于一些其他场景,我这里不过多介绍,因为目前正常的跳转场景基本已经了解了。如果你想知道更多的使用场景,请查看官方文档 。