原文地址:https://blog.csdn.net/weixin_42009003/article/details/80702990
fragment是官方给出处理页面功能块分离重用的方案,它有完整的生命周期,几乎可以做到activity可以做到的一切,并且同样的界面Activity占用内存比Fragment要多,总之用fragment好处多多,为啥也有一些前辈大神都建议在项目中尽量避免使用fragment,比如 Square:从今天开始抛弃Fragment吧!不得不说它也有很多坑的地方,如果你只是使用viewpager+fragment方式可以忽略。如果想要实现单activity多fragment的方式,就需要自己管理fragment栈,github上也有很多大神呕心沥血写出很多针对管理fragment的经验,能帮助我们解决很多问题,可以参考fragmentation ,但是这里不是讲fragmentation框架,大家有兴趣可以去看他的源码,还是非常赞的。
这里要讲的是谷歌推出navigation的框架,这里只是讲它的使用,对源码感兴趣的同学可以看android-navigation
导包目前是1.0.0的测试版
在project的build.gradle中添加
classpath 'android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha02'
在app的build.gradle中添加
apply plugin: 'androidx.navigation.safeargs'
implementation 'android.arch.navigation:navigation-fragment:1.0.0-alpha02' implementation 'android.arch.navigation:navigation-ui:1.0.0-alpha02'
然后在res资源中增加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"
app:startDestination="@+id/launcher_home">
android:id="@+id/launcher_home"
android:name="com.example.android.codelabs.navigation.MainFragment"
android:label="@string/home"
tools:layout="@layout/main_fragment">
android:name="step"
app:type="integer"
android:defaultValue="1" />
android:id="@+id/next_action"
app:destination="@+id/flow_step_one"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right"
app:popUpTo="@id/launcher_home"/>
navigation 标签中有一个属性app:startDestination="@+id/launcher_home" 指定默认启动的fragmentid
fragment标签中id和name就不多说了,在fragment的内部标签
argument 一看便知是带参
还有一个action标签,里面有两个属性destination 定义目标fragment的Id,popUpTo是回退的fragment id
fragment开发和之前一样,在宿主activity里需要重写onSupportNavigateUp方法去启动fragment,如下
@Override public boolean onSupportNavigateUp() { return Navigation.findNavController(getActivity(), R.id.host_fragment).navigateUp(); }
fragment之间跳转调用
Navigation.findNavController(v).navigate(R.id.action_test2);它会根据当前fragment在navigation.xml里的定义的fragment节点,解析获取它的fragment信息以及action和argument节点,拿到所有跳转需要所有的数据。
Navigation.findNavController(v).navigateUp();
是fragment栈内回退方法,如果fragment栈内没有则执行activity的回退方法。
这样就可以开始开发单activity多fragment的app了,是不是有点小激动?
转入开发,你会发现navigation.xml里面配置的东西是不是有点多?如果action不配置转场动画,它转换是可以,但是体验只能呵呵了,还有destination 目标fragment,不配置它怎么跳转?这样fragment多起来岂不要炸了?带着疑问看源码!
Navigation.findNavController(v)返回一个控制器,它的navigate方法入参有两种,一个是
public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions)
另一个可以传入一个NavDirections,这是个什么东东???经过一番查找,发现了它的所在,它是我们配置navigation.xml里每个fragment的action时,工具为我们生成了一些类。文件名以fragment名后追加Directions的类
public class MainFragmentDirections { public static Action_test1 action_test1() { return new Action_test1(); } public static class Action_test1 implements NavDirections { public Action_test1() { } public Bundle getArguments() { Bundle __outBundle = new Bundle(); return __outBundle; } public int getActionId() { return com.winky.douniwan.R.id.action_test1; } } }
xml配置一大堆就算了,还生成一堆类,顿时感觉到深深的恶意,还要用吗?有没有方法不用配置那个action?而且你看
public void navigate(@NonNull NavDirections directions, @Nullable NavOptions navOptions) { navigate(directions.getActionId(), directions.getArguments(), navOptions); }
方法最终调用和传id进去是一样的,这样只能再仔细看看这个方法,先贴源码,
public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions) { NavDestination currentNode = mBackStack.isEmpty() ? mGraph : mBackStack.peekLast(); if (currentNode == null) { throw new IllegalStateException("no current navigation node"); } @IdRes int destId = resId; final NavAction navAction = currentNode.getAction(resId); if (navAction != null) { if (navOptions == null) { navOptions = navAction.getNavOptions(); } destId = navAction.getDestinationId(); } if (destId == 0 && navOptions != null && navOptions.getPopUpTo() != 0) { popBackStack(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive()); return; } if (destId == 0) { throw new IllegalArgumentException("Destination id == 0 can only be used" + " in conjunction with navOptions.popUpTo != 0"); } NavDestination node = findDestination(destId); if (node == null) { final String dest = NavDestination.getDisplayName(mContext, destId); throw new IllegalArgumentException("navigation destination " + dest + (navAction != null ? " referenced from action " + NavDestination.getDisplayName(mContext, resId) : "") + " is unknown to this NavController"); } if (navOptions != null) { if (navOptions.shouldClearTask()) { // Start with a clean slate popBackStack(mGraph.getId(), true); } else if (navOptions.getPopUpTo() != 0) { popBackStack(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive()); } } node.navigate(args, navOptions); }
之前传入的actionId 就是第一个参数,bundle显然是附带参数,第三个参数里面存放的是进场退场动画属性,它先会根据这个resid去找action节点解析,如果不为空则将配置action里面的属性全部取出来,会根据action里面的destination去查找目标fragment,如果NavAction 为空!它就使用入参的resid去找fragment!!!谷歌工程师想的还是非常周到的,这样就可以不必在navigation.xml里去为每个fragment配置action和argument了。
然后去掉fragment配置的action,xml里面简洁多了,基本和manifest里差不多,只增加一个fragment标签,为它配置类路径及id即可。
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"
app:startDestination="@id/fragment_main">
android:id="@+id/fragment_main"
android:name="com.winky.douniwan.ui.fragment.MainFragment" />
android:id="@+id/fragment_test1"
android:name="com.winky.douniwan.ui.fragment.FragmentTest1" />
android:id="@+id/fragment_test2"
android:name="com.winky.douniwan.ui.fragment.FragmentTest2" />
android:id="@+id/fragment_test3"
android:name="com.winky.douniwan.ui.fragment.FragmentTest3" />
跳转时
NavOptions options = new NavOptions.Builder() .setEnterAnim(R.anim.slide_right_in) .setExitAnim(R.anim.slide_left_out) .setPopEnterAnim(R.anim.slide_left_in) .setPopExitAnim(R.anim.slide_right_out) .build(); Navigation.findNavController(v).navigate(R.id.fragment_test1, null, options);传入fragment的id,附带参数,以及转场动画。NavOptions 可以缓存全局使用,是不是很棒!如果本文对你使用navigation有帮助,请评论赞个呗!