开启成长之旅!这是我参与「日新计划 12 月更文应战」的第2天,点击查看活动详情

导航是指支撑用户导航、进入和退出运用中不同内容片段的交互。Android Jetpack 的导航组件可完成导航,无论是简略的按钮点击,还是运用栏和抽屉式导航栏等更为复杂的形式,该组件均可应对。

依靠项

    def nav_version = "2.5.2"
    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
    implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"

创立导航图

导航发生在运用中的各个目的地之间。这些目的地是通过操作衔接的,导航图是一种资源文件,其间包括一切目的地和操作,该图表会显示运用的一切导航途径。

点击加号便能够创立目的地 Fragment,衔接操作由箭头表明,该箭头表明用户能够怎么从一个目的地导航到另一个目的地。

Android Jetpack - Navigation

Android Jetpack - Navigation

目的地是指运用中的不同内容区域,操作是指目的地之间的逻辑衔接,表明用户能够采取的途径。然后,咱们来看下此刻的导航xml代码:main_navigation.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/main_navigation"
    app:startDestination="@id/oneFragment">
    <fragment
        android:id="@+id/oneFragment"
        android:name="com.example.myapplication.OneFragment"
        android:label="fragment_one"
        tools:layout="@layout/fragment_one" >
        <action
            android:id="@+id/action_oneFragment_to_twoFragment"
            app:destination="@id/twoFragment" />
    </fragment>
    <fragment
        android:id="@+id/twoFragment"
        android:name="com.example.myapplication.TwoFragment"
        android:label="fragment_two"
        tools:layout="@layout/fragment_two" >
        <action
            android:id="@+id/action_twoFragment_to_threeFragment"
            app:destination="@id/threeFragment" />
    </fragment>
    <fragment
        android:id="@+id/threeFragment"
        android:name="com.example.myapplication.ThreeFragment"
        android:label="fragment_three"
        tools:layout="@layout/fragment_three" />
</navigation>

其间,navigation 元素是导航图的根元素,向图表增加目的地和衔接操作,即 action 和 destination。

导航宿主

导航宿主是一个空容器,用户在运用中导航时,目的地会在该容器中交流进出。导航宿主必须派生于 NavHost,Navigation 组件的默许 NavHost 完成 NavHostFragment,负责处理 fragment 目的地的交流。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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=".MainActivity">
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/container"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/main_navigation" />
</LinearLayout>
  • android:name:NavHost 完成类的称号
  • app:navGraph:将 NavHostFragment 与导航图相关联
  • app:defaultNavHost=”true”:保证 NavHostFragment 会阻拦体系返回按钮

留意:只能有一个默许 NavHost,假如同一布局(例如,双窗格布局)中有多个保管容器,请务必仅指定一个默许 NavHost。

导航到目的地

跳转主张运用 Safe Args 保证类型安全,在 Project 下的 build.gradle 增加。启用 Safe Args 后,该插件会生成代码,其间包括每个操作的类和方法。对于每个操作,Safe Args 还会为每个源头生成一个类。生成的类的称号由源类的称号和 Directions 一词组成,例如 OneFragment,生成的便是 OneFragmentDirections。

buildscript {
    repositories {
        google()
    }
    dependencies {
        def nav_version = "2.5.2"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}

在 APP 模块的 build.gradle 增加

plugins {
    id 'androidx.navigation.safeargs'
}

点击按钮,进行跳转

        jump.setOnClickListener {
            val action = OneFragmentDirections.actionOneFragmentToTwoFragment()
            it.findNavController().navigate(action)
        }

传递参数

启用 Safe Args 后,会创立一个类,该类的称号是在目的地的称号后边加上 Args,例如 OneFragment,称号为 OneFragmentArgs,用于参数传递。

在 main_navigation.xml 加上参数标签 argument

    <fragment
        android:id="@+id/oneFragment"
        android:name="com.example.myapplication.OneFragment"
        android:label="fragment_one"
        tools:layout="@layout/fragment_one">
        <argument
            android:name="name"
            android:defaultValue="1"
            app:argType="string" />
        <action
            android:id="@+id/action_oneFragment_to_twoFragment"
            app:destination="@id/twoFragment" />
    </fragment>

在 OneFragment 中进行跳转,参数传递

        jump.setOnClickListener {
            val param = OneFragmentArgs.Builder().setName(nameStr).build().toBundle()
            it.findNavController().navigate(R.id.action_oneFragment_to_twoFragment, param)
        }

TwoFragment 接纳参数,主张运用 ktx,就能够运用 by navArgs() 特点托付来访问参数。

class TwoFragment : Fragment() {
    private val args: OneFragmentArgs by navArgs()
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val nameStr = args.name //接纳的值
        return inflater.inflate(R.layout.fragment_two, container, false)
    }
}

NavigationUI

