页面之间的参数传递。我现在主要学习官方提供的安全的方式。
看官网: 使用 Safe Args 传递安全的数据
使用安全的方式首先需要做两件准备工作:
- 在项目下的
build.gradle
中的:
buildscript {
repositories {
google()
}
dependencies {
def nav_version = "2.3.2"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
}
- 在
app
下的build.gradle
中添加(这里仅说java
):
apply plugin: "androidx.navigation.safeargs"
这样 ide
就会自动帮你生成一个类,比如你的 Fragment
叫 HomeFragment
,那么就生成一个叫 HomeFragmentDirections
的类,这个类下面的方法或属性也不是都是固定的,会根据你 action
中的 id
自动生成,这里的 action
是
图中的箭头,对应的
id
就是图中右边的 Attributes
下的 id
那一栏。自动帮我们生成的类下面有一个属性(内部类)和一个方法,方法的名称就是上面 id
的小驼峰写法,类的名称就是大驼峰法。比如我的是 toDetail
,那么对应的方法名称是 toDetail
,类名称是 ToDetail
。
我们知道页面跳转传递参数,那么就需要定义参数,参数的定义也是在 res/navigation/
下的你的 xml
文件中,如果 Design
的方式,见下图,我们看到右边 Attributes
下面有一个 Arguments
属性,这里就可以很方便的设置所需要传递的属性;这里是设置当前页面的属性,也就是从其他页面跳转过来需要传递的属性,并不是跳转前的属性。我这里是从 HomeFragment
页面跳转到 DetailFragment
页面,所以我需要设置的属性是在 DetailFragment
页面。
如果设置好了属性,那么在生成这个类的时候也会生成对应的 getter
和 setter
。比如我这里在 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");
}