Android Jetpack Navigation组件(一):入门使用

目录

    • 前言
    • 一、配置环境
    • 二、创建导航图
    • 三、在Activity里添加NavHost
      • 1. 什么是NavHost?
      • 2. 如何实现NavHost?
      • 3.添加NavHost到Activity
    • 四、创建Fragment(作为目的地)
    • 五、添加目的地到导航图
    • 六、连接目的地
    • 七、导航到目的地
    • 八、在导航时传递参数
      • 1.声明参数
      • 2.传递参数
      • 3.接收参数
      • 4.参数默认值
        • 4.1给参数设置默认值
        • 4.2修改设置了默认值的参数
    • 九.其他导航方式
    • 十.最终效果和工程代码
      • 1.最终效果
      • 2.工程代码
    • 总结

前言

引用Android官方文档的一句话:

The Navigation component is designed for apps that have one main activity with multiple fragment destinations.

Navigation组件是为了那些使用"单Activity多Fragment"架构模式的app而设计,在这个系列教程里我们只使用Fragment作为目的地去管理界面和实现界面跳转

一、配置环境

  1. 配置project根目录下的build.gradle文件
buildscript {
    repositories {
        google()
    }
    dependencies {
    	// safe args gradle插件版本号
        def nav_version = "2.4.1"
        // 依赖safe args gradle插件。用于自动生成目的地之间传递参数需要的class文件
        // 通过它自动生成的代码我们可以方便快捷地在导航的时候传递参数
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}
  1. 配置app目录下的build.gradle文件
plugins {
  // 应用safe args gradle插件
  id 'androidx.navigation.safeargs'
}

dependencies {
  // Navigation组件版本号,推荐使用最新的稳定版本号
  def nav_version = "2.4.1"

  // Java和Kotlin二选一即可

  // 依赖Java语言对应的navigation组件
  implementation "androidx.navigation:navigation-fragment:$nav_version"
  implementation "androidx.navigation:navigation-ui:$nav_version"
  // 依赖Kotlin语言对应的navigation组件
  implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
  implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
}

二、创建导航图

res目录下创建一个Navigation类型的资源文件:

  1. 右键点击res目录->New->Android Resource File
  2. 在弹出的对话框中输入文件名“nav_graph”,并选择资源文件类型为Navigation,然后点击OK
    Android Jetpack Navigation组件(一):入门使用_第1张图片
  3. 结果会在res目录下创建一个navigation目录,里面包含你创建的导航图资源文件nav_graph.xml,它的根元素是
    Android Jetpack Navigation组件(一):入门使用_第2张图片

三、在Activity里添加NavHost

1. 什么是NavHost?

NavHost是一个接口,实现了NavHost接口的类称为导航宿主。
让我们看看Android官方文档是如何解释导航宿主的:

The navigation host is an empty container where destinations are swapped in and out as a user navigates through your app.

直接翻译:导航宿主是一个空的容器,当用户在你的app中导航时,目的地会在其中换入换出。例如:我们在app里从A目的地导航到B目的地的时候,导航组件会把在导航宿主中的A目的地换出,然后把B目的地换入导航宿主
总结翻译:导航宿主(NavHost)是一个目的地容器

2. 如何实现NavHost?

直接使用NavHostFragment
还是Android官方文档的说明:

A navigation host must derive from NavHost. The Navigation component’s default NavHost implementation, NavHostFragment, handles swapping fragment destinations.

意思就是说:导航宿主必须派生自NavHost接口。在导航组件里有一个默认的实现了NavHost接口的类叫NavHostFragment,这个NavHostFragment作为导航宿主,它既作为Fragment目的地的容器,又处理Fragment目的地之间的交换
总结:当我们只使用Fragment作为目的地时,不需要再写一个类去实现NavHost接口,直接使用NavHostFragment即可满足需求。

3.添加NavHost到Activity

添加NavHostFragment到Activity的xml文件中

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph" />

androidx.constraintlayout.widget.ConstraintLayout>

属性说明:
android:name="androidx.navigation.fragment.NavHostFragment"
表示使用NavHostFragment作为NavHost
app:defaultNavHost="true"
表示拦截系统的返回按钮事件
app:navGraph="@navigation/nav_graph"
表示NavHostFragment关联的导航图为nav_graph.xml。这个导航图指定了在此NavHostFragment里面,我们可以导航到的所有目的地。这里nav_graph.xml就是我们刚刚创建的那个导航图

四、创建Fragment(作为目的地)

分别创建AFragmentBFragment作为导航的目的地。

public class AFragment extends Fragment {
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_a, container, false);
    }
}
public class BFragment extends Fragment {
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_b, container, false);
    }
}

五、添加目的地到导航图

通过元素添加目的地



<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/nav_graph"
    app:startDestination="@id/aFragment">

    <fragment
        android:id="@+id/aFragment"
        android:name="com.scx.navigation.basicuse.AFragment"
        android:label="aFragment" />
    <fragment
        android:id="@+id/bFragment"
        android:name="com.scx.navigation.basicuse.BFragment"
        android:label="bFragment" />
