由于使用了Navigation,导致Fragment的创建行为完全交给了系统。也就是说,以前的那种通过#Fragment.newInstance(Args...)方式传递参数的方式就被切断了,没有办法快乐的在fragment之间传参了~~。但是不要担心,google爸爸早就帮我们想好了方式,下面我们来一条一条的看看吧:
这个是Navigation中提供的最原始的一种参数传递的方法,我们只需要在使用这个函数导航的时候,将原先需要在newInstance()中传递的bundle对象放在改方法的第二个参数中。系统就会帮我们将bundle传递到目标fragment/activity的argument/Intent中,只需要和之前一样,在跳转到的fragment/activity中调用getArgument()/getIntent(),就可以获得我们传递的Bundle对象。我们来看看源码,探索下系统到底怎么是帮我们实现的吧!(没有兴趣的同学可以直接跳过)
public final void navigate(@IdRes int resId, @Nullable Bundle args) { navigate(resId, args, null); }
可以看到在这个方法体中系统只是简单的帮我们做了转调。我们继续:
NavController.java:
public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions) { ... node.navigate(args, navOptions); }
NavDestination.java:
public void navigate(@Nullable Bundle args, @Nullable NavOptions navOptions) { Bundle defaultArgs = getDefaultArguments(); Bundle finalArgs = new Bundle(); finalArgs.putAll(defaultArgs); if (args != null) { finalArgs.putAll(args); } mNavigator.navigate(this, finalArgs, navOptions); }
public abstract void navigate(@NonNull D destination, @Nullable Bundle args, @Nullable NavOptions navOptions);
最后我们可以看到,它指向了到了一个抽象类(Navigator)中的抽象方法。
这个抽象方法具体有3个主要实现:
1.ActivityNavigator.navigate(...):
@Override public void navigate(@NonNull Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions) { if (destination.getIntent() == null) { throw new IllegalStateException("Destination " + destination.getId() + " does not have an Intent set."); } Intent intent = new Intent(destination.getIntent()); if (args != null) { intent.putExtras(args); String dataPattern = destination.getDataPattern(); if (!TextUtils.isEmpty(dataPattern)) { // Fill in the data pattern with the args to build a valid URI StringBuffer data = new StringBuffer(); Pattern fillInPattern = Pattern.compile("\\{(.+?)\\}"); Matcher matcher = fillInPattern.matcher(dataPattern); while (matcher.find()) { String argName = matcher.group(1); if (args.containsKey(argName)) { matcher.appendReplacement(data, ""); data.append(Uri.encode(args.getString(argName))); } else { throw new IllegalArgumentException("Could not find " + argName + " in " + args + " to fill data pattern " + dataPattern); } } matcher.appendTail(data); intent.setData(Uri.parse(data.toString())); } } if (navOptions != null && navOptions.shouldClearTask()) { intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); } if (navOptions != null && navOptions.shouldLaunchDocument() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); } else if (!(mContext instanceof Activity)) { // If we're not launching from an Activity context we have to launch in a new task. intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } if (navOptions != null && navOptions.shouldLaunchSingleTop()) { intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); } if (mHostActivity != null) { final Intent hostIntent = mHostActivity.getIntent(); if (hostIntent != null) { final int hostCurrentId = hostIntent.getIntExtra(EXTRA_NAV_CURRENT, 0); if (hostCurrentId != 0) { intent.putExtra(EXTRA_NAV_SOURCE, hostCurrentId); } } } final int destId = destination.getId(); intent.putExtra(EXTRA_NAV_CURRENT, destId); NavOptions.addPopAnimationsToIntent(intent, navOptions); mContext.startActivity(intent); if (navOptions != null && mHostActivity != null) { int enterAnim = navOptions.getEnterAnim(); int exitAnim = navOptions.getExitAnim(); if (enterAnim != -1 || exitAnim != -1) { enterAnim = enterAnim != -1 ? enterAnim : 0; exitAnim = exitAnim != -1 ? exitAnim : 0; mHostActivity.overridePendingTransition(enterAnim, exitAnim); } } // You can't pop the back stack from the caller of a new Activity, // so we don't add this navigator to the controller's back stack dispatchOnNavigatorNavigated(destId, BACK_STACK_UNCHANGED); }
2.FragmentNavigator.navigate(...)
@Override public void navigate(@NonNull Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions) { final Fragment frag = destination.createFragment(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); final StateFragment oldState = getState(); if (oldState != null) { ft.remove(oldState); } final @IdRes int destId = destination.getId(); final StateFragment newState = new StateFragment(); newState.mCurrentDestId = destId; ft.add(newState, StateFragment.FRAGMENT_TAG); final boolean initialNavigation = mFragmentManager.getFragments().isEmpty(); final boolean isClearTask = navOptions != null && navOptions.shouldClearTask(); // TODO Build first class singleTop behavior for fragments final boolean isSingleTopReplacement = navOptions != null && oldState != null && navOptions.shouldLaunchSingleTop() && oldState.mCurrentDestId == destId; if (!initialNavigation && !isClearTask && !isSingleTopReplacement) { ft.addToBackStack(getBackStackName(destId)); } else { ft.runOnCommit(new Runnable() { @Override public void run() { dispatchOnNavigatorNavigated(destId, isSingleTopReplacement ? BACK_STACK_UNCHANGED : BACK_STACK_DESTINATION_ADDED); } }); } ft.commit(); mFragmentManager.executePendingTransactions(); }
3.NavGraphNavigation.navigate(...):
@Override public void navigate(@NonNull NavGraph destination, @Nullable Bundle args, @Nullable NavOptions navOptions) { int startId = destination.getStartDestination(); if (startId == 0) { throw new IllegalStateException("no start destination defined via" + " app:startDestination for " + (destination.getId() != 0 ? NavDestination.getDisplayName(mContext, destination.getId()) : "the root navigation")); } NavDestination startDestination = destination.findNode(startId, false); if (startDestination == null) { final String dest = NavDestination.getDisplayName(mContext, startId); throw new IllegalArgumentException("navigation destination " + dest + " is not a direct child of this NavGraph"); } dispatchOnNavigatorNavigated(destination.getId(), BACK_STACK_DESTINATION_ADDED); startDestination.navigate(args, navOptions); }
看了源码是不是觉得其实很简单。
主要注意的是第三个NavGraphNavigator它其实也是一个转调,他将这个Bundle传递给了startDestination的navigate方法。也就是回到了NavDestination的navigate方法,其实也就是分情况调用ActivityNavigator或FragmentNavigator的Navigate。
ViewModel也是Android JetPack中引入的一个lib(其实之前在android架构组建里已经被引入了),主要是为了解决在android/Fragment中onConfigrationChange导致的状态丢失问题,和Fragment的参数传递问题,因为很多时候依附在同一个activity伤的多个fragment其实是如果一个逻辑单元的。这个我将来应该也会专门介绍,不了解的同学可以把它想象成Activity/fragment的一个内部属性或者可以看看网上其他的关于ViewModel的教程。
在Fragment中使用:
ViewModelProviders.of(FragmentActivity)获得Activity的ViewModel,只要多Fragment attach到的是同一个activity对象。那么他们返回ViewModel就是同一个,这样就不用关心Fragment。而只用在ViewModel中保存需要的属性,他们就可以在不同fragment之间共享啦。
需要在你的Root Project中添加如下依赖,添加后你的root Project依赖部分应该大致如下(注意红色部分):
buildscript { apply from:'dependencies.gradle' repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.2.0-alpha14' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha01" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } }
并在你需要使用safeArgs的project的build.gradle中引入这个插件,引入后你的build.gradle大致如下(还是注意红色部分):
apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' apply plugin: 'androidx.navigation.safeargs' android { compileSdkVersion sdk_version defaultConfig { applicationId "com.example.xwh.myapplication" minSdkVersion rootProject.ext.mini_sdk_version versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } dexOptions { // Sets the maximum number of DEX processes // that can be started concurrently. maxProcessCount 8 // Sets the maximum memory allocation pool size // for the dex operation. javaMaxHeapSize "2g" // Enables Gradle to pre-dex library dependencies. preDexLibraries true } } dependencies { //do not modify dependencies here, modify in root project's depencencies.gradle implementation fileTree(include: ['*.jar'], dir: 'libs') def deps = rootProject.ext.project_dependencies.get(getProject().getName()) deps.libs.each { dep -> implementation dep } deps.textLibs.each { dep -> testImplementation dep } deps.androidTestLibs.each { dep -> androidTestImplementation dep } //java //deps.annceationProcessors.each { dep -> annotationProcessor dep } //kotlin deps.annceationProcessors.each { dep -> kapt dep } deps.projects.each { dep -> implementation project dep } //add other ext dependencies below }
(对dependencies有疑惑的同学,我打个广告:Android开发中Gradle依赖到底应该怎么写?)
然后你就可以在你的nav_graph.xml中通过 右侧的click+to add arguments按钮添加safeArgs了
添加之后要怎么做呢?你需要build这个项目,这个时候android studio会自动帮你生成几个类。他的生成规则是:你原Fragment的名称+'Directions',比如你的framgnet名称是FragmentA 那他就会帮你生成一个叫做FragmentADirections的类,这个类里还有一个静态内部类,名字为你在xml中定义的Action,如果你你这个fragment标签里有argument子标签(即配置过safeArg)。那么,这个静态内部类还会为你生成这些参数作为他的属性。这样你就可以愉快的传递参数了。传递参数的关键代码如下:
nav_graph.xml:
android:id="@+id/fragmentStep2"
tools:layout="@layout/fragment_step2"
android:name="com.example.xwh.myapplication.FragmentStep2"
android:label="FragmentStep2" >
android:name="test"
android:defaultValue="11111"
app:type="integer" />
android:id="@+id/action_fragmentStep2_to_fragmentStep3"
app:destination="@id/fragmentStep3"
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" />
跳转到Fragment的部分代码:(kotlin)
val directions = FragmentStep1Directions.action_step1_to_step2().run { setTest(111) } Navigation.findNavController(it) .navigate(directions)
自动生成的类:
public class FragmentStep1Directions { public static Action_step1_to_step2 action_step1_to_step2() { return new Action_step1_to_step2(); } public static class Action_step1_to_step2 implements NavDirections { private int test = 11111; public Action_step1_to_step2() { } public Action_step1_to_step2 setTest(int test) { this.test = test; return this; } public Bundle getArguments() { Bundle __outBundle = new Bundle(); __outBundle.putInt("test", this.test); return __outBundle; } public int getActionId() { return com.example.xwh.myapplication.R.id.action_step1_to_step2; } } }
参数接收:
val test = FragmentStep2Args.fromBundle(arguments).test
可以看到safeArgs还会帮你生成一个叫做"你的fragment名称"+‘Args’名称的类,其实参数真正的传递者还是fragment的arguments属性。
这些就是Navigation中传递参数的几个方法。如果哪里有纰漏和疑问,欢迎留言