最全面的Navigation的使用指南

栏目: IOS · Android · 发布时间: 5年前

内容简介: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固定不变.

布局编辑器

最全面的Navigation的使用指南

点击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无法互相关联

<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

最全面的Navigation的使用指南

占位页面如果运行时没有指定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 类, 该类包含该页面能使用的所有跳转动作(包含全局动作和自身动作).

最全面的Navigation的使用指南

生成类会包含一个有规则的静态函数用于获取 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静态内部类还会生成参数的构造和访问器

最全面的Navigation的使用指南

还有一系列 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还是比较麻烦的.

列举下所谓麻烦

  1. Fragment无法设置默认动画, 动画统一管理起来很麻烦
  2. 所谓Fragment减少内存开销用户都无法感知
  3. 很多框架还是基于Activity实现的(例如路由,状态栏), 可能某些项目架构会受到局限

我认为Navigation替代FragmentManger还是得心应手的, 并且导航图看起来也很有逻辑感.


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

人人都是架构师:分布式系统架构落地与瓶颈突破

人人都是架构师:分布式系统架构落地与瓶颈突破

高翔龙 / 电子工业出版社 / 2017-5 / 69

《人人都是架构师:分布式系统架构落地与瓶颈突破》并没有过多渲染系统架构的理论知识,而是切切实实站在开发一线角度,为各位读者诠释了大型网站在架构演变过程中出现一系列技术难题时的解决方案。《人人都是架构师:分布式系统架构落地与瓶颈突破》首先从分布式服务案例开始介绍,重点为大家讲解了大规模服务化场景下企业应该如何实施服务治理;然后在大流量限流/消峰案例中,笔者为大家讲解了应该如何有效地对流量实施管制,避......一起来看看 《人人都是架构师:分布式系统架构落地与瓶颈突破》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

随机密码生成器
随机密码生成器

多种字符组合密码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码