navigation>

六、连接目的地

通过元素连接目的地。
假设我们的需求是从AFragmet跳转到BFragment,那么我们需要在AFragment目的地里面添加元素

<fragment
        android:id="@+id/aFragment"
        android:name="com.scx.navigation.basicuse.AFragment"
        android:label="aFragment" >
        <action
            android:id="@+id/action_aFragment_to_bFragment"
            app:destination="@id/bFragment" />
fragment>

此时,safe args gradle插件会生成AFragmentDirections类,包含actionAFragmentToBFragment()方法和ActionAFragmentToBFragment内部类。
AFragmentDirections类:

// 格式为`目的地类名+Directions`。方法名和内部类名都是actionId的驼峰式写法
public class AFragmentDirections {
	// 返回值为ActionAFragmentToBFragment(NavDirections)对象
	public static ActionAFragmentToBFragment actionAFragmentToBFragment(){}
	// NavDirections实现类
	public static class ActionAFragmentToBFragment implements NavDirections {}
}

NavDirections接口:

/**
 * An interface that describes a navigation operation: action's id and arguments
 */
public interface NavDirections {
    /**
     * An action id to navigate with.
     */
    @get:IdRes
    public val actionId: Int

    /**
     * Arguments to pass to the destination
     */
    public val arguments: Bundle
}

NavDirections主要有两个作用:

  • NavController的navigate方法使用它导航到目的地
  • 配置导航参数

七、导航到目的地

使用NavController+NavDirections导航到目的地。
通过前面的学习我们知道导航宿主(NavHostFragment)是一个容纳和交换目的地的容器,其实它本身并不会执行导航的操作,那我们想导航到目的地应该怎么做?
答案是使用NavControllernavigate(navDirections:NavDirections)方法。
顾名思义NavController是一个导航控制管理器。每个导航宿主都有其对应的NavController。

代码如下:

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    // 点击屏幕跳转
    view.findViewById(R.id.tv_a).setOnClickListener(v -> {
        // 获取NavController
        NavController navController = NavHostFragment.findNavController(AFragment.this);
        /* 
        * 通过NavController+NavDirections执行导航操作 
        * 通过AFragmentDirections.actionAFragmentToBFragment()获取NavDirections对象
        */
        navController.navigate(AFragmentDirections.actionAFragmentToBFragment());
    });
}

好了,这样就实现了最基本的导航功能了,是不是超级简单?但显然这无法满足我们实际开发的需求,起码应该能够像Activity一样在跳转的时候传递参数。

八、在导航时传递参数

1.声明参数

通过声明参数。
假设BFragment需要知道前一个目的地是谁,那么可以这样写

<fragment
    android:id="@+id/bFragment"
    android:name="com.scx.navigation.basicuse.BFragment"
    android:label="bFragment" >
    <argument
        android:name="fromWho"
        app:argType="string" />
fragment>

只需要在BFragment目的地里面加一个元素,android:name指定参数的名称,app:argType指定参数的类型。通过Android官方文档可以知道safe args gradle插件支持以下参数类型

类型 app:argType语法 是否支持默认值 是否支持路由 是否可以为空
Integer app:argType=“integer” Yes Yes No
Float app:argType=“float” Yes Yes No
Long app:argType=“long” Yes - 默认值必须以 ‘L’ 作为后缀(e.g. “123L”). Yes No
Boolean app:argType=“boolean” Yes - “true” or “false” Yes No
String app:argType=“string” Yes Yes Yes
资源引用 app:argType=“reference” Yes - 默认值格式必须为 “@resourceType/resourceName” (e.g. “@style/myCustomStyle”) 或者 “数字” Yes No
自定义Parcelable app:argType="", 是自定义Parcelable的完全限定类名 只支持 "@null"作为默认值 No Yes
自定义Serializable app:argType="", 是自定义Serializable的完全限定类名 只支持 "@null"作为默认值 No Yes
自定义Enum app:argType="", 是enum的完全限定类名 Yes - 默认值必须为非限定名(e.g. “SUCCESS” to match MyEnum.SUCCESS). No No
数组 app:argType="[]",除了资源引用和enum,支持以上任一种类型 Yes-只支持 "@null"作为默认值,但必须添加app:nullable="true"属性 No Yes

注意:参数应尽可能地轻量化。通过参数传递复杂的数据结构被认为是一种反面模式。 每个目的地都应根据最少的必要信息(例如Item ID)加载 UI 数据。 这简化了导航流程重建并避免了潜在的数据不一致。

声明参数之后,safe args gradle插件会立马做两件事件:
(1).在源目的地的NavDirections内添加该参数

此时的AFragmentDirections类:

