Android Jetpack Navigation 深入体验报告

Android Jetpack 之 Navigation深入体验报告

前言

当前Android开发中使用Fragment来开发页面已经成为主流做法。Fragment轻量、可控性强等优点让人感觉很香。

但是Fragment也有自己的硬伤,那就是回退栈与页面参数传递。虽然当前有例如Fragmentation这样的开源库解决了这类问题,而且这些三方开源库也经受住了时间与项目的检验。但是总让Android开发者心中觉得少了点什么(尤其是学了iOS开发之后。。。)

Navigation的横空出世,让Android开发者终于看到了一线曙光。

上手接入

看到这么个好药,能治Android开发者的心病,我就抱着试试看的态度买了两盒,额,不对,是接入了一下。

其实接入方法非常简单,就是一顿添加依赖。Navigation主要是有两个库和一个gradle插件。

// 需要添加依赖的两个Navigation库
implementation 'android.arch.navigation:navigation-fragment-ktx:1.0.0-beta02'
implementation 'android.arch.navigation:navigation-ui-ktx:1.0.0-beta02'

// 这段依赖添加在工程的build.gradle中,这个是一个gradle插件
classpath 'android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-beta02'

// 在app用到的module中启用这个插件
apply plugin: 'androidx.navigation.safeargs'
复制代码

两个依赖库就是Navigation的主体,提供了Navigation的基础能力。safeArgs这个插件是用来支持页面间类型安全的参数传递,不是必须的,但是建议使用,因为真的很方便,这个后面会讲到。

创建一个Navigation

依赖都添加好了,开搞!

Navigation还是延用了Android的XML管理思想,每一个Navigation导航逻辑对应一个xml文件(就是对应iOS的storyboard)。这个xml文件在资源目录的navigation文件夹下(和layout、drawable是平级的)

创建方法就是右键,选择new一个Android Resource File,之后的弹窗里,你会发现ResourceType的选项里,多了一个Navigation,如下图。(如果没有,老弟儿,你的依赖一定没配置对,或者你还是AndroidStudio3.2吧。。。)

起个名字,创建!操作面板长成这样(不要在意我风骚的名字)

我的需求很简单,我有一个桌面页(desktop),我现在想上youtube,我先要跳转的Google,然后在通过Google上youtube去看看外面的世界,完事以后回到桌面。嗯,就这么简单。

按照以前我们的做法,首先肯定要自己创建3个页面的Fragment布局,然后再创建Fragment类,然后还要在Activity里面创建三个Fragment,然后通过SupportFragmentManager来添加页面,管理页面跳转和回退,这个就是我们开篇时候说的痛点。

现在有了Navigation,怎么做呢?放下一前的老思想吧,不用写代码就能搞定。

首先,在刚才的面板中点击添加一个destination,destination(目的地)是一个新概念,用于页面跳转。一个destination对应一个Fragment。例如从A跳转B,我的destination就是B。

创建destination的过程非常人性化,填写Fragment名字,自动创建对应的xml布局文件。我们就添加三个页面,Desktop、Google、youtube。添加完如图所示:

OK,我们用到的3个页面都在这里了。我们的跳转流程就是Desktop - > Google -> youtube -> Desktop。怎么设置呢?连线就行了!

连线完毕是这样的,不要在意那飘逸的线条。Desktop页面左上角有个小房子图标,这说明这个页面是Navigation的起始页面。这个可以在右侧编辑栏里的start destination中修改。

关联Navigation到Activity

联好线了,走到这一步,我们已经将页面的跳转关系声明完毕。但是要提醒大家一下,我们编辑的这个Navigation是一个xml,而且是一个导航xml,并不是布局啊。怎么往页面里添加呢?

别急,这个也很简单。先在Activity的布局xml里添加一个fragment标签。然后添加navi属性,具体如下:

"@+id/nav_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:defaultNavHost="true"
            app:navGraph="@navigation/navi_youtube"/>
