Jetpack是google近年来力推的一系列安卓开发组件框架,目前仍在不断的更新与完善,其主要目的是帮助开发者们构建高质量的现代化APP,减少模版代码。
本系列文章将会介绍Jetpack常用组件框架的使用方式,如Navigation、ViewModel、LiveData、Room、Paging、WorkManager等。
一、Navigation简介
Navigation主要用于实现Fragment代替Activity的页面导航功能,让Fragment能够轻松的实现跳转与传递参数,我们可以通过使用Navigation,让Fragment代替android项目中绝大多数的Activity。
本文主要内容有:Navigation依赖、创建第一个Navigation页面、Navigation页面跳转、Navigation页面携带参数跳转、Navigation页面数据回传、配置ActionBar、切换动画、共享元素动画
我以前在开发android项目时,除了少部分的内嵌页面使用Fragment之外,其他绝大多数的页面都是使用Activity来实现,因为安卓为Activity提供了一套完整的页面解决方案:
- oneActivity跳转并传递参数到twoActivity:
Intent intent = new Intent(context,twoActivity.class);
intent.putExtra(key,value);//传递参数
//oneActivity.startActivity(intent);//无回传参数
oneActivity.startActivityForResult(intent,oneRequestCode);
- 在twoActivity获取参数:
value = getIntent().getStringExtra(key);
- 在twoActivity回传参数到oneActivity:
Intent intent = getIntent();
intent.putExtra(key,value);//回传参数
setResult(RESULT_OK,intent);
finish();
- 在oneActivity接收回传的参数
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK){
if (requestCode == oneRequestCode){
value = data.getStringExtra(key);
}
}
}
毫无疑问,Activity可以说是我们android开发人员最熟悉的组件了,而且功能也非常成熟与完善,既然如此,google为何又要开发出一套全新的Navigation框架呢?原因我认为主要有两个:
- Activity太过于重量级了,如果我们去查看Activity的源码就会发现,其内部实现机制却是非常复杂的,因此每一个Activity的启动会消耗大量的资源,增加了开发高质量android项目的难度。相比之下,Fragment显得非常的轻量级。
- 页面是APP中最重要的组件,使用Activity构建页面却往往需要维护大量复杂的生命周期,无疑增加了android项目开发的难度,对于想要学习android开发的人来说更是不友好,在开发技术日新月异的今天,如此以往不利于android生态的发展,所以google需要一个能够在绝大多数场景代替Activity、简单且现代化的页面解决方案。
二、Navigation的使用
官网主页:传送门
最新版本:传送门
(一)新建项目
- 依次点击File -- New -- NewProject
- 选择Empty Activity,点击Next
- 输入项目名和包名,点击Finish完成新建项目。
我们将在这个Project上学习Navigation的使用。
(二)添加依赖
- 打开Module:app的build.gradle,在dependencies中添加依赖:
dependencies {
def nav_version = "2.1.0"
// Java
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"
}
- 使用Safe Args Gradle插件,实现在Navigation页面间类型安全的传递数据(如果不使用Safe Args可以跳过这一步)。先打开Project的build.gradle添加以下内容:
buildscript {
...
dependencies {
...
def nav_version = "2.1.0"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
}
然后再打开Module:app的build.gradle添加以下内容:
apply plugin: "androidx.navigation.safeargs"
这样子我们就完成Navigation依赖的添加了。
(三)创建第一个Navigation页面
Navigation由3个关键部分组成:
- 导航图:用于配置导航页面及路径的XML文件
- NavHost:用于显示导航图中目标页面的空白容器,常用NavHostFragment来实现
- NavController:负责在 NavHost 中管理页面导航的对象,但需要页面跳转时,NavController会控制NavHost中的目标页面进行交换
1. 创建导航图
我们先来创建一个导航图,在项目上右键选择New - Android Resource File,在弹出窗口中选择Resource Type为Navigation(如下图),接着为导航图命名(例如nav_main),最后点击OK创建好的导航图会保存在res/navigation/nav_main.xm,接下来我们双击打开创建好的导航图(如下图)
里面还没有任何页面,所以我们先来创建一个页面,首先创建一个布局文件(例如fragment_one.xml)
再创建一个Fragment(例如FragmentOne.java)
public class FragmentOne extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_one, container, false);
}
}
接下来我们回到导航图,把创建好的页面添加到导航图中,先双击导航图界面上的New Destination小图标,然后点击我们创建好的页面,即可完成页面的添加(如下图)
2. 创建NavHost
NavHost是用于显示导航图中目标页面的空白容器,常用NavHostFragment来实现。这一步我们直接打开项目默认创建好的activity_main.xml,把NavHostFragment添加到里面即可:
这一步做完之后,我们便完成了第一个Navigation页面的创建,我们可以安装到设备来看一下效果(如下图):
(四)Navigation页面跳转
上文说到Navigation有3个关键部分,并且学习了其中的导航图和NavHost,这一节我们来学习剩下的最后一个关键部分NavController,并实现Navigation页面跳转。
为了实现跳转,我们需要创建第二个页面,首先创建一个布局文件(例如fragment_two.xml)
再创建一个Fragment(例如FragmentTwo.java)
public class FragmentTwo extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_two, container, false);
}
}
接下来我们回到导航图,把创建好的第二个页面也添加到导航图中,添加完成后如下图:
接下来我们单击选中第一个页面(fragmentOne),可以看到选中后会出现蓝色边框和一个圆点(如下图)
我们把鼠标移动到圆点上,按下鼠标把圆点拖动到第二个页面上(fragmentTwo),我们可以看到系统生成了一条由fragmentOne指向fragmentTwo的箭头,它代表了Navigation页面切换的路径。
紧接着,我们来修改第一个页面fragment_one.xml,往里面添加一个按钮,以便实现点击时跳转页面:
准备工作基本完成。
接下来,我们要依次点击Build - Make Project,让android studio先自动生成路径的相关代码。
最后便是实现跳转的代码,要实现Navigation页面跳转,我们首先获取NavController:
NavController navController = Navigation.findNavController(getView());
然后再通过Navigation自动生成的FragmentOneDirections(自动生成的命名格式为:页面名+Directions)获取刚刚设置好的路径:
NavDirections navDirections = FragmentOneDirections.actionFragmentOneToFragmentTwo();
最后通过NavController使用路径进行跳转即可:
navController.navigate(navDirections);
我们打开FragmentOne.java,把上述内容修改到里面,完整代码如下:
public class FragmentOne extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_one, container, false);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Button btnGoTwo = getView().findViewById(R.id.btnGoTwo);
btnGoTwo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 核心代码
NavDirections navDirections = FragmentOneDirections.actionFragmentOneToFragmentTwo();
NavController navController = Navigation.findNavController(getView());
navController.navigate(navDirections);
}
});
}
}
这一步做完之后,我们便完成了Navigation页面的跳转,我们可以安装到设备来看一下效果:
(五)Navigation页面携带参数跳转
上文我们完成了Navigation的跳转,接下来我们看看如何携带参数进行跳转。
场景:假设我们的fragmentTwo现在需要一个String类型的传入参数Message,fragmentOne在跳转时需要携带它。
首先我们打开导航图,点击fragmentTwo,在右边找到Arguments这一行,点击这一行右边的“+”号(如下图)
系统会弹出一个Add Argument Link的窗体,我们在这个窗体上添加传入参数的信息。窗体的字段包括:
Name(参数名)
Type(参数类型)
Array(参数是否为列表)
Nullable(参数是否可为空)
Default Value(参数的默认值)
我们按照需求,添加一个String类型的传入参数Message,然后点击Add按钮(如下图)
接下来,我们依次点击Build - Make Project,让android studio自动生成参数的相关代码。
紧接着,我们打开FragmentOne.java,修改代码实现携带参数跳转:
NavDirections navDirections = FragmentOneDirections.actionFragmentOneToFragmentTwo("你好,我是fragmentOne发来的消息");
NavController navController = Navigation.findNavController(getView());
navController.navigate(navDirections);
最后,我们回到FragmentTwo.java,利用Navigation自动生成的FragmentTwoArgs(自动生成的命名格式为:页面名+Args)来获取传入的参数Message:
String message = FragmentTwoArgs.fromBundle(getArguments()).getMessage();
FragmentTwo.java的完整代码如下:
public class FragmentTwo extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_two, container, false);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
TextView tvMessage = getView().findViewById(R.id.tvMessage);
String message = FragmentTwoArgs.fromBundle(getArguments()).getMessage();
tvMessage.setText(message);
}
}
这一步做完之后,我们便完成了Navigation页面携带参数跳转,我们可以安装到设备来看一下效果:
(六)Navigation页面数据回传
在本文最上面介绍Activity的时候,我们知道Activity能够通过setResult实现数据的回传,那么Navigation如何实现回传呢?
目前google官方并没有在Navigation框架本身实现该功能,而是建议开发者使用LiveData来实现这一功能,因此这一官方建议的解决方案我们会留到后面介绍LiveData时再进行介绍。
现阶段我们可以采用另外一种简单的方式来实现,首先我们知道,当前项目Navigation两个页面都运行在同一个Activity(MainActivity)中,而且在两个页面均可以通过getActivity访问到,因此我们可以通过MainActivity来保存回传的数据,实现简单的数据回传。
首先,在MainActivity.java添加以下内容:
private String navigationResultData;
public void setNavigationResultData(String navigationResultData){
this.navigationResultData = navigationResultData;
}
public String getNavigationResultData(){
return navigationResultData;
}
然后,在fragment_two.xml添加一个返回按钮,以便点击时回传参数:
在FragmentTwo.java对返回按钮进行注册监听,点击时关闭当前页面并回传参数:
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
TextView tvMessage = getView().findViewById(R.id.tvMessage);
String message = FragmentTwoArgs.fromBundle(getArguments()).getMessage();
tvMessage.setText(message);
// 对返回按钮进行注册监听,点击时关闭当前页面并回传参数
Button btnBack = getView().findViewById(R.id.btnBack);
btnBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (getActivity() instanceof MainActivity){
((MainActivity)getActivity()).setNavigationResultData("你好,我是FragmentTwo回传的数据");
}
// Navigation.findNavController(getView()).popBackStack(); // 弹出当前堆栈后面的堆栈,如果后面无堆栈则报错
Navigation.findNavController(getView()).navigateUp(); // 导航到上一个页面,如果当前是第一个页面则停留在当前
}
});
}
这里我们可以看到,Navigation返回上一个页面有两种主要方式:
- popBackStack:弹出当前堆栈后面的堆栈,如果后面无堆栈则报错
- navigateUp:导航到上一个页面,如果当前是第一个页面则停留在当前
回到回传数据上,修改完FragmentTwo页面,接下来我们修改FragmentOne用于接收回传的数据:
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Button btnGoTwo = getView().findViewById(R.id.btnGoTwo);
btnGoTwo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
NavDirections navDirections = FragmentOneDirections.actionFragmentOneToFragmentTwo("你好,我是fragmentOne发来的消息");
NavController navController = Navigation.findNavController(getView());
navController.navigate(navDirections);
}
});
// 接收回传的数据
TextView tvMessage = getView().findViewById(R.id.tvMessage);
if (getActivity() instanceof MainActivity) {
String navigationResultData = ((MainActivity) getActivity()).getNavigationResultData();
if (navigationResultData != null) {
tvMessage.setText(navigationResultData);
}
}
}
这一步做完之后,我们便完成了简单版的数据回传,我们可以安装到设备来看一下,但在第二个页面点击“返回”时,效果如下:
(七)配置ActionBar
上文我们完成了Navigation页面跳转,但和Activity跳转相比仍有不足之处——我们的ActionBar标题不会跟随Navigation页面变化,而且也没有用于返回的小图标。
因此,本节我们来解决该问题,Navigation已为我们提供了便捷的解决方案,我们只需配置NavController对ActionBar进行控制,即可解决该问题。
我们打开MainActivity.java,通过四个步骤即可完成配置:
- 第一步,在Activity上获取NavController:
navController = Navigation.findNavController(this, R.id.navHost);
- 第二步,配置AppBarConfiguration,以便NavController接管ActionBar后能正确进行控制:
appBarConfiguration = new AppBarConfiguration.Builder(R.id.fragmentOne).build();//配置fragmentOne为顶部页面
- 第三步,配置NavController对ActionBar进行控制
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
- 第四步,配置NavController对后退事件进行控制
@Override
public boolean onSupportNavigateUp() {
// 配置NavController对后退事件进行控制
return NavigationUI.navigateUp(navController, appBarConfiguration);
}
完整的MainActivity.java代码如下:
public class MainActivity extends AppCompatActivity {
private NavController navController;
private AppBarConfiguration appBarConfiguration;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 配置Toolbar到ActionBar
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// 配置NavController对ActionBar进行控制
navController = Navigation.findNavController(this, R.id.navHost);
appBarConfiguration = new AppBarConfiguration.Builder(R.id.fragmentOne).build();//配置fragmentOne为顶部页面
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
}
@Override
public boolean onSupportNavigateUp() {
// 配置NavController对后退事件进行控制
return NavigationUI.navigateUp(navController, appBarConfiguration);
}
// 实现简单的Navigation页面回传数据
private String navigationResultData;
public void setNavigationResultData(String navigationResultData) {
this.navigationResultData = navigationResultData;
}
public String getNavigationResultData() {
return navigationResultData;
}
}
现在我们的Navigation页面跳转和Activity跳转就基本一样了,我们可以安装到设备来看一下效果:
棒棒哒!
我们观察到页面的标题是fragment_one和fragment_two,这是Navigation根据页面文件名自动生成的,而实际项目我们通常都需要对这些页面标题进行修改,那么应该如何进行修改呢?
-
静态修改标题:我们打开导航图,点击选中需要修改标题的页面(例如fragmentOne),在右边Attributes窗体的Label输入框进行修改即可(如下图)
-
动态修改标题:例如我们想使用fragmentOne页面传递过来的标题来作为fragmentTwo页面的标题,这种情况下我们首先需要为fragmentTwo页面添加一个Title参数。
步骤和添加Message参数一样,先打开导航图,然后点击选中fragmentTwo页面,接着在右边Arguments点击"+"号,完成Title参数的添加(如下图)
接下来我们把fragmentTwo页面的标题设置为Title参数,步骤和静态修改标题一样,只不过我们这次需要把Label修改为{参数名}(如下图)
添加完参数后,记得Build - Make Project,让android studio自动生成参数的相关代码。
最后一步,我们只需要在跳转时把Title参数携带传递到fragmentTwo页面上即可。我们打开FragmentOne.java,修改跳转的相关代码:
NavDirections navDirections = FragmentOneDirections
.actionFragmentOneToFragmentTwo("你好,我是fragmentOne发来的消息")
.setTitle("第二个页面");
NavController navController = Navigation.findNavController(getView());
navController.navigate(navDirections);
现在我们已经成功实现动态修改标题了,我们可以安装到设备来看一下效果:
(八)切换动画
我们知道,使用Activity跳转我们可以实现各种各样的切换动画,那么使用Navigation是否也可以呢?答案是肯定的,Navigation为我们提供了完善的解决方案,能够实现与Activity一样的切换动画效果。
首先我们创建四个简单的切换动画文件:
anim/slide_in_left.xml
slide_in_right.xml
slide_out_left.xml
slide_out_right.xml
接着我们把切换动画应用到Navigation中,打开导航图,点击fragmentOne页面,在右边Actions一栏中双击需要添加切换动画的路径(例如fragmentTwo,如下图)
弹出Update Action窗体,我们找到Transition设置相应的动画文件即可(如下图)
修改后点击Update即可完成Navigation切换动画的设置!
(九)共享元素动画
Navigation页面跳转同样也可以实现共享元素动画!
首先我们创建第三个页面,用于演示共享元素动画效果,首先创建一个布局文件(例如fragment_three.xml),其中的ImageView设置有transitionName属性,为共享元素
values/strings.xml
transitionBtn
@drawable/img资源
接着创建对应的Fragment(例如FragmentThree.java)
public class FragmentThree extends Fragment {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//设置共享动画
setSharedElementEnterTransition(TransitionInflater.from(getContext()).inflateTransition(android.R.transition.move));
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_three, container, false);
}
}
同样添加到导航图中,然后在导航图中选中fragmentTwo页面拖动圆点到fragmentThree页面上(如下图),记得Build - Make Project,让android studio自动生成路径的相关代码。
接着我们修改fragmentTwo页面,让它实现跳转到fragmentThree页面且共享元素动画,我们先修改fragment_two.xml,增加一个ImageView作为共享元素(如下图)
再修改FragmentThree.java的onActivityCreated方法,让ImageView点击时跳转到fragmentThree页面,并启用共享元素动画
// 对ImageView进行注册监听,点击时跳转到fragmentThree页面,并启用共享元素动画
final ImageView ivGoThree = getView().findViewById(R.id.ivGoThree);
ivGoThree.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
NavDirections navDirections = FragmentTwoDirections
.actionFragmentTwoToFragmentThree();
NavController navController = Navigation.findNavController(getView());
// 添加共享元素动画
FragmentNavigator.Extras extras = new FragmentNavigator.Extras.Builder()
.addSharedElement(ivGoThree, ViewCompat.getTransitionName(ivGoThree))
.build();
navController.navigate(navDirections, extras);
}
});
这样便可实现Navigation页面跳转共享元素动画!