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 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中传递参数的几个方法。如果哪里有纰漏和疑问,欢迎留言

你可能感兴趣的:(移动开发,Android)