[学习]JetPack 中的 Navigation(二)

页面之间的参数传递。我现在主要学习官方提供的安全的方式。

看官网: 使用 Safe Args 传递安全的数据

使用安全的方式首先需要做两件准备工作:

  1. 在项目下的 build.gradle 中的:
buildscript {
    repositories {
        google()
    }
    dependencies {
        def nav_version = "2.3.2"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}
  1. app 下的 build.gradle 中添加(这里仅说 java ):
apply plugin: "androidx.navigation.safeargs"

这样 ide 就会自动帮你生成一个类,比如你的 FragmentHomeFragment ,那么就生成一个叫 HomeFragmentDirections 的类,这个类下面的方法或属性也不是都是固定的,会根据你 action 中的 id 自动生成,这里的 action

navigation 图示

图中的箭头,对应的 id 就是图中右边的 Attributes 下的 id 那一栏。自动帮我们生成的类下面有一个属性(内部类)和一个方法,方法的名称就是上面 id 的小驼峰写法,类的名称就是大驼峰法。比如我的是 toDetail ,那么对应的方法名称是 toDetail ,类名称是 ToDetail

我们知道页面跳转传递参数,那么就需要定义参数,参数的定义也是在 res/navigation/ 下的你的 xml 文件中,如果 Design 的方式,见下图,我们看到右边 Attributes 下面有一个 Arguments 属性,这里就可以很方便的设置所需要传递的属性;这里是设置当前页面的属性,也就是从其他页面跳转过来需要传递的属性,并不是跳转前的属性。我这里是从 HomeFragment 页面跳转到 DetailFragment 页面,所以我需要设置的属性是在 DetailFragment 页面。

Arguments设置

如果设置好了属性,那么在生成这个类的时候也会生成对应的 gettersetter 。比如我这里在 DetailFragment 页面添加了 name 属性,在下面生成的代码中可以看到对应的方法。

如果当前页面可以跳转到多个页面,每个页面都有对应的属性,这些属性都会在对应的内部类中。比如我这里的 name 属性,它是在 ToDetail 内部类中的。

自动生成的类为(这里我的,参考):

public class HomeFragmentDirections {
  private HomeFragmentDirections() {
  }

  @NonNull
  public static ToDetail toDetail() {
    return new ToDetail();
  }

  public static class ToDetail implements NavDirections {
    private final HashMap arguments = new HashMap();

    private ToDetail() {
    }

    @NonNull
    public ToDetail setName(@NonNull String name) {
      if (name == null) {
        throw new IllegalArgumentException("Argument \"name\" is marked as non-null but was passed a null value.");
      }
      this.arguments.put("name", name);
      return this;
    }

    @Override
    @SuppressWarnings("unchecked")
    @NonNull
    public Bundle getArguments() {
      Bundle __result = new Bundle();
      if (arguments.containsKey("name")) {
        String name = (String) arguments.get("name");
        __result.putString("name", name);
      } else {
        __result.putString("name", "吴天歌");
      }
      return __result;
    }

    @Override
    public int getActionId() {
      return R.id.toDetail;
    }

    @SuppressWarnings("unchecked")
    @NonNull
    public String getName() {
      return (String) arguments.get("name");
    }

    @Override
    public boolean equals(Object object) {
      if (this == object) {
          return true;
      }
      if (object == null || getClass() != object.getClass()) {
          return false;
      }
      ToDetail that = (ToDetail) object;
      if (arguments.containsKey("name") != that.arguments.containsKey("name")) {
        return false;
      }
      if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
        return false;
      }
      if (getActionId() != that.getActionId()) {
        return false;
      }
      return true;
    }

    @Override
    public int hashCode() {
      int result = 1;
      result = 31 * result + (getName() != null ? getName().hashCode() : 0);
      result = 31 * result + getActionId();
      return result;
    }

    @Override
    public String toString() {
      return "ToDetail(actionId=" + getActionId() + "){"
          + "name=" + getName()
          + "}";
    }
  }
}

理解上面的,接下来就可以跳转并传入参数了,在我的 HomeFragment 页面中添加如下函数:

@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
  super.onViewStateRestored(savedInstanceState);
  Log.e(LOG, "onViewStateRestored");
  Button button = requireView().findViewById(R.id.homeButton);
  button.setOnClickListener(v -> {
    NavController controller = Navigation.findNavController(v);
    HomeFragmentDirections.ToDetail action = HomeFragmentDirections.toDetail();
    action.setName("吴敬悦");
    controller.navigate(action);
  });
}

