【Android进阶必学】JetPack指路明灯,腾讯、美团Android面试经验分享

引入依赖


implementation “androidx.fragment:fragment-ktx:1.2.0”

implementation “androidx.navigation:navigation-fragment-ktx:2.3.0”

implementation “androidx.navigation:navigation-ui-ktx:2.3.0”

创建测试Fragment和Activity


class LoginFragment : Fragment(R.layout.fragment_login) {

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

super.onViewCreated(view, savedInstanceState)

}

}

类似这样的测试Fragment,不浪费笔墨了。

创建Navigation Graph


在res文件夹下创建navigation文件夹,并定义一个xxxx.xml文件,选择类型为navigation。

这时候,将测试的Fragment导入Design视图,就可以看见这些Fragment的界面了,通过每个视图左右拉出来的箭头,就可以生产一个路由Action,如图【Android进阶必学】JetPack指路明灯,腾讯、美团Android面试经验分享
所示。

【Android进阶必学】JetPack指路明灯,腾讯、美团Android面试经验分享

通过可视化界面,可以很清楚的看见Fragment间的路由路径,同时要注意的是,单个Fragment可以生成不止一个Action,例如一个Fragment可以跳转多个其他Fragment。

通过Design生成的代码如下所示。

【Android进阶必学】JetPack指路明灯,腾讯、美团Android面试经验分享

对于navigation标签来说,最重要的是它的startDestination属性,即类似MainActivity的概念,代表了路由的起点。多个destination连接起来就组成了一个栈导航图,destination之间连接就是action。

每个fragment标签,代表了一层路由,当然,这里不仅仅可以是fragment,也可以是Activity、Dialog。

在每个fragment标签里面的action标签,就代表路由的具体行为,destination就是该路由的终点。

创建Activity并引入NavHostFragment


在Activity的xml布局中,通过FragmentContainerView来创建这些Fragment的容器,代码如下所示。

【Android进阶必学】JetPack指路明灯,腾讯、美团Android面试经验分享

FragmentContainerView是一个特殊的Fragment,只能添加Fragment,

  • app:navGraph:这里需要指定前面在res文件夹下创建的navigation文件

  • app:defaultNavHost=“true”:代表可以拦截系统的返回键,用来托管路由

  • android:name=“androidx.navigation.fragment.NavHostFragment”:代表这个容器就是用来管理Fragment的容器

FragmentContainerView内部会通过反射的方式,初始化名为name所指定的class——NavHostFragment,它就是所有需要管理的Fragment的Container。

在NavHostFragment中,有两个重要的参数,即mGraphId和mDefaultNavHost,保存着我们从xml中解析出来的数据。同时,在onCreate的时候,创建了NavController,与mGraphId进行绑定。

使用路由


在Fragment中,可以通过NavController来进行路由,代码如下所示。

class LoginFragment : Fragment(R.layout.fragment_login) {

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

super.onViewCreated(view, savedInstanceState)

login.setOnClickListener {

Navigation.findNavController(it).navigate(R.id.action_loginFragment_to_registerFragment)

}

}

}

同时,也可以通过Bundle来进行参数的传递,这跟之前使用Fragment基本类似,代码如下。

Navigation.findNavController(it).navigate(R.id.action_registerFragment_to_mainListFragment, bundleOf(“name” to “xuyisheng”))

所以这里可以很方便的进行路由选择,针对不同的判断条件,选择不同的路由action。

为什么能获取

这里有个地方很有意思,那就是为什么通过view可以获取NavController。

Navigation.findNavController(View)

从源码中可以发现。

【Android进阶必学】JetPack指路明灯,腾讯、美团Android面试经验分享

实际上,他是从Tag中取出的,而这个Tag,则是在NavHostFragment的onViewCreated中创建的。

【Android进阶必学】JetPack指路明灯,腾讯、美团Android面试经验分享

这样的API设计,可以让用户传入View后进行遍历,通过查找指定Tag来获取NavController,简化了调用方式。

路由跳转


