在日常开发,尤其是大型项目中,越来越多地使用Activity嵌套多个Fragment的UI模式开发,但对于Fragment的生命周期、隐藏显示、动画控制和切换传值等都是比较麻烦的一件事。通常做法是通过FragmentManager来管理,这种方式代码冗余,不易维护。
Jetpack为此提供了Navigation组件,用来帮助开发者更高效简单地开发和维护Fragment。Navigation组件可以类似xml布局一样可视化编程,开发更加简单便捷,提供了一些属性来实现Fragment之间的跳转等操作,还提供了Fragment之间跳转的动画和传参的能力,除此之外还支持deeplink。
Navigation主要组成
- Navigation Graph:一种新的xml文件,内部定义Fragment及他们之间的关系;
- NavHostFragment:一种特殊的Fragment,根据名称可以看出是一种Fragment的容器,Navigation Graph中定义的Fragment正是通过NavHostFragment来展示;
- NavController:是一个Java类,用来实现Fragment之间的跳转等操作。
使用
1、创建Navigation Graph
Navigation Graph是一种xml文件,也属于res资源的一种,所以新建Navigation Graph的时候需要在res目录下操作:
res目录下点击New,选择Android Resource,Resource Type选择Navigation,为文件命名,点击ok就可以创建一个Navigation Graph文件。创建好文件后打开如下图 :
2、创建NavHostFragment
我们知道,使用Navigation来操作Fragment,需要为它们定义一个容器Fragment,叫做NavHostFragment,它是Jetpack提供的一个类,我们在Activity的xml布局中直接使用就可以,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".NavigationActivity">
<fragment
android:id="@+id/nav_host"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/navigation_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
这里要注意的是,我们需要在Activity的xml布局中添加fragment,在该元素下添加name属性,并指定是android:name="androidx.navigation.fragment.NavHostFragment",将defaultNavHost属性设置成true,则该Fragment会默认作为NavHostFragment,navGraph属性用来执行其所对应的Navigation Graph文件。
这是再打开之前创建好的Navigation Graph文件就会发现,Destinations面板上的HOST选项已经对应设置了我们在Activity的xml中声明的内容:
3、创建destination
在Navigation Graph中,点就如图中的+号,选择create new destination,选择一个Blank Fragment并创建,如图:
destination指的是目标Fragment,即下一个Fragment,创建好之后便可以在destination面板中的GRAPH选项中看到定义好的Fragment:
不难发现,在navigationOneFragment后面有个"start",表示的是起始Fragment,即第一个展示的Fragment。此时再看下Navigation Graph的xml文件:
<?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/navigation_graph"
app:startDestination="@id/navigationOneFragment">
<fragment
android:id="@+id/navigationOneFragment"
android:name="com.jia.demo.NavigationOneFragment"
android:label="fragment_navigation_one"
tools:layout="@layout/fragment_navigation_one" />
</navigation>
最外层的navigation节点中有一个属性:startDestination,指定了我们创建好的Fragment。
4、切换Fragment
接着用同样的方式创建一个NavigationTwoFragment,选中NavigationOneFragment,选择右侧圆形按钮,拖拽至NavigationTwoFragment,如图:
再查看Graph文件:
<?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/navigation_graph"
app:startDestination="@id/navigationOneFragment">
<fragment
android:id="@+id/navigationOneFragment"
android:name="com.jia.demo.NavigationOneFragment"
android:label="fragment_navigation_one"
tools:layout="@layout/fragment_navigation_one" >
<action
android:id="@+id/action_navigationOneFragment_to_navigationTwoFragment"
app:destination="@id/navigationTwoFragment" />
</fragment>
<fragment
android:id="@+id/navigationTwoFragment"
android:name="com.jia.demo.NavigationTwoFragment"
android:label="fragment_navigation_two"
tools:layout="@layout/fragment_navigation_two" />
</navigation>
这时NavigationOneFragment节点中多了一个action子节点,其中声明了destination属性,指向navigationTwoFragment,即表示NavigationOneFragment的下一个页面就是NavigationTwoFragment。
接下来就该使用之前提到过的NavController了。
完善一下NavigationOneFragment的布局,增加一个Button,并设置点击事件:
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
class NavigationOneFragment : Fragment() {
private var param1: String? = null
private var param2: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_navigation_one, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
button_jump.setOnClickListener {
Navigation.findNavController(it)
.navigate(R.id.action_navigationOneFragment_to_navigationTwoFragment)
}
// button_jump.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_navigationOneFragment_to_navigationTwoFragment))
}
companion object {
@JvmStatic
fun newInstance(param1: String, param2: String) =
NavigationOneFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
除了使用Navigation.findNavController(view).navigate()的方式,还可以使用:Navigation.createNavigateOnClickListener()的方式直接创建一个OnClickListener。传入的id就是Graph文件中的action节点的id。
这样就可以创建Fragment并实现Fragment切换了。
5、为Fragment切换设置动画
为Fragment的切换添加动画,那就需要在anim文件夹下先定义好动画了,不再详细描述。
在Graph的Destinations面板中,选中跳转的箭头,右侧的Attributes面板中显示Animations的区域,如图:
为了方便,我这里直接使用了默认的动画,再看下graph文件:
<?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/navigation_graph"
app:startDestination="@id/navigationOneFragment">
<fragment
android:id="@+id/navigationOneFragment"
android:name="com.jia.demo.NavigationOneFragment"
android:label="fragment_navigation_one"
tools:layout="@layout/fragment_navigation_one" >
<action
android:id="@+id/action_navigationOneFragment_to_navigationTwoFragment"
app:destination="@id/navigationTwoFragment"
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>
<fragment
android:id="@+id/navigationTwoFragment"
android:name="com.jia.demo.NavigationTwoFragment"
android:label="fragment_navigation_two"
tools:layout="@layout/fragment_navigation_two" />
</navigation>
在action节点中多了四个属性,分别对应各个动画,这样就为Fragment切换增加了动画。
除了在Graph文件中进行可视化设置动画,也可以通过代码来设置:
button_jump.setOnClickListener {
val options = NavOptions.Builder()
.setEnterAnim(R.anim.nav_default_enter_anim)
.setExitAnim(R.anim.nav_default_exit_anim)
.setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
.setPopExitAnim(R.anim.nav_default_pop_exit_anim)
.build()
Navigation.findNavController(it)
.navigate(R.id.action_navigationOneFragment_to_navigationTwoFragment, null, options)
}
创建NavOptions对象,设置各个动画,在navigate方法中传入即可,该方法第二个参数为Buddle对象,后面会介绍。
在Fragment中传递参数
1、普通的传参方式
我们最长使用的就是通过Bundle来实现在Fragment之间传递数据,NavController提供了重载的几个navigate方法,可以传入Bundle对象:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
button_jump.setOnClickListener {
val options = NavOptions.Builder()
.setEnterAnim(R.anim.nav_default_enter_anim)
.setExitAnim(R.anim.nav_default_exit_anim)
.setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
.setPopExitAnim(R.anim.nav_default_pop_exit_anim)
.build()
var bundle = Bundle()
bundle.putString("key", "value")
Navigation.findNavController(it).navigate(
R.id.action_navigationOneFragment_to_navigationTwoFragment,
bundle,
options
)
}
}
接收的时候在目标Fragment中:
private const val KEY = "key"
class NavigationTwoFragment : Fragment() {
private var value: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
value = it.getString(KEY)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_navigation_two, container, false)
}
}
2、使用safe args传递参数
使用safe args,首先需要添加依赖和插件,在project的build.gradle文件中:
dependencies {
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.0"
}
在module的build.gradle文件中:
apply plugin: 'androidx.navigation.safeargs'
在Graph文件的fragment节点中,和action同级添加argument节点:
<?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/navigation_graph"
app:startDestination="@id/navigationOneFragment">
<fragment
android:id="@+id/navigationOneFragment"
android:name="com.jia.demo.NavigationOneFragment"
android:label="fragment_navigation_one"
tools:layout="@layout/fragment_navigation_one">
<action
android:id="@+id/action_navigationOneFragment_to_navigationTwoFragment"
app:destination="@id/navigationTwoFragment"
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" />
<argument
android:name="params"
android:defaultValue="default_value"
app:argType="string" />
</fragment>
<fragment
android:id="@+id/navigationTwoFragment"
android:name="com.jia.demo.NavigationTwoFragment"
android:label="fragment_navigation_two"
tools:layout="@layout/fragment_navigation_two" />
</navigation>
添加arguments节点之后,便会自动生成java代码:
传递参数:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
button_jump.setOnClickListener {
val options = NavOptions.Builder()
.setEnterAnim(R.anim.nav_default_enter_anim)
.setExitAnim(R.anim.nav_default_exit_anim)
.setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
.setPopExitAnim(R.anim.nav_default_pop_exit_anim)
.build()
var bundle = NavigationOneFragmentArgs.Builder()
.setParams("one param")
.build()
.toBundle()
Navigation.findNavController(it).navigate(
R.id.action_navigationOneFragment_to_navigationTwoFragment,
bundle,
options
)
}
}
通过自动生成的NavArgs类,使用builder模式创建出Bundle对象,使用之前介绍的方式,便可以在Fragment切换时传递参数。接收的时候也类似:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
var params = arguments?.let { NavigationOneFragmentArgs.fromBundle(it).params}
textView.text = params
}
safe args顾名思义就是安全的参数,使用更加方便也更加安全。
Jetpack为开发者提供了Navigation组件,使用该组件可以方便、快捷、可视化地实现Fragment之间的切换以及动画和传值的功能。