【详解】Android Jetpack 新组件之Navigation的用法和源码结构分析

作 者 简 介

邱俊杰——Android高级工程师

5年以上开发经验,对架构设计以及性能调优有着深刻认知,负责京东商城商品详情的开发,热衷于学习和创新。

>>>>

导读

美国当地时间2018年5月8日,Google I/O大会上发布了 Android Jetpack 。将之前发布的一系列组件比如 Lifecycle、LiveData、Room、ViewModel等进行融合从而推出了一套官方认证的开发体系Android Jetpack。这套体系分为架构(Architecture)、UI、基础(Foundation)以及行为(Behavior)四个方面。

这次的Android Jetpack推出了五个新组件,它们分别是:Navigation(导航组件)、Paging(分页组件)、WorkManager(处理类似后台任务组件)、Slices(切片)、Android KTX(Kotiln扩展程序)

本文主要介绍的是Navigation,接下来还会有Paging的深度解析好文,敬请关注~

>>>>

介绍

>>>>

为什么使用Navigation?

  • 简化fragment栈管理,终结IllegalStateExceptions这个异常?
  • Deeplink的支持,绕过activity直接跳到fragment
  • 传递参数更安全

>>>>

Navigation——导航组件

a、基于Android Studio 3.2 Canary 14(https://developer.android.com/studio/preview/)引入包:

dependencies {
 nav_version = '1.0.0-alpha01'
implementation "android.arch.navigation:navigation-fragment:$nav_version"
implementation "android.arch.navigation:navigation-ui:$nav_version"
}

b、看看它什么样子

了解XCode应该都知道,有没有感觉和StoryBoard很像?通过可视化直接连接他们的跳转,方便操作并且可读性强。

>>>>

Navigation详解

>>>>

Navigation Graph

处理导航的跳转并且连接它们,存放在res目录下navigation目录,其实可以理解为导航图,结构如图:

再来看看这个nav_graph的源码,创建的时候选择navigation,这就是text编辑器,design视图就是个Storyborad,这个可以通过编辑器左上角的+号把fragment加入进来,所有的fragment都可以通过这个布局定义导航,看代码注释如下:

<?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/nav_graph"
    app:startDestination="@id/mainFragment"> //默认开始启动的fragment

    <fragment
        android:id="@+id/mainFragment" // fragment id
        android:name="com.jd.demo.ui.main.MainFragment"//当前节点指定的fragment
        android:label="main_fragment"
        tools:layout="@layout/main_fragment_2" > //fragment布局文件,便于可视化查看
        <action
            android:id="@+id/action_to_mainFragment2" //action id,导航需要指定这个id
            app:destination="@+id/mainFragment2" //跳转的下一个fragment
            app:enterAnim="@anim/slide_in_right" //转场动画 ..
            app:exitAnim="@anim/slide_out_left"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@animslide_out_right" />
    </fragment>
    <fragment
        android:id="@+id/mainFragment2"
        android:name=" com.jd.demo.ui.main.MainFragment2"
        android:label="main_fragment_2"
        tools:layout="@layout/main_fragment_2" >
    </fragment>

配置Activity布局,fragment指定name为androidx.navigation.fragment. NavHostFragment,这个不需要我们处理,它负责显示和处理fragment跳转的载体,defaultNavHost会处理返回键,是的,我们不需要处理返回键的fragment切换了!

<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">

  <fragment
    android:id="@+id/my_nav_host_fragment"
    android:name="androidx.navigation.fragment.NavHostFragment"//系统生成
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:defaultNavHost="true" //处理返回事件
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"    
      app:layout_constraintHorizontal_bias="1"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintVertical_bias="1"
    app:navGraph="@navigation/nav_graph" />  //导航navigation
</LinearLayout>

MainFragment.kt 实现一个跳转就行

val inflate = inflater.inflate(R.layout.main_fragment_3, container, false)
val button1 = inflate.findViewById<Button>(R.id.button3)
button1.setOnClickListener { view -> Navigation.findNavController(view).navigate(R.id.action_mainFragment2_to_mainFragment1) }
return inflate

同样的,其他fragment跳转也是通过这个方式,navigate传入的是actionId,写完这几个步骤,那么恭喜你,程序可以开跑了。

>>>>

Navigation to a destination

要导航到目的地,您可以使用下列四种方法之一:

  • Navigation.findNavController(Activity, @IdRes Int viewId)
  • Navigation.findNavController(View)
  • NavHostFragment.findNavController(Fragment)
  • View.findNavController()

例如,Navigation.findNavController(view).navigate(R.id. action_ to_mainFragment2);

>>>>

Action

负责处理导航的行为、属性、转场动画。id是这个动作的唯一属性,导航的时候需要指定这个id,destination为跳转的对应id的fragment ,转场动画上面已经贴过代码了。

>>>>

传递数据

您可以通过两种方式在目标之间传递数据:使用Bundle对象或使用safeargs Gradle插件以类型安全的方式传递数据。

1、使用Bundle对象

  • Xml配置argument节点
<fragmentandroid:id="@+id/confirmationFragment"  android:name="com.example.cashdog.cashdog.ConfirmationFragment"
android:label="fragment_confirmation"
tools:layout="@layout/fragment_confirmation">
<argument android:name="amount" android:defaultValue=”0” />
  • 使用navigate()方法创建一个包并将其传递到目标:
Bundle bundle = new Bundle();
  bundle.putString("amount", amount);
  Navigation.findNavController(view).navigate(R.id.confirmationAction, bundle);
  • 在您的接收目标的代码中,使用getArguments()方法获取
TextView tv = view.findViewById(R.id.textViewAmount);
   tv.setText(getArguments().getString("amount"));

2、以类型安全的方式在目标之间传递数据

导航架构组件有一个名为safeargs的Gradle插件,它生成简单的对象和构建器类,以便对目标和动作指定的参数进行类型安全访问。安全参数建立在Bundle方法的基础上,但需要一些额外的代码来换取更多的类型安全。如果您使用Gradle,则可以使用安全参数插件。要添加此插件,请将'androidx.navigation.safeargs'插件添加到您的build.gradle文件中。例如:

apply plugin: 'com.android.application'
apply plugin: 'androidx.navigation.safeargs'
android {   //...
}
  • Xml布局代码:
<fragment android:id="@+id/confirmationFragment"  android:name="com.example.buybuddy.buybuddy.ConfirmationFragment"
 android:label="fragment_confirmation"
 tools:layout="@layout/fragment_confirmation">
 <argument android:name="amount" android:defaultValue="1" app:type="integer"/>
  </fragment>
  • 传递数据
EditText amountTv = (EditText) getView().findViewById(R.id.editTextAmount); int amount = Integer.parseInt(amountTv.getText().toString());
 ConfirmationAction action =
        SpecifyAmountFragmentDirections.confirmationAction()
 action.setAmount(amount)
 Navigation.findNavController(view).navigate(action);
  • 接收数据
TextView tv = view.findViewById(R.id.textViewAmount); int amount = ConfirmationFragmentArgs.fromBundle(getArguments()).getAmount();
 tv.setText(amount + "")

>>>>

Navigation嵌套使用

使用场景,多分支深度跳转可以考虑使用,如图:

>>>>

Deeplink-深度链接跳转

在Android中,深层链接是指向应用中特定目标的URI。类似Activity的scheme协议。这样就可以直接跳转到fragment

  • 添加deelink在fragment节点下
<deepLink app:uri="www.jd.com"/>
<deepLink app:uri="www.jd.com.*"/>
<deepLink app:uri="www.jd.com/{ myarg }" android:autoVerify="true"/>
  • 注意配置
<activity name=".MainActivity">
    <nav-graph android:value="@navigation/xxxxx" />
</activity>
  • 在fragment中获取参数
arguments.getString("myarg")

注意:

根据官方文档说明,为了保证导航的正确使用,我们需要在目标的Activity中重写onSupportNavigateUp,以确保导航器能够正确的回退栈。

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

>>>>

源码分析

为了更直观的了解Navigation源码结构,我花了点时间绘制了一张类图

先看看NavHostFragment,NavHostFragment在布局中提供一个载体,控制和管理导航行为。它实现NavHost,NavHost是提供导航的接口,便于扩展。

在oncreate的时候实例化NavController,它才是真正处理导航的,并且把FragmentNavigator实例化添加到SimpleNavigatorProvider

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    final Context context = getContext();
    mNavController = new NavController(context);
    mNavController.getNavigatorProvider().addNavigator(createFragmentNavigator());
}

