JetPack中的Navigation

2018年谷歌I/O 发布了一系列辅助android开发者的实用工具,合称Jetpack,以帮助开发者构建出色的 Android 应用。
这次发布的 Android Jetpack 组件覆盖以下 4 个方面:Architecture、Foundation、Behavior 以及 UI。
该系列博客介绍一下Jetpack中常用组件,本篇介绍LiveData、ViewModel、LifeCycle。最后借助于https://github.com/android/sunflower 来写一个完整的应用

原文https://developer.android.com/guide/navigation/?hl=zh-cn,就像它的名字一样,用来做导航,可以用来做单页面应用(一个Activity中多个Fragment进行切换)

导航组件由以下三个关键部分组成:

  • 导航图:在一个集中位置包含所有导航相关信息的 XML 资源。这包括应用内所有单个内容区域(称为目标)以及用户可以通过应用获取的可能路径。
  • NavHost:显示导航图中目标的空白容器。导航组件包含一个默认 NavHost 实现 (NavHostFragment),可显示 Fragment 目标
  • NavController:在 NavHost 中管理应用导航的对象。当用户在整个应用中移动时,NavController 会安排 NavHost 中目标内容的交换。

使用方式

添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
dependencies {
def nav_version = "2.3.0-alpha01"

// Java language implementation
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"

// Kotlin
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

// Dynamic Feature Module Support
implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"

// Testing Navigation
androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"
}

创建导航图

res/navigation下创建一个Navigation source file,根节点如下:

1
2
3
4
5
6
<?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"
android:id="@+id/test_navigation">

</navigation>

向 Activity 添加 NavHost

导航宿主是 Navigation 组件的核心部分之一。导航宿主是一个空容器,用户在您的应用中导航时,目的地会在该容器中交换进出。

导航宿主必须派生于 NavHost。Navigation 组件的默认 NavHost 实现 (NavHostFragment) 负责处理 Fragment 目的地的交换。

在Activit的布局文件中添加如下控件

1
2
3
4
5
6
7
<fragment
android:id="@+id/fragment_first"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/test_navi" />

请注意以下几点:

  • android:name 属性包含 NavHost 实现的类名称。
  • app:navGraph 属性将 NavHostFragment 与导航图相关联。导航图会在此 NavHostFragment 中指定用户可以导航到的所有目的地。
  • app:defaultNavHost="true" 属性确保您的 NavHostFragment 会拦截系统返回按钮。请注意,只能有一个默认 NavHost。如果同一布局(例如,双窗格布局)中有多个主机,请务必仅指定一个默认 NavHost

在Activit中设置支持:

1
2
3
4
5
6
7
8
9
10
11
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_navigation);
}


@Override
public boolean onSupportNavigateUp() {
return Navigation.findNavController(this, R.id.fragment_first).navigateUp();
}

添加Fragment

我们添加FragmentA,FragmentB,FragmentC,每个Fragment中只有一个文本按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class FragmentA extends Fragment {

public FragmentA() {
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view =
inflater.inflate(R.layout.fragment_a, container, false);
view.findViewById(R.id.jump).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentBArgs.Builder builder= new FragmentBArgs.Builder();
builder.setNum(11111);
builder.setTitle("FragmentB");
Navigation.findNavController(v).navigate(R.id.action_fragmentA_to_fragmentB,builder.build().toBundle());
}
});

return view;
}

}

添加导航

在我们前面步骤创建的Navigation Resource文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?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/test_navi"
app:startDestination="@id/fragmentA">
<fragment
android:id="@+id/fragmentA"
android:name="com.huangyuanlove.androidjetpack.architecture.navigation.FragmentA"
android:label="fragmentA"
tools:layout="@layout/fragment_a">
<action
android:id="@+id/action_fragmentA_to_fragmentB"
app:destination="@id/fragmentB"
app:exitAnim="@android:anim/slide_out_right" />
</fragment>

<fragment
android:id="@+id/fragmentB"

android:name="com.huangyuanlove.androidjetpack.architecture.navigation.FragmentB"
android:label="fragmentB"
tools:layout="@layout/fragment_b">
<argument
android:name="title"
android:defaultValue="test"
app:argType="string" />
<argument
android:name="num"
android:defaultValue="100"
app:argType="integer" />
<action
android:id="@+id/action_fragmentB_to_fragmentC"
app:destination="@id/fragmentC"
app:exitAnim="@android:anim/slide_out_right" />
</fragment>
<fragment
android:id="@+id/fragmentC"

android:name="com.huangyuanlove.androidjetpack.architecture.navigation.FragmentC"
android:label="fragmentC"
tools:layout="@layout/fragment_c">
<action
android:id="@+id/action_fragmentC_to_fragmentA"
app:destination="@id/fragmentA"
app:exitAnim="@android:anim/slide_out_right" />
</fragment>

</navigation>
  • Type 字段指示在您的源代码中,该目的地是作为 Fragment、Activity 还是其他自定义类实现的。
  • Label 字段包含该目的地的 XML 布局文件的名称。
  • ID 字段包含该目的地的 ID,它用于在代码中引用该目的地。
  • Class 下拉列表显示与该目的地相关联的类的名称。您可以点击此下拉列表,将相关联的类更改为其他目的地类型。

使用

导航到目的地是使用 NavController 完成的,后者是一个在 NavHost 中管理应用导航的对象。每个 NavHost 均有自己的相应 NavController。可以使用以下方法之一检索 NavController

比如我们打算在FragmentA中导航到FragmentB,则

1
2
3
4
5
6
7
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Navigation.findNavController(v)
.navigate(R.id.action_fragmentA_to_fragmentB);
}
});

如果我们需要传递参数,可以使用 Safe Args 来确保类型安全。

在顶级build.gradle中添加classPath:classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.0-alpha02"

在对应module的build.gradle中添加plugin:apply plugin: 'androidx.navigation.safeargs'

我们在上面的Navigation Resource中的fragmentB中添加了三个argument节点,插件会帮我们自动生成FragmentBArgs类,我们可以这么使用

1
2
3
4
5
6
7
8
9
view.findViewById(R.id.jump).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentBArgs.Builder builder= new FragmentBArgs.Builder();
builder.setNum(11111);
builder.setTitle("FragmentB");
Navigation.findNavController(v).navigate(R.id.action_fragmentA_to_fragmentB,builder.build().toBundle());
}
});

遗留问题

尽管通过Navigation方式管理Fragment的代码简洁而且直观,但是有一个非常致命的问题,就是同一个Fragment会被重复创建,比如A到B,B再到C,然后点击返回键返回到B,这时B会被重建,再点击返回键返回到A,A也会被重建,目前还找不到解决方案,或许是我的使用方式不对


以上


JetPack中的Navigation
https://blog.huangyuanlove.com/2020/02/25/JetPack中的Navigation/
作者
HuangYuan_xuan
发布于
2020年2月25日
许可协议
BY HUANG兄