1、前言
最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的数据逻辑和UI界面深层解耦,实现数据驱动型的ui。
Android Architecture组件是Android Jetpack的一部分,它们是一组库,旨在帮助开发者设计健壮、可测试和可维护的应用程序,包含一下组件:
- 带你领略Android Jetpack组件的魅力
- Android Jetpack 架构组件之 Lifecycle(使用篇)
- Android Jetpack 架构组件之 Lifecycle(源码篇)
- Android Jetpack 架构组件之 ViewModel (源码篇)
- Android Jetpack 架构组件之 LiveData(使用、源码篇)
- Android Jetpack架构组件之 Paging(使用、源码篇)
- Android Jetpack 架构组件之 Room(使用、源码篇)
- Android Jetpack 架构组件之Navigation
- Android Jetpack架构组件之WorkManger
上述时Android Architecture所提供的架构组件,本文主要从使用和源码的角度分析Navigation组件。
2、Navigation简介
导航架构组件简化了Android应用程序中导航的实现,通过在xml中添加元素并指定导航的起始和目的地,从而在Fragment之间建立连接在Activity中调用xml中设置的导航action从而跳转界面到目的地,简单来说它和之前在活动中调用startActivity的区别就类似于代码布局和xml中layout布局一样,既简单又可视化,如下图就是一个navigaton的xml图:
Navigation多数作用于Fragment中,不过导航组建还支持:Fragment、Activity、导航图和子图、自定义目标
3、导航设置操作
3.1、在项目中设置导航
- 开启导航支持
- 点击 File -》 Setting -》Experimental -> 选中 Enable Navigation Editor -> Restart Studio
- 添加项目组件依赖
dependencies {
def nav_version = "1.0.0-alpha06"
implementation "android.arch.navigation:navigation-fragment:$nav_version"
implementation "android.arch.navigation:navigation-ui:$nav_version"
androidTestImplementation "android.arch.navigation:navigation-testing:$nav_version"
}
- 创建 xml 文件
3.2、导航编辑器
- 导航编辑器界面
- 导航编辑器的三个部分:
- “目标”列表 - 列出“曲线图编辑器”中当前的所有目标
- 图表编辑器 - 包含导航图的可视化表示
- 属性编辑器 - 包含与导航图中的目标和操作关联的属性
3.3、确定目的地
- 要确定应用的目标,请使用以下步骤
- 从图形编辑器,单击新目的地 。出现“ 新目标”对话框
- 单击“ 创建空白目标”或单击片段或活动。将出现“新建Android组件”对话框
- 在“ 片段名称”字段中输入名称。此名称是片段类的名称
- 在“ 片段布局名称”字段中输入名称。此名称是片段的布局文件的名称,单击完成
- Attributes面板中显示以下属性
- Type:字段包含“Fragment”或“Activity”,以指示目标是否在源代码中实现为片段或活动
- Label:字段包含目标的XML布局文件的名称
- ID:字段包含将用于在代码中引用目标的目标ID
- Class:字段包含目标类的名称
上面的属性选中后都会自动生成xml中的代码,如:
<fragment android:id="@+id/blankFragment"
android:name="com.example.administrator.navigation.BlankFragment"
android:label="fragment_blank"
tools:layout="@layout/fragment_blank">
</fragment>
3.4、连接目的地
- 连接
- 在图表编辑器中,将鼠标悬停在您希望用户导航的目标的右侧。目的地上会出现一个圆圈
- 单击并按住,将光标拖动到希望用户导航到的目标上,然后释放。绘制一条线以指示两个目的地之间的导航
- 此时Fragment会添加<action>标签
<action
android:id="@+id/action_otherFragment_to_blankFragment" // 在导航时调用此ID设置目的
app:destination="@id/blankFragment" />
- 单机箭头显示操作属性
- Type:操作类型
- ID:系统为操作分配的ID
- Destination:目标片段的id
- 切换起始目的地
- 界面加载的第一个目标名称旁边放置一个房屋图标
- 点击对应的Fragment在右边属性中(或右击)点击“Set Start Destination“
4、实现导航
4.1、修改活动主持导航
- 活动通过实现NavHost 添加到活动布局的界面来托管应用程序的导航, NavHost是一个空视图,当用户浏览您的应用程序时,目的地会被换入和换出,默认 NavHost实现是 NavHostFragment
- 将导航图与NavHostFragment 使用navGraph属性相关联
- xml中设置导航
- 在xml中设置navGraph的资源文件
- app:defaultNavHost="true”属性,可确保您NavHostFragment拦截系统“后退”按钮
- 覆盖AppCompatActivity.onSupportNavigateUp() 并调用NavController.navigateUp
//在Activity的xml中添加fragment
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/nav_graph"
app:defaultNavHost="true"
/>
// 在Activity中设置Navcontroller
override fun onSupportNavigateUp()
= findNavController(nav_host_fragment).navigateUp()
- 代码创建 NavHostFragment实现导航
- 使用NavHostFragment.create() 以编程方式创建NavHostFragment
val finalHost = NavHostFragment.create(R.navigation.example_graph)
supportFragmentManager.beginTransaction()
.replace(R.id.nav_host_fragment, finalHost)
.setPrimaryNavigationFragment(finalHost)
.commit()
4.2、将目标绑定到UI小部件
使用NavController该类导航到目的地,系统提供以下获取NavController的方法:
- NavHostFragment.findNavController(Fragment)
- Navigation.findNavController(Activity, @IdRes int viewId)
- Navigation.findNavController(View)
检索 NavController,使用其 navigate() 方法传入navigation.xml中设置的导航action,执行导航到目标
btnFragment.setOnClickListener {
Navigation.findNavController(btnFragment).navigate(R.id.action_blankFragment_to_secondFragment)
}
除了上面的navigation()方法外,还可以调用NavController.navigateUp() 和 NavController.popBackStack() 方法“向上”或“返回”,或使用 Navigation类的 createNavigateOnClickListener() 便捷方法导航到目标
button.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.next_fragment, null))
执行上面的导航,点击按钮跳转到第二个Fragment
- 将目标驱动绑定到菜单的UI组件
- 通过将目标id与XML中导航抽屉或溢出菜单项的id相同,将目标绑定到导航抽屉和溢出菜单
// 导航抽屉菜单xml文件
<item android:id="@id/secondFragment"
android:title="Second"
android:icon="@drawable/ic_launcher_foreground"
android:menuCategory="secondary"
app:showAsAction="always"
/>
- 使用NavigationUI 类的方法,可以将菜单中的项目连接到NavigationView
val navigationView = findViewById<NavigationView>(R.id.nav_view)
NavigationUI.setupWithNavController(navigationView, findNavController(mainBlankFragment))
运行结果:
- 设置ToolBar
// Set up ActionBar
setSupportActionBar(binding.toolbar)
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
5、导航的其他操作
5.1、在目的地之间传递数据
- Bundle 方法
- 在图表编辑器中,单击接收参数的目标位
- 单击“属性”面板的“参数”部分中的“ Add(+)”
- 双击名称并输入参数的名称
- 按Tab并输入参数的默认值
- 单击此目标之前的操作。参数默认值应包含新添加的参数
- 单击“ 文本”选项卡以切换到XML视图
此时的xml中添加了参数
<argument
android:name="value"
app:type="integer"
android:defaultValue="0" /
代码中,使用navigate() 方法创建一个Bundle并将其传递到目标
val bundle = Bundle()
bundle.putString("name","Blank")
bundle.putInt("number",10)
Navigation.findNavController(btnFragment).navigate(R.id.action_blankFragment_to_secondFragment,bundle)
// 在接收的代码中,使用该 getArguments()方法检索包并使用其内容
val tv = view.findViewById(R.id.textViewAmount)
text.text = "Name = ${arguments?.getString("name")}
Number = ${arguments?.getInt("number")}"
5.2、将目标分组为嵌套导航图
- 可以将一系列目的地分组为导航图中的子图。子图称为“ 嵌套图 ”,而包含图称为“ 根图“
- 要将目标分组到嵌套图中
- 在“曲线图编辑器”中,按住shift并单击要包含在嵌套图形中的目标
- 打开上下文菜单,然后选择“ 移动到嵌套图形” >“ 新建图形”。目标包含在嵌套图中
- 双击嵌套图。将显示嵌套图中的目标,在“目标”列表中,单击“ 根”以返回到根导航图
<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/navigation"
app:startDestination="@id/blankFragment">
<fragment ....../>
<navigation android:id="@+id/navigation2" app:startDestination="@id/blankFragment">
<fragment android:id="@+id/blankFragment" android:name="com.example.administrator.navigation.BlankFragment"
......
</fragment>
</navigation>
</navigation>
此时预览图发生以下变化:
- include
- 可以使用引用其他图形<include>
- 虽然这在功能上与使用嵌套图形相同,但<include>允许您使用其他项目模块或库项目中的图形
下面我们将上面的两个navigation并使用include引用,拆分如下:
- navigation.xml:将两个Fragment的跳转放在navigation中
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
......>
<fragment android:id="@+id/blankFragment" android:name="com.example.administrator.navigation.BlankFragment"
android:label="fragment_blank" tools:layout="@layout/fragment_blank">
......
<action android:id="@+id/action_blankFragment_to_include" app:destination="@id/secondActivity"/> // 跳转到SecondActivity
//<action android:id="@+id/action_blankFragment_to_include" app:destination="@id/include"/> // 跳转到include
**:注意这里无论设置哪个,只要是跳转到include中,就会首先到app:startDestination的界面中
</fragment>
<include app:graph="@navigation/include"/>
</navigation>
- include.xml:将对SecondActivity的跳转放在include中
<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/include" app:startDestination="@id/secondActivity"> // 将include的startDestination设置为SecondActivity所以直接跳到SecondActivity // 必须设置否则会报异常 no start destination defined via app:startDestination
<activity android:id="@+id/secondActivity" android:name="com.example.administrator.navigation.SecondActivity"
android:label="activity_second" tools:layout="@layout/activity_second"/>
</navigation>
5.3、目标创建深层链接
- 为目标分配深层链接:点击右边属性框中的Deep Links 填写Url
<deepLink app:uri="https://cashdog.com/sendmoney"/>
- 接添加intent过滤器
- 对于Android Studio 3.1之前,必须手动添加intent-filter 元素
- 对于Android Studio 3.2+,像Activity元素中添加nav-graph元素
<activity name=".MainActivity">
<nav-graph android:value="@navigation/main_nav" />
</activity>
5.4、目的地之间创建过渡
- 点击导航的连接线,在右侧属性菜单的Transition中修改过度动画
- 修改后的动画会自动添加到Navigation的代码中
此时xml中会自动补充代码:
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim"
5.5、条件导航
- 实施条件导航
- 调用popBackStack()导航回原始目的地(如Login后返回Profile
- 共同目的地
- 程序中多个元素访问公共的目标
- 在“曲线图编辑器”中,单击目标以突出显示目标
- 右键单击目标以显示上下文菜单
- 选择添加操作>全局
此时代码自动填充:
// 在最外层自动添加
<action android:id="@+id/action_global_secondFragment2" app:destination="@id/secondFragment"/>
使用共同目标
viewTransactionsButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Navigation.findNavController(view).navigate(R.id.action_global_mainFragment);
}
});
6、实战
使用Navigation,在Activity实现各自Fragment的导航以及两个Activity间的导航
- 创建SecondActivity的导航文件 second.xml:引入了两个Fragment之间的切换
<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/second" app:startDestination="@id/fragmentT">
<fragment android:id="@+id/fragmentT" android:name="com.example.administrator.navigation.FragmentT"
android:label="fragment_fragment_t" tools:layout="@layout/fragment_fragment_t">
<action android:id="@+id/action_fragmentT_to_fragmentOther" app:destination="@id/fragmentOther"/>
</fragment>
<fragment android:id="@+id/fragmentOther" android:name="com.example.administrator.navigation.FragmentOther"
android:label="fragment_fragment_other" tools:layout="@layout/fragment_fragment_other"/>
</navigation>
- 在navigation文件中添加跳转到second.xml的Action
<action android:id="@+id/action_blankFragment_to_secondActivity2" app:destination="@id/second"/>
// 跳转到second导航
- 在MainActivity的Fragment中调用Action,跳转到SecondActivity
btnActivity.setOnClickListener {
val option = ActivityOptionsCompat.makeSceneTransitionAnimation(activity as Activity, imageView, "image")
val exeras = ActivityNavigator.Extras(option)
Navigation.findNavController(btnFragment)
.navigate(R.id.action_blankFragment_to_secondActivity2, null, null, exeras)
到此Navigation的使用介绍就结束了,Navigation的使用确实使应用导航的设计变得更加简单,还有个更大的好处就是阅读性极好,在以往我们查看项目时,要想得到导航需要在代码中就行查看,现在直接可以查看xml文件即可,是不是很方便,好了Android Jetpack组件快要介绍完了,敬请期待