前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【详解】Android Jetpack 新组件之Navigation的用法和源码结构分析

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

作者头像
京东技术
发布2018-07-30 16:10:37
2.1K0
发布2018-07-30 16:10:37
举报
文章被收录于专栏:京东技术京东技术京东技术

作 者 简 介

邱俊杰——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---------------------

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-07-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 京东技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档