作 者 简 介
邱俊杰——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?
>>>>
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(view).navigate(R.id. action_ to_mainFragment2);
>>>>
Action
负责处理导航的行为、属性、转场动画。id是这个动作的唯一属性,导航的时候需要指定这个id,destination为跳转的对应id的fragment ,转场动画上面已经贴过代码了。
>>>>
传递数据
您可以通过两种方式在目标之间传递数据:使用Bundle对象或使用safeargs Gradle插件以类型安全的方式传递数据。
1、使用Bundle对象
<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” />
Bundle bundle = new Bundle();
bundle.putString("amount", amount);
Navigation.findNavController(view).navigate(R.id.confirmationAction, bundle);
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 { //...
}
<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
<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>
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---------------------