在JetPack中有一个组件是Navigation,顾名思义它是一个页面导航组件,相对于其他的第三方导航,不同的是它是专门为Fragment的页面管理所设计的。它对于单个Activity的App来说非常有用,因为以一个Activity为架构的App页面的呈现都是通过不同的Fragment来展示的。所以对于Fragment的管理至关重要。通常的实现都要自己维护Fragment之间的栈关系,同时要对Fragment的Transaction操作非常熟悉。为了降低使用与维护成本,所以就有了今天的主角Navigation。
对于Navigation的使用,我将其归纳于以下四点:
-
Navigation的基本配置
-
Navigation的跳转与数据传递
-
Navigation的页面动画
-
Navigation的deepLink
配置
在使用之前需要引入Navigation的依赖,然后我们需要为Navigation创建一个配置文件,它将位于res/navigation/nav_graph.xml。为了方便理解文章中的代码,我写了一个Demo,大家可以通过Android精华录查看。
在我的Demo中打开nav_graph.xml你将清晰的看到它们页面间的关系纽带
一共有6个页面,最左边的为程序入口页面,它们间的线条指向为它们间可跳转的方向。
我们再来看它们的xm配置👇
…
页面标签主要包含navigation、fragment与action
-
navigation: 定义导航栈,可以进行嵌套定义,各个navigation相互独立。它有一个属性startDestination用来定义导航栈的根入口fragment
-
fragment: 顾名思义fragment页面。通过name属性来定义关联的fragment
-
action: 意图,可以理解为Intent,即跳转的行为。通过destination来关联将要跳转的目标fragment。
以上是nav_graph.xml的基本配置。
在配置完之后,我们还需要将其关联到Activity中。因为所有的Fragment都离不开Activity。
Navigation为我们提供了两个配置参数: defaultNavHost与navGraph,所以在Activity的xml中需要如下配置👇
<?xml version="1.0" encoding="utf-8"?>-
defaultNavHost: 将设备的回退操作进行拦截,并将其交给Navigation进行管理。
-
navGraph: Navigation的配置文件,即上面我们配置的nav_graph.xml文件
除此之外,fragment的name属性必须为NavHostFragment,因为它会作为我们配置的所有fragment的管理者。具体通过内部的NavController中的NavigationProvider来获取Navigator抽象实例,具体实现类是FragmentNavigator,所以最终通过它的navigate方法进行创建我们配置的Fragment,并且添加到NavHostFragment的FrameLayout根布局中。
此时如果我们直接运行程序后发现已经可以看到入口页面WelcomeFragment
但点击register等操作你会发现点击跳转无效,所以接下来我们需要为其添加跳转
跳转
由于我们之前已经在nav_graph.xml中定义了action,所以跳转的接入非常方便,每一个action的关联跳转只需一行代码👇
class WelcomeFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_welcome, container, false).apply {
register_bt.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_go_to_register_page))
stroll_bt.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_go_to_order_list_page))
}
}
}
代码中的id就是配置的action的id,内部原理是先获取到对应的NavController,通过点击的view来遍历找到最外层的parent view,因为最外层的parent view会在配置文件导入时,即NavHostFragment中的onViewCreated方法中进行关联对应的NavController👇
@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”);
}
Navigation.setViewNavController(view, mNavController);
// When added programmatically, we need to set the NavController on the parent - i.e.,
// the View that has the ID matching this NavHostFragment.
if (view.getParent() != null) {
View rootView = (View) view.getParent();
if (rootView.getId() == getId()) {
Navigation.setViewNavController(rootView, mNavController);
}
}
}
然后再调用navigate进行页面跳转处理,最终通过FragmentTransaction的replace进行Fragment替换👇
-------------- NavController ------------------
private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
boolean popped = false;
if (navOptions != null) {
if (navOptions.getPopUpTo() != -1) {
popped = popBackStackInternal(navOptions.getPopUpTo(),
navOptions.isPopUpToInclusive());
}
}
Navigator navigator = mNavigatorProvider.getNavigator(
node.getNavigatorName());
Bundle finalArgs = node.addInDefaultArgs(args);
---- 关键代码 -------
NavDestination newDest = navigator.navigate(node, finalArgs,
navOptions, navigatorExtras);
…
}
-------------- FragmentNavigator ------------------
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
if (mFragmentManager.isStateSaved()) {
Log.i(TAG, “Ignoring navigate() call: FragmentManager has already”
- " saved its state");
return null;
}
String className = destination.getClassName();
if (className.charAt(0) == ‘.’) {
className = mContext.getPackageName() + className;
}
final Fragment frag = instantiateFragment(mContext, mFragmentManager,
className, args);
frag.setArguments(args);
final FragmentTransaction ft = mFragmentManager.beginTransaction();
int enterAnim = navOptions != null ? navOptions.getEnterAnim() : -1;
int exitAnim = navOptions != null ? navOptions.getExitAnim() : -1;
int popEnterAnim = navOptions != null ? navOptions.getPopEnterAnim() : -1;
int popExitAnim = navOptions != null ? navOptions.getPopExitAnim() : -1;
if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
enterAnim = enterAnim != -1 ? enterAnim : 0;
exitAnim = exitAnim != -1 ? exitAnim : 0;
popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
}
------ 关键代码 ------
ft.replace(mContainerId, frag);
ft.setPrimaryNavigationFragment(frag);
…
}
源码就分析到这里了,如果需要深入了解,建议阅读NavHostFragment、NavController、NavigatorProvider与FragmentNavigator
传参
以上是页面的无参跳转,那么对于有参跳转又该如何呢?
大家想到的应该都是bundle,将传递的数据填入到bundle中。没错Navigator提供的navigate方法可以进行传递bundle数据👇
findNavController().navigate(R.id.action_go_to_shop_detail_page, bundleOf(“title” to “I am title”))
这种传统的方法在传递数据类型上并不能保证其一致性,为了减少人为精力上的错误,Navigation提供了一个Gradle插件,专门用来保证数据的类型安全。
使用它的话需要引入该插件,方式如下👇
buildscript {
repositories {
google()
}
dependencies {
def nav_version = “2.1.0”
classpath “androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version”
}
}
最后再到app下的build.gradle中引入该插件👇
apply plugin: “androidx.navigation.safeargs.kotlin”
而它的使用方式也很简单,首先参数需要在nav_graph.xml中进行配置。👇
现在我们从ShopListFragment跳转到ShopDetailFragment,需要在ShopListFragment的对应action中添加argument,声明对应的参数类型与参数名,也可以通过defaultValue定义参数的默认值与nullable标明是否可空。对应的ShopDetailFragment接收参数也是一样。
另外popUpTo与popUpToInclusive属性是为了实现跳转到CartFragment时达到SingleTop效果。
下面我们直接看在代码中如何使用这些配置的参数,首先是在ShopListFragment中👇
holder.item.setOnClickListener(Navigation.createNavigateOnClickListener(ShopListFragmentDirections.actionGoToShopDetailPage(shopList[position])))
还是创建一个createNavigateOnClickListener,只不过现在传递的不再是跳转的action id,而是通过插件自动生成的ShopListFragmentDirections.actionGoToShopDetailPage方法。一旦我们如上配置了argument,插件就会自动生成一个以[类名]+Directions的类,而自动生成的类本质是做了跳转与参数的封装,源码如下👇
class ShopListFragmentDirections private constructor() {
private data class ActionGoToShopDetailPage(val title: String) : NavDirections {
override fun getActionId(): Int = R.id.action_go_to_shop_detail_page
override fun getArguments(): Bundle {
val result = Bundle()
result.putString(“title”, this.title)
return result
}
}
companion object {
fun actionGoToShopDetailPage(title: String): NavDirections = ActionGoToShopDetailPage(title)
}
}
本质是将action id与argument封装成一个NavDirections,内部通过解析它来获取action id与argument,从而执行跳转。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
尾声
你不踏出去一步,永远不知道自己潜力有多大,千万别被这个社会套在我们身上的枷锁给捆住了,30岁我不怕,35岁我一样不怕,去做自己想做的事,为自己拼一把吧!不试试怎么知道你不行呢?
改变人生,没有什么捷径可言,这条路需要自己亲自去走一走,只有深入思考,不断反思总结,保持学习的热情,一步一步构建自己完整的知识体系,才是最终的制胜之道,也是程序员应该承担的使命。
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
,也是程序员应该承担的使命。
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-OOlFvZZJ-1713775766814)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!