onCreateView创建一个FrameLayout做为导航区域

@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
   @Nullable Bundle savedInstanceState) {
   FrameLayout frameLayout = new FrameLayout(inflater.getContext());
   frameLayout.setId(getId());
   return frameLayout;
    }

onViewCreated传入当前的根布局以及NavController实例,并且给根布局设置一个tag,所以在每次findNavController的时候都会通过这个tag取唯一的实例,如果取不到会循环从父布局在去找。

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        if (!(view instanceof ViewGroup)) {
           throw new IllegalStateException("created host view " + view + " is not a ViewGroup");
        }
        View rootView = view.getParent() != null ? (View) view.getParent() : view;
        Navigation.setViewNavController(rootView, mNavController);
}
    public static void setViewNavController(@NonNull View view,
            @Nullable NavController controller) {
        view.setTag(R.id.nav_controller_view_tag, controller);
    }

>>>>

NavController

通过NavInflater解析nav_graph.xml,储存所有节点的Argument,Action以及deeplink,并且返回NavDestination,并且记录当前栈的顺序。

NavGraph 是NAVDESTINATION导航节点的集合类,实现ITERABLE接口,通过ADDDESTINATION方法储存导航节点。

Navigator是一个抽象类,提供navigate、popBackStack抽象方法,指定导航和回退导航,而ActivityNavigator和FragmentNavigator才是实现类,最终的导航行为都是他们负责实现。