说完了跳转出发页面,下面再说跳转目的地页面。

同样的也会生成对应的类,这里的规则是目的地页面的类名后面添加 Args ,得到了新的类。比如我的是 DetailFragment 页面,自动生成的类名为 DetailFragmentArgs ,这个类会自动根据设置的属性生成 getter 。下面是我这边自动生成的类:

public class DetailFragmentArgs implements NavArgs {
  private final HashMap arguments = new HashMap();

  private DetailFragmentArgs() {
  }

  private DetailFragmentArgs(HashMap argumentsMap) {
    this.arguments.putAll(argumentsMap);
  }

  @NonNull
  @SuppressWarnings("unchecked")
  public static DetailFragmentArgs fromBundle(@NonNull Bundle bundle) {
    DetailFragmentArgs __result = new DetailFragmentArgs();
    bundle.setClassLoader(DetailFragmentArgs.class.getClassLoader());
    if (bundle.containsKey("name")) {
      String name;
      name = bundle.getString("name");
      if (name == null) {
        throw new IllegalArgumentException("Argument \"name\" is marked as non-null but was passed a null value.");
      }
      __result.arguments.put("name", name);
    } else {
      __result.arguments.put("name", "吴天歌");
    }
    return __result;
  }

  @SuppressWarnings("unchecked")
  @NonNull
  public String getName() {
    return (String) arguments.get("name");
  }

  @SuppressWarnings("unchecked")
  @NonNull
  public Bundle toBundle() {
    Bundle __result = new Bundle();
    if (arguments.containsKey("name")) {
      String name = (String) arguments.get("name");
      __result.putString("name", name);
    } else {
      __result.putString("name", "吴天歌");
    }
    return __result;
  }

  @Override
  public boolean equals(Object object) {
    if (this == object) {
        return true;
    }
    if (object == null || getClass() != object.getClass()) {
        return false;
    }
    DetailFragmentArgs that = (DetailFragmentArgs) object;
    if (arguments.containsKey("name") != that.arguments.containsKey("name")) {
      return false;
    }
    if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
      return false;
    }
    return true;
  }

  @Override
  public int hashCode() {
    int result = 1;
    result = 31 * result + (getName() != null ? getName().hashCode() : 0);
    return result;
  }

  @Override
  public String toString() {
    return "DetailFragmentArgs{"
        + "name=" + getName()
        + "}";
  }

  public static class Builder {
    private final HashMap arguments = new HashMap();

    public Builder(DetailFragmentArgs original) {
      this.arguments.putAll(original.arguments);
    }

    public Builder() {
    }

    @NonNull
    public DetailFragmentArgs build() {
      DetailFragmentArgs result = new DetailFragmentArgs(arguments);
      return result;
    }

    @NonNull
    public Builder setName(@NonNull String name) {
      if (name == null) {
        throw new IllegalArgumentException("Argument \"name\" is marked as non-null but was passed a null value.");
      }
      this.arguments.put("name", name);
      return this;
    }

    @SuppressWarnings("unchecked")
    @NonNull
    public String getName() {
      return (String) arguments.get("name");
    }
  }
}

我们知道以前页面传参数是根据 Bundle 进行传参的,同样的从其他页面传过来的参数都会保存到 Bundle 所对应对象中,每一个 Fragment 提供有一个方法可以获取到当前页面得到的参数:

// Fragment.java
/**
 * Return the arguments supplied when the fragment was instantiated,
 * if any.
 */
@Nullable
final public Bundle getArguments() {
    return mArguments;
}

为了安全起见,一般不会直接调用这个方法,而是 requireArguments

// Fragment.java
/**
 * Return the arguments supplied when the fragment was instantiated.
 *
 * @throws IllegalStateException if no arguments were supplied to the Fragment.
 * @see #getArguments()
 */
@NonNull
public final Bundle requireArguments() {
    Bundle arguments = getArguments();
    if (arguments == null) {
        throw new IllegalStateException("Fragment " + this + " does not have any arguments.");
    }
    return arguments;
}

再结合上面自动生成的类,代码可以为:

// DetailFragment
@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
  super.onViewStateRestored(savedInstanceState);
  DetailFragmentArgs args = DetailFragmentArgs.fromBundle(requireArguments());
  TextView textView  = requireView().findViewById(R.id.textView);
  textView.setText(args.getName());
  Log.e(LOG, "onViewStateRestored");
}

你可能感兴趣的:([学习]JetPack 中的 Navigation(二))