Android—Jetpack教程(七)

这是我参与11月更文挑战的第28天,活动详情查看:2021最后一次更文挑战

前言

在上一篇中,已经将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' //这个是java引入的,下面才是kotlin
    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 布局以及关系搭建

1.png

如图所示

  • 先创建对应的Fragment和对应的布局,并且相互关联
  • 在res资源目录下创建对应的Navigation资源文件(my_nav_graph.xml)

2.png

如图所示

  • 随后通过标注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)
            //id 对应my_nav_graph.xml 里对应Fragment  里面 的 action ID
            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)
            //id 对应my_nav_graph.xml 里对应Fragment  里面 的 action ID
            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)

		//下面代码的 意思是:当切换对应的Fragment时,动态修改ActionBar标题文字,
		//目的为了让运行效果明显
        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)
        
		//下面代码的 意思是:当切换对应的Fragment时,动态修改ActionBar标题文字,
		//目的为了让运行效果明显
        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")
//        args?.getInt("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 = Bundle()
//            args.putString("userName","hqk")
            
            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)

//        var args = arguments
//        var userName: String? = args?.getString("userName")
//        args?.getInt("userName")
//        Log.d("hqk", "userName is $userName")

        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相关的内容

猜你喜欢

转载自juejin.im/post/7035912681362030628