>>>>

结语

至此Navigation的用法和源码结构都介绍分析完了,他的功能还是很强大的,并且有着安全性和灵活性,以及可视化、人性化的操作,deeplink跳转,同时还支持所有架构组件例如Lifecycle和ViewModel。有兴趣的一定要试试。

一项新的技术出来,必然是为了解决某些问题或者实现一些新功能,我们应该勇于尝试和探索,并且学习它的精髓,同时提醒,kotlin已经应用到官方文档教程以及demo中,并且Google做了KTX支持,所以kotlin很有必要去熟练应用它。

更多详细请参阅:

https://developer.android.com/topic/libraries/architecture/navigation/

---------------------END---------------------

原文发布于微信公众号 - 京东技术(jingdongjishu)

原文发表时间:2018-07-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏我有一个梦想

Python 项目实践一(外星人入侵小游戏)第三篇

接着上节的继续学习, 一 重构:模块game_functions 在大型项目中,经常需要在添加新代码前重构既有代码。重构旨在简化既有代码的结构,使其更容易扩展。...

34690
来自专栏GIS讲堂

Openlayers3加载天地图

在前文中分别讲到了在Arcgis for js、Openlayers2中去加载天地图,同时也讲到了天地图的离线加载方式。在本文,讲述在Openlayers3中实...

46430
来自专栏向治洪

Android 深入ViewPager补间动画,实现类京东商城首页广告Banner切换效果

某天看到京东商城首页的滑动广告的Banner,在流动切换的时候有立体的动画效果,感觉很有意思,然后研究了下如何实现.  废话不多说,接下来我会讲述如何实现这种效...

31560
来自专栏Jaycekon

Phantomjs+Nodejs+Mysql数据抓取(2.抓取图片)

概要 这篇博客是在上一篇博客Phantomjs+Nodejs+Mysql数据抓取(1.抓取数据) http://blog.csdn.net/jokerko...

37750
来自专栏Android开发经验

自适应软键盘的Dialog以及监听软键盘弹起

30730
来自专栏技术总结

Photos存储、获取、更改照片详解

28090
来自专栏developerHaoz 的安卓之旅

知乎 Matisse 源码解析,带你探究高效图片选择库的秘密

可以看到 Matisse 的可拓展性是非常强的,不仅可以自定义我们需要的主题,而且还可以按照需求来过滤出我们想要的文件,除此之外,Matisse 采用了建造者模...

58410
来自专栏Android干货

Android多媒体录制--MediaRecorder视频录制

47270
来自专栏腾讯Bugly的专栏

浅谈Android自定义锁屏页的发车姿势

一、为什么需要自定义锁屏页 锁屏作为一种黑白屏时代就存在的手机功能,至今仍发挥着巨大作用,特别是触屏时代的到来,锁屏的功用被发挥到了极致。多少人曾经在无聊的时候...

55180
来自专栏androidBlog

使用ViewDragHelper打造属于自己的DragLayout(抽屉开关 )

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/details...

14720

扫码关注云+社区

领取腾讯云代金券