一 Navigation主要元素
- Navigation Graph 一种xml资源文件,包含应用程序的所有页面以及页面之间的关系。
- NavHostFragment 一种特殊的Fragment,Navigation Graph中的Fragment正是通过这个特殊的Fragment启动的。
- NavController 导航控制器,主要用来完成页面切换工作。
二 使用Navigation
2.1 添加依赖
Navigation需要依赖几个支持库,所以在使用Navigation之前需要在gradle文件中导入相关库:
implementation "androidx.navigation:navigation-fragment-ktx:2.3.2"
implementation "androidx.navigation:navigation-ui-ktx:2.3.2"
如果要在导航页面之前传递类型安全的参数,需要添加Safe Args插件并且配置classpath:
id 'androidx.navigation.safeargs'
//id 'androidx.navigation.safeargs.kotlin'
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.2"
2.2 创建导航图
Res右键New Resource File创建一个资源文件,类型名字写成nav_graph,类型选择Navigation
就会创建一个导航图文件,里边包含以下内容:
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph">
</navigation>
2.3 添加NavHostFragment
NavHostFragment是一个特殊的Fragment,需要添加到Activity的布局文件中。
<androidx.fragment.app.FragmentContainerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph"/>
- name属性指定NavHost的实现类,这里是NavHostFragment。
- defaultNavHost=“true” 拦截系统返回键,当用户按下返回键时系统会将正在展示的Fragment返回。
- navGraph属性 关联导航图。
2.4 为导航图添加目的地
首先需要创建两个Fragment,FirstFragment和SecondFragment,并且将这两个Fragment关联到我们的导航文件中。
<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/fragmentFirst">
<fragment
android:id="@+id/fragmentFirst"
android:name="com.example.navigationtest.FirstFragment"
android:label="fragment_first"
tools:layout="@layout/fragment_first"/>
<fragment
android:id="@+id/fragmentSecond"
android:name="com.example.navigationtest.SecondFragment"
android:label="fragment_second"
tools:layout="@layout/fragment_second"/>
</navigation>
- id 必填项,后面fragment跳转的时候要用到
- name Fragment的全路径
- layout Fragment的布局
2.5 添加跳转action
为了达到跳转的目的,需要为fragment添加用于跳转的action。
<action
android:id="@+id/first_to_second"
app:destination="@id/fragmentSecond"/>
其中,destination指定了要跳转到的fragment,这也就是为什么在导航文件中创建fragment时要指定id的原因。定义好action之后,在FirstFragment中添加跳转的点击事件,使用Navigation将页面导航到SecondFragment中:
val view=inflater.inflate(R.layout.fragment_first, container, false)
val binding = FragmentFirstBinding.bind(view)
binding.btnFirst.setOnClickListener {
Navigation.findNavController(view!!).navigate(R.id.first_to_second)
}
navigate方法接收的参数就是前面定义的action。
2.6 添加转场动画
刚刚创建的跳转过程默认是没有动画效果的,现在把转场动画效果加上。修改导航文件中的action,添加动画属性:
<action
android:id="@+id/first_to_second"
app:destination="@id/fragmentSecond"
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"/>
运行程序,切换页面时就可以看到动画效果。
2.7 使用NavigationUI
2.7.1 NavigationUI的意义
在Navigation组件中,导航图是很重要的一部分,可以帮助我们了解页面之间的关系,并且快速完成页面间的切换,而在页面切换的过程中还伴随着菜单栏的变化。对于不同的页面,AppBar中的menu菜单很可能是不一样的。并且AppBar中的按钮和菜单同样可能承担着页面切换的工作,为了统一管理,JetPack引入了NavigationUI组件来将AppBar与Navigation中的导航图关联到一起。
2.7.2 NavigationUI的使用分析
- 首先需要创建一个settingFragment并且关联到导航图文件中
<fragment
android:id="@+id/fragmentSetting"
android:name="com.example.navigationtest.SettingFragment"
tools:layout="@layout/fragment_setting"
android:label="fragment_setting"/>
- 创建菜单文件menu_settings.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@id/fragmentSetting"
android:icon="@drawable/ic_launcher_background"
android:title="设置" />
</menu>
item的id需要和导航文件中的settingFragment保持一致,否则会无法跳转到settingFragment。
- 在activity中实现跳转
class NavigationActivity : AppCompatActivity() {
private var navController: NavController? =null
private var appBarConfig:AppBarConfiguration?=null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityNavigationBinding.inflate(layoutInflater)
setContentView(binding.root)
// navController = Navigation.findNavController(this@NavigationActivity,R.id.nav_host_fragment)
val navHost = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)
navController = navHost!!.findNavController()
appBarConfig = AppBarConfiguration(navController!!.graph)
NavigationUI.setupActionBarWithNavController(this, navController!!, appBarConfig!!)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.menu_settings,menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return NavigationUI.onNavDestinationSelected(item,navController!!)||super.onOptionsItemSelected(item)
}
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(navController!!,appBarConfig!!)||super.onSupportNavigateUp()
}
}
AppBarConfiguration用于AppBar的配置,NavController用于页面导航。通过调用NavigationUI.setupActionBarWithNavController
将NavigationUI、AppBarConfiguration以及NavController绑定到一起,最后通过onOptionsItemSelected
完成菜单项的点击跳转。后面如果需要增加菜单项,只需要添加对应的fragment然后修改菜单文件即可。
2.7.3 监听页面切换
如果想要在页面切换的时候做一些其他操作,可以使用NavController提供的addOnDestinationChangedListener
方法监听切换过程:
navController!!.addOnDestinationChangedListener { _, _, _ ->
Toast.makeText(this@NavigationActivity, "完成页面切换", Toast.LENGTH_LONG).show()
}
2.8 传递参数
2.8.1 使用Bundle传递参数
这种方式属于传统方式传参,需要把用到的参数按照键值对的方式定义好,在跳转的时候将Bundle作为参数传递过去,然后在另一个页面接收:
//传参
val binding = FragmentFirstBinding.bind(view)
binding.btnFirst.setOnClickListener {
val bundle = bundleOf("name" to "yang","age" to 32)
Navigation.findNavController(view!!).navigate(R.id.first_to_second,bundle)
}
//接收参数
val name=arguments?.getString("name")
binding.tvName.text = name
2.8.2 使用SafeArgs插件传参
使用SafeArgs插件之前需要先在gradle文件中配置该插件:
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.2"
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'androidx.navigation.safeargs'
// id 'androidx.navigation.safeargs.kotlin'
}
插件导入成功之后就可以定义参数了,在导航文件中为要传递参数的fragment中添加两个argument标签,用来定义参数:
<argument
android:name="name"
android:defaultValue=""
app:argType="string" />
<argument
android:name="age"
android:defaultValue="0"
app:argType="integer" />
在FirstFragment中传递参数,有两种方式传递,Bundle和Directions,先来卡一下使用Bundle的方式:
Bundle传参
val bundle = FirstFragmentArgs.Builder().setName("yang").setAge(32).build().toBundle()
Navigation.findNavController(view!!).navigate(R.id.first_to_second,bundle)
这里和传统使用Bundle方式的区别就是生成Bundle的方式不一样了,当引用safeargs插件之后就会为每个Fragment自动生一个带有Args后缀的类,里边已经为我们定义的name和age两个参数创建了set方法。
Directions传参
val firstFragmentDirections = FirstFragmentDirections.firstToSecond().setName("yang").setAge(32)
Navigation.findNavController(view!!).navigate(firstFragmentDirections)
SafeArgs会自动为每个目的地创建一个Directions类,firstToSecond(),这个方法返回一个FirstFragmentDirections对象,对象内部已经实现了name和age字段的get/set方法,调用set方法即可完成参数的创建,最后将firstFragmentDirections作为navigate()方法的参数即可。
接收参数
接收参数统一使用前文介绍的接收方式即可:
val name=arguments?.getString("name")
binding.tvName.text = name
三 结语
关于Navigation的基本使用介绍到这里,下一篇文章介绍Navigation中另外一个比较重要的知识点DeepLink的用法。