Jetpack入门系列(二)Navigation

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框架呢?原因我认为主要有两个:

  1. Activity太过于重量级了,如果我们去查看Activity的源码就会发现,其内部实现机制却是非常复杂的,因此每一个Activity的启动会消耗大量的资源,增加了开发高质量android项目的难度。相比之下,Fragment显得非常的轻量级。
  2. 页面是APP中最重要的组件,使用Activity构建页面却往往需要维护大量复杂的生命周期,无疑增加了android项目开发的难度,对于想要学习android开发的人来说更是不友好,在开发技术日新月异的今天,如此以往不利于android生态的发展,所以google需要一个能够在绝大多数场景代替Activity、简单且现代化的页面解决方案。

二、Navigation的使用

官网主页:传送门
最新版本:传送门

(一)新建项目
  • 依次点击File -- New -- NewProject
  • 选择Empty Activity,点击Next
  • 输入项目名和包名,点击Finish完成新建项目。
    我们将在这个Project上学习Navigation的使用。
(二)添加依赖
  1. 打开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"
}
  1. 使用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
Jetpack入门系列(二)Navigation_第1张图片
Resource Type

创建好的导航图会保存在res/navigation/nav_main.xm,接下来我们双击打开创建好的导航图(如下图)


Jetpack入门系列(二)Navigation_第2张图片
导航图

里面还没有任何页面,所以我们先来创建一个页面,首先创建一个布局文件(例如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小图标,然后点击我们创建好的页面,即可完成页面的添加(如下图)


Jetpack入门系列(二)Navigation_第3张图片
New Destination

Jetpack入门系列(二)Navigation_第4张图片
选中我们创建好的页面

Jetpack入门系列(二)Navigation_第5张图片
添加完成
2. 创建NavHost

NavHost是用于显示导航图中目标页面的空白容器,常用NavHostFragment来实现。这一步我们直接打开项目默认创建好的activity_main.xml,把NavHostFragment添加到里面即可:




    

    


这一步做完之后,我们便完成了第一个Navigation页面的创建,我们可以安装到设备来看一下效果(如下图):


Jetpack入门系列(二)Navigation_第6张图片
效果图
(四)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);
    }
}

接下来我们回到导航图,把创建好的第二个页面也添加到导航图中,添加完成后如下图:


Jetpack入门系列(二)Navigation_第7张图片
把创建好的第二个页面也添加到导航图中

接下来我们单击选中第一个页面(fragmentOne),可以看到选中后会出现蓝色边框和一个圆点(如下图)


Jetpack入门系列(二)Navigation_第8张图片
选中第一个页面(fragmentOne)

我们把鼠标移动到圆点上,按下鼠标把圆点拖动到第二个页面上(fragmentTwo),我们可以看到系统生成了一条由fragmentOne指向fragmentTwo的箭头,它代表了Navigation页面切换的路径。
Jetpack入门系列(二)Navigation_第9张图片
路径

紧接着,我们来修改第一个页面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页面的跳转,我们可以安装到设备来看一下效果:


Jetpack入门系列(二)Navigation_第10张图片
第一个页面

Jetpack入门系列(二)Navigation_第11张图片
点击按钮,跳转到第二个页面
(五)Navigation页面携带参数跳转

上文我们完成了Navigation的跳转,接下来我们看看如何携带参数进行跳转。
场景:假设我们的fragmentTwo现在需要一个String类型的传入参数Message,fragmentOne在跳转时需要携带它。
首先我们打开导航图,点击fragmentTwo,在右边找到Arguments这一行,点击这一行右边的“+”号(如下图)


Jetpack入门系列(二)Navigation_第12张图片
Arguments

系统会弹出一个Add Argument Link的窗体,我们在这个窗体上添加传入参数的信息。窗体的字段包括:
Name(参数名)
Type(参数类型)
Array(参数是否为列表)
Nullable(参数是否可为空)
Default Value(参数的默认值)
我们按照需求,添加一个String类型的传入参数Message,然后点击Add按钮(如下图)


