当前位置: 移动技术网 > IT编程>移动开发>Android > Android Jetpack-Navigation 使用中参数的传递方法

Android Jetpack-Navigation 使用中参数的传递方法

2018年10月25日  | 移动技术网IT编程  | 我要评论

蛊惑总裁,我的上司女魔头,嗜血妖王的宠妃

由于使用了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属性。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网