此类包括多种静态方法,可帮助咱们运用顶部运用栏,抽屉式导航栏和底部导航栏来管理导航。

这儿简略完成一个底部导航栏,先创立一个 menu

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/oneFragment"
        android:icon="@android:drawable/ic_menu_camera"
        android:title="相机" />
    <item
        android:id="@+id/twoFragment"
        android:icon="@android:drawable/ic_dialog_email"
        android:title="邮件" />
    <item
        android:id="@+id/threeFragment"
        android:icon="@android:drawable/ic_menu_call"
        android:title="电话" />
</menu>

创立三个 Fragment,作为三个页面,需求留意的是 menu 各个 item 的 id 和 fragment 的 id 需求相对应。

<?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/main_navigation"
    app:startDestination="@id/oneFragment">
    <fragment
        android:id="@+id/oneFragment"
        android:name="com.example.myapplication.OneFragment"
        android:label="fragment_one"
        tools:layout="@layout/fragment_one" />
    <fragment
        android:id="@+id/twoFragment"
        android:name="com.example.myapplication.TwoFragment"
        android:label="fragment_two"
        tools:layout="@layout/fragment_two" />
    <fragment
        android:id="@+id/threeFragment"
        android:name="com.example.myapplication.ThreeFragment"
        android:label="fragment_three"
        tools:layout="@layout/fragment_three" />
</navigation>

宿主的布局如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".MainActivity">
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/container"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        app:defaultNavHost="true"
        app:navGraph="@navigation/main_navigation" />
    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:menu="@menu/main_item" />
</LinearLayout>

然后在宿主Activity中履行如下就行啦

        val navHostFragment =
            supportFragmentManager.findFragmentById(R.id.container) as NavHostFragment
        val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom)
        NavigationUI.setupWithNavController(bottomNavigationView, navHostFragment.navController)

Android Jetpack - Navigation

多模块导航

对于比较大型的项目,咱们一般都会采用组件化开发,每个功能模块都专注于一项功能。每个功能模块都是一个独立的单元,拥有自己的导航图和目的地,app 模块依靠于每个功能模块。

app 模块负责提供运用的完整导航图,以及将 NavHost 增加到界面中,能够运用 include 来引用库图。

<?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/main_navigation"
    app:startDestination="@id/mainFragment">
    <include app:graph="@navigation/bike_nav" />
    <include app:graph="@navigation/car_nav" />
    <fragment
        android:id="@+id/mainFragment"
        android:name="com.example.myapplication.MainFragment"
        android:label="fragment_main"
        tools:layout="@layout/fragment_main">
        <action
            android:id="@+id/action_mainFragment_to_car_nav"
            app:destination="@id/car_nav" />
        <action
            android:id="@+id/action_mainFragment_to_bike_nav"
            app:destination="@id/bike_nav" />
    </fragment>
</navigation>

如下图所示,这儿主页面一个 MainFragment,用来做跳转,分别可跳转到两个不同的模块

Android Jetpack - Navigation

然后在 MainFragment 里履行跳转逻辑

            findViewById<TextView>(R.id.red).setOnClickListener {
                it.findNavController().navigate(MainFragmentDirections.actionMainFragmentToCarNav())
            }
            findViewById<TextView>(R.id.purple).setOnClickListener {
                it.findNavController()
                    .navigate(MainFragmentDirections.actionMainFragmentToBikeNav())
            }

那么,假如是两个单独的功能模块之间需求导航呢,而独立的功能模块互相又看不到对方,怎么办呢? 这时,能够运用深层链接直接导航到与隐式深层链接关联的目的地。

举个比如,现在要从 Bikelibrary 里的 Fragment 跳转到 Carlibrary 里的 Fragment

Android Jetpack - Navigation

将 Carlibrary 中的导航增加深层链接

<?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/car_nav"
    app:startDestination="@id/carFragment">
    <fragment
        android:id="@+id/carFragment"
        android:name="com.example.carlibrary.CarFragment"
        android:label="fragment_car"
        tools:layout="@layout/fragment_car">
        <deepLink
            android:id="@+id/deepLink"
            app:uri="android-app://com.example.carlibrary/carFragment" />
    </fragment>
</navigation>

当然,你也能够从 xml 编辑器的右侧增加

Android Jetpack - Navigation

然后在 bikelibrary 里的 fragment 中根据此链接进行导航,这儿设置了一个按钮的点击事情

            findViewById<Button>(R.id.bike_to_car).setOnClickListener {
                val request =
                    NavDeepLinkRequest.Builder.fromUri("android-app://com.example.carlibrary/carFragment".toUri())
                        .build()
                it.findNavController().navigate(request)
            }

这样,就完成了跨模块导航,是不是很方便呢?不过,需求留意的是,Safe Args 不支撑跨模块导航,因为没有针对目的地的直接操作。