Jetpack入门系列(二)Navigation_第13张图片
添加参数

Jetpack入门系列(二)Navigation_第14张图片
添加好的参数会显示在这里,我们可以双击修改

接下来,我们依次点击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页面携带参数跳转,我们可以安装到设备来看一下效果:


Jetpack入门系列(二)Navigation_第15张图片
第一个页面

Jetpack入门系列(二)Navigation_第16张图片
点击按钮,携带参数跳转到第二个页面
(六)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);
            }
        }
    }

这一步做完之后,我们便完成了简单版的数据回传,我们可以安装到设备来看一下,但在第二个页面点击“返回”时,效果如下:


Jetpack入门系列(二)Navigation_第17张图片
数据回传效果
(七)配置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跳转就基本一样了,我们可以安装到设备来看一下效果:


Jetpack入门系列(二)Navigation_第18张图片
第一个页面

Jetpack入门系列(二)Navigation_第19张图片
第二个页面

棒棒哒!
我们观察到页面的标题是fragment_one和fragment_two,这是Navigation根据页面文件名自动生成的,而实际项目我们通常都需要对这些页面标题进行修改,那么应该如何进行修改呢?

  • 静态修改标题:我们打开导航图,点击选中需要修改标题的页面(例如fragmentOne),在右边Attributes窗体的Label输入框进行修改即可(如下图)


    Jetpack入门系列(二)Navigation_第20张图片
    静态修改标题
  • 动态修改标题:例如我们想使用fragmentOne页面传递过来的标题来作为fragmentTwo页面的标题,这种情况下我们首先需要为fragmentTwo页面添加一个Title参数。
    步骤和添加Message参数一样,先打开导航图,然后点击选中fragmentTwo页面,接着在右边Arguments点击"+"号,完成Title参数的添加(如下图)


    Jetpack入门系列(二)Navigation_第21张图片
    为fragmentTwo页面添加Title参数

    接下来我们把fragmentTwo页面的标题设置为Title参数,步骤和静态修改标题一样,只不过我们这次需要把Label修改为{参数名}(如下图)


    Jetpack入门系列(二)Navigation_第22张图片
    动态修改标题

    添加完参数后,记得Build - Make Project,让android studio自动生成参数的相关代码。
    最后一步,我们只需要在跳转时把Title参数携带传递到fragmentTwo页面上即可。我们打开FragmentOne.java,修改跳转的相关代码:
NavDirections navDirections = FragmentOneDirections
                        .actionFragmentOneToFragmentTwo("你好,我是fragmentOne发来的消息")
                        .setTitle("第二个页面");
NavController navController = Navigation.findNavController(getView());
navController.navigate(navDirections);

现在我们已经成功实现动态修改标题了,我们可以安装到设备来看一下效果:


Jetpack入门系列(二)Navigation_第23张图片
第一个页面

Jetpack入门系列(二)Navigation_第24张图片
第二个页面
(八)切换动画

我们知道,使用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,如下图)


Jetpack入门系列(二)Navigation_第25张图片
双击需要添加切换动画的路径

弹出Update Action窗体,我们找到Transition设置相应的动画文件即可(如下图)


Jetpack入门系列(二)Navigation_第26张图片
设置相应的动画文件

修改后点击Update即可完成Navigation切换动画的设置!
(九)共享元素动画

Navigation页面跳转同样也可以实现共享元素动画!
首先我们创建第三个页面,用于演示共享元素动画效果,首先创建一个布局文件(例如fragment_three.xml),其中的ImageView设置有transitionName属性,为共享元素



    

values/strings.xml

transitionBtn

@drawable/img资源


@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自动生成路径的相关代码。


Jetpack入门系列(二)Navigation_第27张图片
导航图

接着我们修改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页面跳转共享元素动画!


Jetpack入门系列(二)Navigation_第28张图片
欢迎大家关注我的个人公众号,阅读最新的系列文章,交流学习经验

你可能感兴趣的:(Jetpack入门系列(二)Navigation)