欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2022-12-11 08:14:52
由于使用了navigation,导致fragment的创建行为完全交给了。也就是说,以前的那种通过#fragment.newinstance(args...)方式传递参数的方式就被切断了,没有办法快...

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