前言
在上一篇中,已经将Jetpack对应的Room讲解完毕。在本篇中将会开启Navigation初步讲解!
1、认识Navigation
1.1 Navigation的诞生
Activity嵌套多个Fragment的UI架构模式已经非常普遍,但是对Fragment的管理一直是一件比较麻烦的事情。我们需要通过FragmentManager和FragmentTransaction来管理Fragment之间的切换。
页面的切换通常包括对应用程序App Bar的管理、Fragment间的切换动画,以及Fragment间的参数传递。
纯代码的方式使用起来不是特别友好,并且Fragment和App bar在管理和使用的过程中显得混乱。
为此,Jetpack提供了Navigation组件,旨在方便我们管理页面和App Bar
1.2 Navigation的主要元素
- Navigation Graph,一种新的XML资源文件,包含应用程序所有的页面,以及页面间的关系。
- NavHostFragment,一个特殊的Fragment,可以将它看作是其他Fragment的容器,
Navigation Graph中的Fragment正式通过NavHostFragment进行展示的。 - NavController ,用于在代码中完成Navigation Graph中具体的页面切换工作。
- 它们三者之间的关系
- 当你想切换Fragment时,使用NavController对象,告诉它你想要去Navigation Graph 中的哪个Fragment,NavController会将你想去的Fragment展示NavHostFragment中。
概念说了一大堆,开始实战吧!
2、Navigation实战
2.1 准备工作
项目根目录对应的build.gradle需要引入
classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.3.0-alpha06'
对应app module里面的build.gradle
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'androidx.navigation.safeargs.kotlin'
}
...略
dependencies {
...略
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
...略
}
好了准备工作做好了,开始实战吧!
2.2 开始实战
2.2.1 布局以及关系搭建
如图所示
- 先创建对应的Fragment和对应的布局,并且相互关联
- 在res资源目录下创建对应的Navigation资源文件(my_nav_graph.xml)
如图所示
- 随后通过标注1,将对应的Fragment添加进来,随后将对应的Fragment添加对应的关系
- 我这关系是:homeFragment可跳detailFragment;detailFragment也可跳homeFragment
来看看当前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/my_nav_graph"
app:startDestination="@id/homeFragment">
<fragment
android:id="@+id/homeFragment"
android:name="com.dongnaoedu.navigation.HomeFragment"
android:label="fragment_home"
tools:layout="@layout/fragment_home" >
<action
android:id="@+id/action_homeFragment_to_detailFragment"
app:destination="@id/detailFragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim" />
</fragment>
<fragment
android:id="@+id/detailFragment"
android:name="com.dongnaoedu.navigation.DetailFragment"
android:label="fragment_detail"
tools:layout="@layout/fragment_detail" >
<action
android:id="@+id/action_detailFragment_to_homeFragment"
app:destination="@id/homeFragment" />
</fragment>
</navigation>
从这段代码可以看出:
- 最外层为navigation标签,其次包裹着不同的
fragment - 而不同的
fragment 里面有不同的action 操作 - 除了基础操作外,还可以在对应
action 里添加入场出场动画
2.2.2 业务逻辑实现
HomeFragment.kt
class HomeFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_home, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
var button: Button? = view?.findViewById(R.id.button)
button?.setOnClickListener {
val navController = Navigation.findNavController(it)
navController.navigate(R.id.action_homeFragment_to_detailFragment)
}
}
}
DetailFragment.kt
class DetailFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_detail, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val button: Button? = view?.findViewById(R.id.button2)
button?.setOnClickListener {
val navController = Navigation.findNavController(it)
navController.navigate(R.id.action_detailFragment_to_homeFragment)
}
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navController = Navigation.findNavController(this, R.id.fragment)
NavigationUI.setupActionBarWithNavController(this, navController)
}
}
来看看运行效果
我们看到,这里Fragment相互跳转已经实现了!但是点击上方返回缺返回不了!要怎么解决呢?
进入MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navController = Navigation.findNavController(this, R.id.fragment)
NavigationUI.setupActionBarWithNavController(this, navController)
}
override fun onSupportNavigateUp(): Boolean {
val navController = Navigation.findNavController(this, R.id.fragment)
return navController.navigateUp()
}
}
我们看到重写了onSupportNavigateUp 方法,里面实现了对应逻辑(固定代码)。
再来看看运行效果
从这个运行效果可以看出,点击上面返回已经能够成功返回了。
现在Fragment跳转已经实现了,那么传递值该怎么传呢?
2.2.3 Fragment 数据传递
我们先看以前所使用的方式:
对应HomeFragment
class HomeFragment : Fragment() {
...略
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
var button: Button? = view?.findViewById(R.id.button)
button?.setOnClickListener {
var args = Bundle()
args.putString("userName","hqk")
val navController = Navigation.findNavController(it)
navController.navigate(R.id.action_homeFragment_to_detailFragment, args)
}
}
}
对应DetailFragment
class DetailFragment : Fragment() {
...略
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
var args = arguments
var userName: String? = args?.getString("userName")
Log.d("hqk", "userName is $userName")
val button: Button? = view?.findViewById(R.id.button2)
button?.setOnClickListener {
val navController = Navigation.findNavController(it)
navController.navigate(R.id.action_detailFragment_to_homeFragment)
}
}
}
这个是我们比较熟悉的方式,运行效果我就不展示了
- 相信大家对bundle取值的时候,往往还会前往上一个页面复制粘贴对应的key值以及对比对应的数据类型。
- 因为在bundle取值的时候,即使key传错了,或者对应的变量类型取错了,在代码里都不会有任何错误提示
- 只有当运行到对应逻辑时,才知道对应取值有问题
那么有没有新颖的方式呢?既然问了那就肯定有!
继续进入my_nav_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/my_nav_graph"
app:startDestination="@id/homeFragment">
<fragment
android:id="@+id/homeFragment"
android:name="com.hqk.navigation.HomeFragment"
android:label="fragment_home"
tools:layout="@layout/fragment_home" >
<action
android:id="@+id/action_homeFragment_to_detailFragment"
app:destination="@id/detailFragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"/>
<argument
android:name="user_name"
app:argType="string"
android:defaultValue="unknown"/>
<argument
android:name="age"
app:argType="integer"
android:defaultValue="0"/>
</fragment>
<fragment
android:id="@+id/detailFragment"
android:name="com.hqk.navigation.DetailFragment"
android:label="fragment_detail"
tools:layout="@layout/fragment_detail" >
<action
android:id="@+id/action_detailFragment_to_homeFragment"
app:destination="@id/homeFragment" />
</fragment>
</navigation>
我们看到,在对应的fragment里面添加了对应的argument 标签,里面标注了对应的类型和变量名!
那么如何使用呢?
进入HomeFragment
class HomeFragment : Fragment() {
...略
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
var button: Button? = view?.findViewById(R.id.button)
button?.setOnClickListener {
var args = HomeFragmentArgs(userName = "hqk", 18).toBundle()
val navController = Navigation.findNavController(it)
navController.navigate(R.id.action_homeFragment_to_detailFragment, args)
}
}
}
我们看到使用了HomeFragmentArgs 将参数传递过去,那么接收方呢?
DetailFragment.kt
class DetailFragment : Fragment() {
...略
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val args = HomeFragmentArgs.fromBundle(requireArguments())
val userName = args.userName
val age = args.age
Log.d("hqk", "$userName,$age")
val button: Button? = view?.findViewById(R.id.button2)
button?.setOnClickListener {
val navController = Navigation.findNavController(it)
navController.navigate(R.id.action_detailFragment_to_homeFragment)
}
}
}
我们这里看到:
- 通过
HomeFragmentArgs.fromBundle(requireArguments()) 获取到了前者传入过来的bundle信息,而取值就直接通过对应的属性访问! - 极大的避免了运行时出错!也不会来回切换前后者看对应的变量名以及变量类型
运行效果我这就不贴了,读者可尝试一下
结束语
好了本篇Navigation 的讲解,到这里就结束了。当然Navigation 可不止于此,因此在下一篇中,将会讲解NavigationUI 以及DeepLink 相关的内容
|