复制代码

关键属性有两个个,android:name app:navGraph

name中声明这个Fragment是一个NavHostFragment。这个可以理解为所有导航页面的容器。导航中声明了各个页面的跳转关系,但是总要有一个容器来展示并控制它们吧?这个容器就是NavHostFragment,然后创建一个Activity,用这个Activity再包一下NavHostFragment就可以了。

navGraph属性指定一个导航xml,就是我们刚才编辑的那个navi_pornhub。

配置完了就可以运行一下看看效果了,果然启动后进入了Desktop页面(这里懒得截图了。。。)

页面间的跳转

看的这里可能各位会说,这玩意除了一个图形化的编辑界面就完事了?这个我用原来的纯代码也能做啊。。。它的优势在哪?难道只是长得好看吗?

各位别急啊

以前页面的跳转怎么做?SupportFragmentManager用FragmentTransaction,自己管理,写好多行代码。

看看现在的新操作:

在Desktop页面中点击TextView跳转Google,只需要在onClick里加入这样一行代码即可:

tv_desktop.setOnClickListener {
    findNavController().navigate(FragmentDesktopDirections.actionFragmentDesktopToFragmentGoogle())
}
复制代码

加完之后运行一下,发现真的可以跳转了!

我们来仔细看看这行魔法代码。首先它执行findNavController(),这个函数点进去发现它是Navigation库给Fragment做的一个拓展。

这里有就是调用了NaviHostFragment的同名函数,这也印证了我们前文说到的NaviHostFragment的作用,它就是一个控制容器。

找到NavController之后,调用了navigate(NavDirections directions)函数。这个函数从名字看出来就是做导航跳转用的。但是新姿势是它的参数比较特殊,是一个NavDirections实例。这个类是干什么的呢?

还记得前面介绍的Destination概念吗?Destination表示一个跳转目的地,一个Destination对应一个Fragment。NavDirections表示的是两个页面之间的跳转关系,其实就是我们最开始画导航页面关系时候,连接页面的那个箭头。一个箭头对应一个NavDirections实例。

那跳转的时候如何获取NavDirections实例呢?这里Navigation框架采用编译器自动生成代码的技术,对导航xml每一个Fragment,都生成了一个对应的类,名字就是类名+Directions,例如FragmentGoogleDirections。这些自动生成的类可以在工程的generatedJava路径下看到。代码如下:

其实就是一个Java静态工具类。有一个action,名字就可以看出是由桌面跳转Google,它返回一个NavDirections实例。所以在跳转页面时,我们直接调用这个函数获取NavDirections实例即可。这个实例就包含了页面跳转信息,告诉Navigation我要从桌面跳转的Google页面。

这里只有一个跳转action函数,因为桌面只有跳转Google一个跳转管理。如果页面增加,桌面有多条跳转关系,这里会生成多个action函数,函数名称对应导航xml中每个箭头的action名称。

同理,把Google跳转youtube,和youtube跳转桌面也加上,都很简单。

tv_google.setOnClickListener {
    findNavController().navigate(FragmentGoogleDirections.actionFragmentGoogleToFragmentYoutube())
}

tv_youtube.setOnClickListener {
    findNavController().navigate(FragmentPornHubDirections.actionFragmentYoutubeToFragmentDesktop())
}
复制代码

这样就实现了页面间的跳转逻辑。这种写法首先很简洁,思路清晰,看函数名一眼就能看出来是怎么跳转的,而且自动生成的代码也避免了人为犯错。怎么样,回想一下以前用SupportFragmentManager做跳转的方法,是不是感觉相见恨晚?

页面回退管理

了解了页面跳转,我们再来看看页面回退。运行一下刚才的程序,发现可以无限循环跳转,桌面到Google,Google到youtube,youtube再到桌面。。。

我们点一下返回键看看会发生什么。额。。。发现会一直回退,你刚才重复循环了跳转多少次,就要回退多少次。这个说白了就是Activity的Standard启动模式。

