Android Jetpack-Navigation 使用中参数的传递方法
由于使用了navigation,导致fragment的创建行为完全交给了。也就是说,以前的那种通过#fragment.newinstance(args...)方式传递参数的方式就被切断了,没有办法快乐的在fragment之间传参了~~。但是不要担心,google爸爸早就帮我们想好了方式,下面我们来一条一条的看看吧:
1.通过navcontroller.navigate(int, bundle)方法传参。
这个是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。
2.通过viewmodle传参(只支持fragment导航)
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之间共享啦。
3.通过官方专门添加的lib safeargs
需要在你的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 inpidual 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 }
然后你就可以在你的nav_graph.xml中通过 右侧的click+to add arguments按钮添加safeargs了
添加之后要怎么做呢?你需要build这个项目,这个时候android studio会自动帮你生成几个类。他的生成规则是:你原fragment的名称+'directions',比如你的framgnet名称是fragmenta 那他就会帮你生成一个叫做fragmentadirections的类,这个类里还有一个静态内部类,名字为你在xml中定义的action,如果你你这个fragment标签里有argument子标签(即配置过safearg)。那么,这个静态内部类还会为你生成这些参数作为他的属性。这样你就可以愉快的传递参数了。传递参数的关键代码如下:
nav_graph.xml:
"@+id/fragmentstep2" tools:layout="@layout/fragment_step2" android:name="com.example.xwh.myapplication.fragmentstep2" android:label="fragmentstep2" > "test" android:defaultvalue="11111" app:type="integer" /> "@+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属性。
上一篇: 喝酒之后你更美了
推荐阅读