public class AFragmentDirections {
	// 必须传入String类型的fromWho参数。由于fromWho没有默认值,所以必须要传参,详情见参数默认值章节
	public static ActionAFragmentToBFragment actionAFragmentToBFragment(String fromWho){}
	// NavDirections实现类
	public static class ActionAFragmentToBFragment implements NavDirections {
		// 设置fromWho参数值(一般不需要手动调用,上面的actionAFragmentToBFragment()方法已经设置了该参数)
		public ActionAFragmentToBFragment setFromWho(String fromWho){}
	}
}

(2).生成一个Ags类用来接收参数,类名格式为目的地类名+Args
我们这个例子的Ags类就是BFragmentArgs,它实现了NavArgs接口
BFragmentArgs类:

public class BFragmentArgs implements NavArgs {
	// 接收参数
	public static BFragmentArgs fromBundle(@NonNull Bundle bundle) {}
	// 获取fromWho参数值
	public String getFromWho(){}
	// 构造参数的Builder,在后面介绍的通过id导航传参会用到,可先跳过
	public static final class Builder {
		// fromWho没有默认值,必须设置它的值
		public Builder(String fromWho){}
		public Builder setFromWho(String fromWho){}
	}
}

下面分别通过这两个类传递和接收参数

2.传递参数

通过NavDirections传递参数。
直接通过AFragmentDirections的actionAFragmentToBFragment()方法传递参数

// 把字符串“A”传递给BFragment
navController.navigate(AFragmentDirections.actionAFragmentToBFragment("A"));

3.接收参数

通过NavArgs接收参数
在BFragment里面使用BFragmentArgs接收参数

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    // 获取接收参数类对象,获取方法是固定的
    BFragmentArgs args = BFragmentArgs.fromBundle(requireArguments());
    // 获取fromWho参数
    String fromWho = args.getFromWho();
    // 获取showFromWho参数
    boolean showFromWho = args.getShowFromWho();
    TextView textView = view.findViewById(R.id.tv_from_who);
    // 如果showFromWho参数为true则显示fromWho
    if (showFromWho) {
        textView.setText("from:" + fromWho);
    }
}

等等,这里为什么还有一个showFromWho参数?请看下文。

4.参数默认值

4.1给参数设置默认值

通过android:defaultValue属性给参数设置默认值。
我们再给BFragment目的地添加一个参数并设置默认值,这个参数决定是否将fromWho参数展示在屏幕上,默认为false。显然在导航的时候这个参数可传可不传。

<argument
    android:name="showFromWho"
    app:argType="boolean"
    android:defaultValue="false"/>        

同样,safe args gradle会修改AFragmentDirections和BFragmentArgs两个类。
AFragmentDirections类:

public class AFragmentDirections {
	// 必须传入String类型的fromWho参数。由于fromWho没有默认值,所以必须要传参,而showFromWho有默认值,这里不传参
	public static ActionAFragmentToBFragment actionAFragmentToBFragment(String fromWho){}
	// NavDirections实现类
	public static class ActionAFragmentToBFragment implements NavDirections {
		// 设置fromWho参数值(一般不需要手动调用,上面的actionAFragmentToBFragment()方法已经设置了该参数)
		public ActionAFragmentToBFragment setFromWho(String fromWho){}
		// 设置showFromWho参数值,由于showFromWho有默认值,所以必须手动调用修改参数值
		public ActionAFragmentToBFragment setShowFromWho(boolean showFromWho){}
	}
}

BFragmentArgs接收参数的方式不变,所以不再展示。

4.2修改设置了默认值的参数

通过Directions类的NavDirections内部类修改默认值。
相信通过上面的AFragmentDirections代码,修改参数默认值的方法显而易见,它类似于Builder模式,代码如下

navController.navigate(AFragmentDirections.actionAFragmentToBFragment("A").setShowFromWho(true));

当然也可以这样写,这两者是等价的,也许能帮助理解

AFragmentDirections.ActionAFragmentToBFragment action = AFragmentDirections.actionAFragmentToBFragment("A");
action.setShowFromWho(true);
navController.navigate(action);

九.其他导航方式

  1. 通过目的地id导航
Bundle bundle = new BFragmentArgs.Builder("A")
        .setShowFromWho(true)
        .build()
        .toBundle();
// 通过目的地id导航,通过bundle传递参数
navController.navigate(R.id.bFragment, bundle);
  1. 通过action id导航
// 通过actionId导航,通过bundle传递参数
navController.navigate(R.id.action_aFragment_to_bFragment, bundle);

十.最终效果和工程代码

1.最终效果

NavigationBasicUse

2.工程代码

代码地址

总结

本篇文章介绍的只是最基本的使用方法,但却是极其重要的,它是进一步使用Navigation组件的基石。我会在后面的文章中继续介绍导航组件的进阶使用和它的一些原理,往后还会介绍我在实际项目中遇到的问题和解决方法。

水平有限,如有不对,欢迎指正。有什么问题也欢迎在评论区留言。

你可能感兴趣的:(#,Navigation,android,jetpack,android,java,jetpack,navigator)