这个不是我们要的效果啊,我们要的是A到B,B到C,C返回A,之后页面栈里就只有A了。不能再返回了。说白了就是singleTask启动模式。

这个需要对跳转的属性进行设置,打开导航xml,选中youtube到桌面的连接箭头。在右侧的属性编辑区里,找到红框里的属性,填写好即可。

这里首先声明了这是一个pop跳转操作,要跳回到Desktop。然后inclusive打上对勾,这个表示调回之后,要把destination也弹出。不勾选这个,会导致返回后栈里有两个桌面Fragment。不信可以试试哦。

之后再你的Activity里面,覆写这个函数:

override fun onSupportNavigateUp() =
        findNavController(this, R.id.nav_host_fragment).navigateUp()
复制代码

覆写这个函数就是把Activity的返回操作权利移交给Navigation框架来处理。从此不用再写onKeyDown了。。。

页面参数传递

跳转和回退都搞定了,就要看看页面参数传递了。这个体验之后发现是真香度最高的部分。

先回顾一下老方法,老方法当然就是setArguments。因为Fragment不支持你在构造函数中加入参数(当然你要硬加也行,各种报警告,贼难受),所以只能通过setArguments来搞,不管你怎么封装,都需要两步:创建和设置参数。怎么都不快乐。

现在来Navigation吧,这个新操作绝对眼前一亮。

需求:我想在桌面跳转Google时,给页面传递一个搜索关键词youtube,并且弹一个Toast。下面是做法:

首先在导航xml里,选中GoogleFragment,在右面的操作面板里,点击添加arguments,添加一个字符串参数,叫keyword。不可空,没有默认值。点击确认添加。

之后可以看见GoogleFragment里面已经有一个叫keyword的参数了。

之后点击桌面到Google的箭头(还记得箭头其实就是一个NavDirections吧),你会发现这个Direction的属性里也有了一个参数,其实这个就是一个自动化的对应机制,因为GoogleFragment有一个参数,所以所有跳转到这个页面的Direction里都应该有对应的参数。

参数添加好以后,修改一下跳转的那一行魔法代码。

tv_desktop.setOnClickListener {
    findNavController().navigate(FragmentDesktopDirections.actionFragmentDesktopToFragmentGoogle("youtube"))
}
复制代码

这里在调用action函数获取NavDirections实例的时候,需要传一个字符串,就是keywork的值。同理如果有多个参数这里就传多个值。

OK,启动方的参数传递已经完成了。我们来看看接收方。

在GoogleFragment中如何获取到keyword的值呢?其实Navigation库已经帮你把参数放到arguments里面了,你直接取就行了。例如这样:

val keyword = arguments?.getString("keyword") ?: ""
复制代码

直接这么调用就能拿到传递过来的值了。但是感觉不是很香啊。那是因为你没有使用safeArgs插件!来看一下这个新操作。

val safeArgs = FragmentGoogleArgs.fromBundle(arguments!!)
val keyword = safeArgs.keyword
复制代码

这里又用到了自动生成代码的技术,对于每一个有参数的Fragment,都生成一个类名+Args的类。调用第一行代码,就能得到一个safeArgs实例。之后需要用参数的时候,直接safeArgs.参数名就能拿到参数的值。

各位老铁,感觉出safeArgs的好处了吗?首先它是类型安全的,可以直接点出来,不用打问号,舒服了很多。最重要的是,老方法是getString("变量名")这个变量名是手打的,safeArgs可以直接点出变量名来取值,方便还不容易出错。

体验总结

  • 真香
  • 目前还是beta版本,还不建议在实际项目中使用
  • 会持续关注后续消息,期待稳定版本
-- 2019.03.18更新,已发布1.0.0正式版

转载于:https://juejin.im/post/5c8f283f6fb9a0710466981a

你可能感兴趣的:(Android Jetpack Navigation 深入体验报告)