类似于ios开发中的storyboard,android studio 3.2中引入了可视化导航。之前Activity和Fragment的跳转代码样式化且易出错,通过使用Jetpack中navigation组件,开发人员可以更直观的维护界面跳转。
使用时需要引入依赖
implementation "androidx.navigation:navigation-fragment-ktx:$rootProject.navigationVersion"
implementation "androidx.navigation:navigation-ui-ktx:$rootProject.navigationVersion"
如果需要在xml中定义跳转出入参,需要引入safe args gradle插件,在项目的build.gradle文件中引入依赖
dependencies {
classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:$navigationVersion"
//...
}
然后在模块的build.gradle中应用插件
apply plugin: 'androidx.navigation.safeargs'
navigation组件主要包含3个部分
直接在res/navigation文件夹中新建根标签为navigation的xml文件,在as中打开后就可以进行可视化编辑,对应的编辑会反映在xml文件中。
navigation文件根标签为navigation,其子标签均为destination,比如fragment或者activity,或者子navigation。
根标签声明中应该包含一个初始destination。
<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"
app:startDestination="@+id/home_dest">
navigation>
在design栏可以看到,每个子标签比如fragment有如下属性
<fragment
android:id="@+id/flow_step_two_dest"
android:name="com.example.android.codelabs.navigation.FlowStepFragment"
android:label="Flow step two"
tools:layout="@layout/flow_step_two_fragment">
<argument
android:name="flowStepNumber"
app:argType="integer"
android:defaultValue="2"/>
<action
android:id="@+id/next_action"
app:popUpTo="@id/home_dest">
action>
<deepLink app:uri="www.example.com/{myarg}" />
fragment>
定义了argument标签后,safe args插件会自动生成FlowStepFragmentArgs类,跳转时按照如下方式传入参数
val args = FlowStepFragmentArgs(1)
findNavController().navigate(R.id.flow_step_two_dest, args.toBundle())
针对xml中定义的action,safe args插件会自动生成对应的Directions类,该类有提供便捷的传参方式
view.findViewById<Button>(R.id.navigate_action_button)?.setOnClickListener{
val action = HomeFragmentDirections.nextAction(1)
findNavController().navigate(action)
}
接收方则按照如下方式接收参数
val safeArgs = FlowStepFragmentArgs.fromBundle(arguments)
val flowStepNumber = safeArgs.flowStepNumber
在应用中使用NavGraph时一般搭配NavHostFragment使用,在布局文件中包含该fragment。
<LinearLayout
.../>
<androidx.appcompat.widget.Toolbar
.../>
<fragment
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/mobile_navigation"
app:defaultNavHost="true"
/>
<com.google.android.material.bottomnavigation.BottomNavigationView
.../>
LinearLayout>
其中app:navGraph定义导航资源文件,视图在加载时会自动加载导航资源文件中的startDestination。app:defaultNavHost关联系统的back事件到NavHostFragment。
定义好导航图后,开发者需要在代码中通过NavController控制界面的跳转,NavHostFragment实现了NavHost,可以通过如下方式获取关联的NavController
需要注意的是,上述方法中的fragment和view必须是位于NavHostFragment下,不然没有对应的NavController,调用会报错。
在代码中获取到NavController后就可以调用**navigate()或者popBackStack()**方法进行相应的跳转。
跳转有几种方式,第一种就是直接跳转到graph中定义的destination,比如点击界面中按钮后需要跳转到另外一个fragment
val options = navOptions {
anim {
enter = R.anim.slide_in_right
exit = R.anim.slide_out_left
popEnter = R.anim.slide_in_left
popExit = R.anim.slide_out_right
}
}
view.findViewById<Button>(R.id.navigate_destination_button)?.setOnClickListener {
findNavController().navigate(R.id.flow_step_one_dest, null, options)
}
其中flow_step_one_dest代表Navigation Graph中定义的destination id。 options可选用于转场动画的设置。
第二种跳转方式为通过Navigation Graph – destination – action来定义跳转行为。比如同样的点击按钮跳转
在Navigation Graph中定义的Action如下,通过该方式可以实现在xml中定义跳转的属性,如转场动画等。
<fragment android:id="@+id/home_dest"
...>
<action android:id="@+id/next_action"
app:destination="@+id/flow_step_one"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
在代码中只需要构造根据该Action构造行为即可
view.findViewById<Button>(R.id.navigate_action_button)?.setOnClickListener(
Navigation.createNavigateOnClickListener(R.id.next_action, null)
)
AndroidX库中提供了现成的视图控件与NavController搭配使用,包括ActionBar, BottomNavigationBar, DrawerLayout, NavigationView。两者的关联主要依靠NavigationUI类和navigation-ui-ktx扩展函数,扩展函数其实也是调用NavigationUI的。
菜单与destination关联的判断依据是两者的id需要相同。
点击ActionBar上菜单然后跳转到对应的destination,首先需要destination和res/menu/中菜单的id相同,然后在代码中建立关联
val drawerLayout : DrawerLayout? = findViewById(R.id.drawer_layout)
appBarConfiguration = AppBarConfiguration(
setOf(R.id.home_dest, R.id.deeplink_dest),
drawerLayout)
// 调用AppCompatActivity中定义的扩展函数关联
setupActionBarWithNavController(navController, appBarConfiguration)
处理options menu事件时,优先交给NavController处理
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return item.onNavDestinationSelected(findNavController(R.id.my_nav_host_fragment))
|| super.onOptionsItemSelected(item)
}
要求底部导航栏类型为com.google.android.material.bottomnavigation.BottomNavigationView,与options menu类似,res/menu中的menu id与Navigation Graph中定义的destination的id需要保持一致,然后在AppCompatActivity中关联NavController即可
val bottomNav = findViewById<BottomNavigationView>(R.id.bottom_nav_view)
bottomNav?.setupWithNavController(navController)
DrawerLayout与NavController的关联包含两方面
关联菜单同样类似于options menu,只需要保持id一致然后调用如下关联代码即可
val sideNavView = findViewById<NavigationView>(R.id.nav_view)
sideNavView?.setupWithNavController(navController)
而ActionBar中navigation button的显示则由AppBarConfiguration类控制,该类用于配置ActionBar是否需要支持DrawerLayout以及指定哪些destination为top level。
当支持DrawerLayout时,ActionBar中的Navigation Button位于top level界面时显示drawer图标,其它界面显示返回图标。
如下代码表示ActionBar需要支持DrawerLayout,并且home_dest和deeplink_dest为top level界面。
val drawerLayout : DrawerLayout? = findViewById(R.id.drawer_layout)
appBarConfiguration = AppBarConfiguration(
setOf(R.id.home_dest, R.id.deeplink_dest),
drawerLayout)
// 调用AppCompatActivity中定义的扩展函数关联
setupActionBarWithNavController(navController, appBarConfiguration)
而如下代码则表示ActionBar不需要支持DrawerLayout,当界面位于Navigation Graph的start destination时ActionBar不需要显示Navigation Button,跳转后的界面则显示返回图标。
val drawerLayout : DrawerLayout? = findViewById(R.id.drawer_layout)
appBarConfiguration = AppBarConfiguration(navController.graph)
// 调用AppCompatActivity中定义的扩展函数关联
setupActionBarWithNavController(navController, appBarConfiguration)
针对ActionBar上的返回,通常需要手动控制返回事件
override fun onSupportNavigateUp(): Boolean {
// Allows NavigationUI to support proper up navigation or the drawer layout
// drawer menu, depending on the situation
return findNavController(R.id.my_nav_host_fragment).navigateUp(appBarConfiguration)
}
本文基于官方codelab整理https://codelabs.developers.google.com/codelabs/android-navigation