内容简介:Navigation可以目前看做Google对于之前的Fragment的不满, 重新搭建的一套Fragment管理框架. 但是Navgation未来应该不仅限于Fragment导航.并且Navigation可以和BottomNavigationView/NavigationView/Toolbar等结合使用, 不再需要去写冗余代码管理Fragment.并且具备完善的Fragment回退栈管理.
Navigation可以目前看做Google对于之前的Fragment的不满, 重新搭建的一套Fragment管理框架. 但是Navgation未来应该不仅限于Fragment导航.
并且Navigation可以和BottomNavigationView/NavigationView/Toolbar等结合使用, 不再需要去写冗余代码管理Fragment.
并且具备完善的Fragment回退栈管理.
如果是使用 Java 语言我不推荐使用任何新框架了, 就自己玩自己的吧.
ktx (除基本依赖外还包含一些Kotlin新特性的函数)
implementation 'androidx.navigation:navigation-fragment-ktx:2.0.0' implementation 'androidx.navigation:navigation-ui-ktx:2.0.0' 复制代码
一些常用的关键字解释
navigation(nv): 导航, 即Navigation框架的fragment返回栈
graph: 指一个描述返回栈关系的xml文件 (NavigationResourceFile) 也是布局编辑器用于显示图表的界面数据来源
destination: 目标, 即在返回栈中要跳转的新页面
pop: 弹出栈, 会弹出所有不符合目标的页面, 直至找到 目标页面
(默认情况不弹出目标页面也可以设置), 可以理解为Fragment的singleTask模式
navHost: 即所有页面的容器. 类似网页中的host, 所有path路径都是在host之后跟随, host固定不变.
布局编辑器
点击NavResourceFile中的Design即可查看布局编辑器, 布局编辑器分为三栏.
左侧是已添加的导航, 中间是页面浏览, 中间栏的 工具 栏可以创建和快速添加标签以及整理页面, 右侧属性栏方便添加属性.
navigation
这是个嵌套的图表, 可以点击打开新的图表页面.
Activity布局中
<LinearLayout .../> <androidx.appcompat.widget.Toolbar .../> <fragment android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:id="@+id/my_nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" app:navGraph="@navigation/mobile_navigation" app:defaultNavHost="true" /> <com.google.android.material.bottomnavigation.BottomNavigationView .../> </LinearLayout> 复制代码
android:name="androidx.navigation.fragment.NavHostFragment" 固定写法 app:navGraph 指定navigation资源文件, 也可以不指定后面通过代码中动态设置 app:defaultNavHost 是否拦截返回键事件, false表示不需要回退栈. 复制代码
res目录创建 AndroidResourceFile 选择 Navigation. 然后 new-> NavigationResourceFile
navigation
app:startDestination="@+id/home_dest" 指定初始目标 复制代码
navigation可以嵌套navigation标签.
在布局编辑器中会显示为
嵌套navigation无法互相关联
<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_global" app:startDestination="@id/mainFragment"> <!-- <action android:id="@+id/global_action" app:destination="@id/navigation" />--> <fragment android:id="@+id/mainFragment" android:name="com.example.frameexample.MainFragment" android:label="fragment_main" tools:layout="@layout/fragment_main"> <action android:id="@+id/action_mainFragment_to_personInfoFragment" app:destination="@id/settingFragment" /> </fragment> <navigation android:id="@+id/navigation" app:startDestination="@id/settingFragment"> <fragment android:id="@+id/settingFragment" android:name="com.example.frameexample.SettingFragment" android:label="fragment_setting" tools:layout="@layout/fragment_setting" /> </navigation> </navigation> 复制代码
上面的 mainFragment
无法直接 app:destination="@id/settingFragment"
这会导致运行错误. 只能先导航到 navigation
.
fragment
android:id android:name 目标要实例化的fragment完全限定类名 tools:layout 用于显示在布局编辑器 android:label 用于后面绑定Toolbar等自动更新标题 复制代码
argument
android:name="myArg" app:argType="integer" android:defaultValue="0" 复制代码
参数名称 参数类型 参数默认值
在跳转导航页面的时候会自动在argument中带上参数(要求指定参数默认值). 数组和Paraclable/Serializable不支持默认值设置, 通过下面要讲的SafeArg可以在编译器校验参数类型安全问题.
action 动作 用于页面跳转时指定目标页面
android:id="@+id/next_action" 动作id app:destination="@+id/flow_step_two_dest"> 目标页面 app:popUpTo="@id/home_dest" 当前属于弹出栈 app:popUpToInclusive="true/false" 弹出栈是否包含目标 app:launchSingleTop="true/false" 是否开启singleTop模式 app:enterAnim="" app:exitAnim="" 导航动画 app:popEnterAnim="" app:popExitAnim="" 弹出栈动画 复制代码
如果从导航页面到新的Activity页面, 动画不支持. 请使用默认的Activity设置动画去支持.
全局动作
一般情况下NavController只能使用当前Fragment在NavXML中声明的子标签 action
, 但是可以通过直接给 navigation
标签创建子标签 action
实现 全局动作 , 即每个Fragment都能使用的动作.
给navigation添加action子标签时要求给navigation指定熟悉 android:id
占位页面如果运行时没有指定Class并且导航到该占位页面时会抛出异常
类关系
涉及到的类关系
- NavController 控制导航的跳转和弹出栈
- NavOptions 控制跳转过程中的配置选项, 例如动画和singleTop模式
- Navigation 工具类 创建点击事件或者获取控制器
- NavHostFragment 导航的容器, 可以设置和获取导航图(NavGraph)
- NavGraph 用于描述导航中页面关系的对象 可以增删改查页面,设置起始页等
- NavigationUI 用于将导航和一系列菜单控件自动绑定的工具类
- Navigator 页面的根接口, 如果想创建一个新的类型页面就要自定义他
- NavDeepLinkBuilder 构建一个能打开导航页面的Intent
NavController
NavController用于跳转页面和参数传递等控制, 可以通过扩展函数得到实例.
Fragment.findNavController() View.findNavController() Activity.findNavController(viewId: Int) 复制代码
导航
public final void navigate (int resId) public final void navigate (int resId, Bundle args) public void navigate (int resId, Bundle args, NavOptions navOptions) public void navigate (NavDirections directions) public void navigate (NavDirections directions, NavOptions navOptions) public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) private void navigate(@NonNull NavDestination node, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) public boolean navigateUp () 返回到上一个页面 public boolean navigateUp (DrawerLayout drawerLayout) public boolean navigateUp (AppConfiguration appConfiguration) 复制代码
resId
可以是NavXML中的 action
或者 destination
标签id, 如果是action则会附带action的选项, 如果是页面destination则不会附带destination标签下的子标签action(写了白写).
args
即需要在fragment之间传递的Bundle参数, 但是导航还支持另外一种插件形式的传递参数方式-安全参数SafeArgs, 后面提到.
navOptions 即导航页面一些配置选项(例如动画)
navigatorExtras
目前是用于支持转场动画的共享元素.
ActivityNavigator和FragmentNavigator内部都实现了Navigator.Extras
通过扩展函数可以快速创建
fun FragmentNavigatorExtras(vararg sharedElements: Pair<View, String>) = FragmentNavigator.Extras.Builder().apply { sharedElements.forEach { (view, name) -> addSharedElement(view, name) } }.build() fun ActivityNavigatorExtras(activityOptions: ActivityOptionsCompat? = null, flags: Int = 0) = ActivityNavigator.Extras.Builder().apply { if (activityOptions != null) { setActivityOptions(activityOptions) } addFlags(flags) }.build() 复制代码
可以从源码看到内部都是使用的Extras.Builder构造器创建的.
示例
<fragment android:id="@+id/home_dest" android:name="com.example.android.codelabs.navigation.HomeFragment" android:label="@string/home" tools:layout="@layout/home_fragment"> <action android:id="@+id/next_action" app:destination="@+id/flow_step_one_dest" app:enterAnim="@anim/slide_in_right" app:exitAnim="@anim/slide_out_left" app:popEnterAnim="@anim/slide_in_left" app:popExitAnim="@anim/slide_out_right" /> </fragment> 复制代码
上面你给action指定动画, 但是如果你使用的navigate中的参数resId不是 R.id.next_action
而是 R.id.home_dest
. 那么你这个action相当于不生效.
弹出栈, 即从Nav回退栈中清除Fragment.
public boolean popBackStack (int destinationId, 目标id boolean inclusive) // 是否包含参数目标 public boolean popBackStack () 弹出当前Fragment 复制代码
监听导航
public void addOnNavigatedListener (NavController.OnNavigatedListener listener) public void removeOnNavigatedListener (NavController.OnNavigatedListener listener) 复制代码
回调
public interface OnDestinationChangedListener { /** * 导航完成以后回调函数(但是可能动画还在播放中) * * @param 控制导航到目标的导航控制器NavController * @param 目标页面 * @param 导航到目标页面的参数 */ void onDestinationChanged(@NonNull NavController controller, @NonNull NavDestination destination, @Nullable Bundle arguments); } 复制代码
状态保存和恢复
public Bundle saveState () public void restoreState (Bundle navState) 复制代码
public NavDestination getCurrentDestination () public NavGraph getGraph () public NavigatorProvider getNavigatorProvider () 复制代码
NavOptions
属于导航时的附加选项设置, 相当于代码动态实现了NavigationResourceFile中的 <action>
标签的属性设置.
目前功能只有设置动画和栈管理
提供一个DSL作用域
fun navOptions(optionsBuilder: NavOptionsBuilder.() -> Unit): NavOptions 复制代码
示例
val options = navOptions { anim { enter = R.anim.slide_in_right exit = R.anim.slide_out_left popEnter = R.anim.slide_in_left popExit = R.anim.slide_out_right } } findNavController().navigate(R.id.flow_step_one_dest, null, options) 复制代码
Navigation
工具类
目前只支持创建点击事件和获取控制器
public static View.OnClickListener createNavigateOnClickListener (int resId) 快速创建一个跳转到目标的View.OnClickListenner public static View.OnClickListener createNavigateOnClickListener (int resId, Bundle args) 以下三个函数都被扩展函数实现 public static NavController findNavController (Activity activity, int viewId) public static NavController findNavController (View view) public static void setViewNavController (View view, NavController controller) 复制代码
NavHostFragment
该对象为Navigation提供一个容器
一般使用情况是在布局中直接定义, 但是也可以通过代码构建实例, 然后通过代码创建视图(例如ViewPager等)
public static NavHostFragment create (int graphResId) 复制代码
得到NavController实例
public static NavController findNavController (Fragment fragment) public NavController getNavController () 复制代码
NavigationUI
该工具类负责绑定视图控件和导航, 所有绑定都只需要id对应即可自动导航.
设置导航到新页面时自动更新标题文字
fun AppCompatActivity.setupActionBarWithNavController( navController: NavController, drawerLayout: DrawerLayout? ) fun AppCompatActivity.setupActionBarWithNavController( navController: NavController, configuration: AppBarConfiguration = AppBarConfiguration(navController.graph) ) 复制代码
这里出现个参数AppBarConfiguration, 用于配置Toolbar/ActionBar/CollapsingToolbarLayout.
构造器模式使用Builder创建实例
AppBarConfiguration.Builder(NavGraph navGraph) 顶级目标是NavGraph的起始页面 AppBarConfiguration.Builder(Menu topLevelMenu) 菜单包含的全部是顶级目标 AppBarConfiguration.Builder(int... topLevelDestinationIds) 顶级目标集合 AppBarConfiguration.Builder(Set<Integer> topLevelDestinationIds) 复制代码
顶级目标: 顶级目标即表示为回退栈最底位置, 无法再返回, 故可以理解为不需要返回键导航的页面(Toolbar等就不会显示返回箭头).
函数
AppBarConfiguration.Builder setDrawerLayout(DrawerLayout drawerLayout) 绑定Toolbar同时绑定一个DrawerLayout联动 AppBarConfiguration.Builder setFallbackOnNavigateUpListener(AppBarConfiguration.OnNavigateUpListener fallbackOnNavigateUpListener) AppBarConfiguration build() 复制代码
AppBarConfiguration.OnNavigateUpListener 该回调接口会在每次点击向上导航时回调
public interface OnNavigateUpListener { /** * 回调处理向上导航 * * @return 返回true表示向上导航, false不处理 */ boolean onNavigateUp(); } 复制代码
Toolbar也可以绑定Nav自动更新对应页面的标题
fun Toolbar.setupWithNavController( navController: NavController, drawerLayout: DrawerLayout? ) { NavigationUI.setupWithNavController(this, navController, AppBarConfiguration(navController.graph, drawerLayout)) } fun Toolbar.setupWithNavController( navController: NavController, drawerLayout: DrawerLayout? ) { NavigationUI.setupWithNavController(this, navController, AppBarConfiguration(navController.graph, drawerLayout)) } 复制代码
CollapsingToolbarLayout
fun CollapsingToolbarLayout.setupWithNavController( toolbar: Toolbar, navController: NavController, configuration: AppBarConfiguration = AppBarConfiguration(navController.graph) ) fun CollapsingToolbarLayout.setupWithNavController( toolbar: Toolbar, navController: NavController, drawerLayout: DrawerLayout? ) 复制代码
绑定菜单条目点击自动导航
fun MenuItem.onNavDestinat ionSelected(navController: NavController): Boolean = NavigationUI.onNavDestinationSelected(this, navController) 复制代码
在onOptionsItemSelected
override fun onOptionsItemSelected(item: MenuItem): Boolean { return item.onNavDestinationSelected(findNavController(R.id.my_nav_host_fragment)) } 复制代码
Nav绑定NavigationView菜单
fun NavigationView.setupWithNavController(navController: NavController) { NavigationUI.setupWithNavController(this, navController) } fun BottomNavigationView.setupWithNavController(navController: NavController) { NavigationUI.setupWithNavController(this, navController) } 复制代码
NavGraph
表示一个导航图对象(可以想象为布局编辑器中的导航), 具备增删改查, 设置起始目标功能. 其为在代码中动态构建Graph而存在(之前都是提到在XML中通过布局编辑器设置导航图).
由NavController填充XML创建
public NavGraph inflate (int graphResId) 加载NavigationResourceFile public NavGraph inflateMetadataGraph () 从AndroidManifest中读取Graph 复制代码
增删改查
void addAll(NavGraph other) void addDestination(NavDestination node) void addDestinations(Collection<NavDestination> nodes) void addDestinations(NavDestination... nodes) void clear() void remove(NavDestination node) NavDestination findNode(int resid) 通过destination的id来查找对象 void setStartDestination(int startDestId) int getStartDestination() 设置目标页面 Iterator<NavDestination> iterator() 复制代码
最终将它设置给NavController
public void setGraph (int graphResId) 直接通过XML设置 public NavGraph getGraph () public void setGraph (NavGraph graph) 复制代码
DeepLink
Nav声明一个DeepLink(深层链接)只需要给Fragment添加一个子标签即可
首先要在AndroidManifest中的activity中添加一个子标签 nav-graph
为NavRes注册DeepLink.
<activity> <nav-graph android:value="@navigation/mobile_navigation" /> </activity> 复制代码
深层链接
<deepLink app:uri="www.example.com/{myarg}" /> 复制代码
通过ADB测试
adb shell am start -a android.intent.action.VIEW -d "http://www.example.com/2334456" 复制代码
2334456
即传递过去的参数
{}
包裹的字段属于变量, *
可以匹配任意字符
下面介绍通过NavDeepLinkBuilder创建Intent来开启目标页面
public NavDeepLinkBuilder createDeepLink () public boolean onHandleDeepLink (Intent intent) 复制代码
NavDeepLinkBuilder
NavDeepLinkBuilder setArguments(Bundle args) NavDeepLinkBuilder setDestination(int destId) NavDeepLinkBuilder setGraph(int navGraphId) NavDeepLinkBuilder setGraph(NavGraph navGraph) 复制代码
生成PendingIntent可以用于开启界面(例如传给Notification)
PendingIntent createPendingIntent() TaskStackBuilder createTaskStackBuilder() 复制代码
SafeArgs
安全类型插件, 基于Gradle实现的插件.
他的目的就是根据你在NavRes中声明argument标签生成工具类, 然后全部使用工具类而不是字符串去获取和设置参数. 避免前后两者参数类型不一致而崩溃.
插件
buildscript { repositories { jcenter() google() } dependencies { classpath 'android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha01' } } 复制代码
应用插件
apply plugin: 'androidx.navigation.safeargs' 复制代码
在NavigationResourceFile中声明 <argument>
标签后会自动生成类
插件会根据页面自动生成 *Directions
类, 该类包含该页面能使用的所有跳转动作(包含全局动作和自身动作).
生成类会包含一个有规则的静态函数用于获取 Directions
的实现类(*Directions的静态内部类), 函数名称规则为
action<页面名称>To<目标页面名称> 全局动作名称规则则为: 动作id的变量命名法 例: public static ActionMainFragmentToPersonInfoFragment actionMainFragmentToPersonInfoFragment() 复制代码
这里看下 NavDirections
接口的含义
public interface NavDirections { /** * 返回动作id * * @return id of an action */ @IdRes int getActionId(); /** * 返回目标参数 */ @NonNull Bundle getArguments(); } 复制代码
可以总结为 包含携带参数和动作.
但是如果navRes中还包含 <argument>
标签, 则还会生成对应的 *Args
类, 并且上面提到的自动生成的 *Directions
中的NavDirection静态内部类还会生成参数的构造和访问器
还有一系列 hashCode/equals/toString 函数
完整的导航页面且传递数据写法
导航至目标页面
val action = MainFragmentDirections.actionMainFragmentToPersonInfoFragment().setName("姜涛") findNavController().navigate(action) 复制代码
在目标页面接受数据
tv.text = PersonInfoFragmentArgs.fromBundle(arguments!!).name 复制代码
这里可以再次想下什么是安全类型参数.
Navigator
导航的本质是一种回退栈机制, 而不仅仅限于Fragment.
Navigator
这是所有导航页面的抽象类, 自定义他可以实现在布局编辑器导航画板中实现其他方式的视图页面导航, 例如不再仅仅支持Fragment和Activity, 你还可以支持View.
总结
关于Google推动的SingleActivity构建应用我说下我的看法, 我认为整个应用使用一个Activity还是比较麻烦的.
列举下所谓麻烦
- Fragment无法设置默认动画, 动画统一管理起来很麻烦
- 所谓Fragment减少内存开销用户都无法感知
- 很多框架还是基于Activity实现的(例如路由,状态栏), 可能某些项目架构会受到局限
我认为Navigation替代FragmentManger还是得心应手的, 并且导航图看起来也很有逻辑感.
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 容器管理方面的四大考量
- 最全面的推荐系统评估方法介绍
- Hackertarget:一款帮助组织发现攻击面的强大工具
- iOS面向切面的TableView-AOPTableView 原 荐
- 测试Android应用程序的逆向方法和寻找攻击面的技巧
- 最全面的CQRS和事件溯源介绍 - Software House ASC
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。