通过NavController进行路由跳转,有多种方式,比如通过路由action指定,也可以指定跳转的destination。

action

这就是前面提到的路由方式,也是最常用的路由方式,代码如下所示。

Navigation.findNavController(it).navigate(R.id.action_loginFragment_to_registerFragment)

不过要注意的是,使用action进行路由跳转,要保证当前页面的实例是存在的,否则会抛出异常。

destination

直接使用destination的id,同样可以跳转到指定的destination,代码如下所示。

Navigation.findNavController(it).navigate(R.id.mainListFragment)

这种方式,同样是创建一个新的页面实例。

返回控制


路由的返回控制,有两种方式,navigateUp和popBackStack。下面通过一个例子来演示下,如何对路由进行返回控制,下面有三个Fragment,A-B-C。

navigateUp

navigateUp与物理返回键的功能类似,即返回当前页面堆栈的栈顶页面,代码如下所示。

Navigation.findNavController(it).navigateUp()

当我们从A路由到B,B路由到C后,通过上面的代码,使用navigateUp返回,则路由返回路径为C到B,B到A,如果在A继续调用navigateUp,则不会响应,因为当前栈中只有唯一一个页面,而且是startDestination,所以不会再响应返回操作。

popBackStack

navigateUp只能响应向上一级的路由控制,而不能跨级进行路由返回,popBackStack则是对其的补充,可以指定路由返回的action,代码如下所示。

Navigation.findNavController(it).popBackStack(R.id.loginFragment, true)

当我们从A路由到B,B路由到C后,通过popBackStack返回,指定要返回到的Fragment的id,即可直接返回到指定位置,第二个参数inclusive,代表返回操作是否包含指定的Fragment id。

这里要注意的是,当你指定返回到A,同时inclusive为true的时候,A也是不会被移除的,因为A是栈顶。

实际上,navigateUp内部就是通过popBackStack实现的。

借助popBackStack的返回值,可以在跳转失败时,创建新的Fragment。

val flag = Navigation.findNavController(getView()).popBackStack(R.id.someFragment, false)

if (!flag){

Navigation.findNavController(getView()).navigate(R.id.someFragment)

}

defaultNavHost

app:defaultNavHost="true"这个属性是我们最早在FragmentContainerView中设置的,通过这个属性,可以让当前的NavHostFragment拦截系统的返回键,也就是说,只要当前Fragment堆栈中有元素,就拦截系统返回键,用于Fragment堆栈的出栈,直到堆栈中只剩下一个元素,则将系统返回值的功能交还给Activity。

popupTo

当我们通过navigation去进行路由的时候,每次都会创建一个新的实例,所以,当navigation出现下面的循环图时,如下所示。

【Android进阶必学】JetPack指路明灯,腾讯、美团Android面试经验分享

这样的循环图,会导致页面路由变成这样A—B—C—A—B—C,这就导致页面栈中存在了大量重复的页面。

所以在这种场景下,就需要在A—B—C之后,在C—A的路由中,配置popUpTo="@id/A",同时设置popUpToInclusive=true,将旧的A界面也移除,这样,C—A路由之后,页面栈中就只剩下A了(如果是false,则会存在两个A的实例),代码如下所示。

<fragment

android:id="@+id/mainListFragment"

android:name=“com.example.navigation.MainListFragment”

android:label=“MainList”>

<action

android:id="@+id/action_mainListFragment_to_loginFragment"

app:destination="@id/loginFragment"

app:popUpTo="@id/loginFragment"

app:popUpToInclusive=“true” />

样,C—A路由之后,页面栈中就只剩下A了(如果是false,则会存在两个A的实例),代码如下所示。

<fragment

android:id="@+id/mainListFragment"

android:name=“com.example.navigation.MainListFragment”

android:label=“MainList”>

<action

android:id="@+id/action_mainListFragment_to_loginFragment"

app:destination="@id/loginFragment"

app:popUpTo="@id/loginFragment"

app:popUpToInclusive=“true” />

上一篇:Ubuntu16.04分解质因数(C++)


下一篇:常用的Linux 命令