ViewModel
如果系统销毁或重新创建Activity 或者fragment ,则存储在其中的任何瞬态界面相关数据都会丢失。例如,应用的某个 Activity 中可能包含用户列表。因配置更改(如旋转屏幕,分辨率改变等)而重新创建 Activity 后,新 Activity 必须重新提取用户列表。对于简单的数据,activity 可以使用 onSaveInstanceState() 方法从 onCreate() 中的捆绑包恢复其数据,但此方法仅适合可以序列化再反序列化的少量数据,而不适合数量可能较大的数据,如用户列表或位图。诸如activity 和fragment 之类的界面控制器主要用于显示界面数据、对用户操作做出响应或处理操作系统通信(如权限请求)。如果要求界面控制器也负责从数据库或网络加载数据,那么会使类越发膨胀。
使用ViewModel 可以做到以生命周期形式管理界面数据。将视图和数据分离。架构组件为界面控制器提供了 ViewModel 辅助程序类,该类负责为界面准备数据。在配置更改期间会自动保留 ViewModel 对象,以便它们存储的数据立即可供下一个 activity 或 fragment 实例使用。实现数据共享。
不使用ViewModel
实现一个小功能,点击按钮,textview的数字加1
public class ViewModelStudy extends AppCompatActivity {
private TextView tv;
private int i = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_model_study);
tv = findViewById(R.id.tv_test);
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
tv.setText(Integer.toString(i));
i++;
}
});
}
}
但是如果在这过程中屏幕发生旋转,那么显示的值会变为最开始的,而不是和旋转之前的一样。因为屏幕发生了旋转,所以activity 重新经历了onCrea t的生命周期。不使用ViewModel 的话,界面数据不会保存。ViewModel 的生命周期如下。可以看到ViewModel 的生命周期比activity 的时间更长。ViewModel 对象存在的时间范围是获取 ViewModel 时传递给 ViewModelProvider 的 Lifecycle 。ViewModel 将一直留在内存中,直到限定其存在时间范围的 Lifecycle 永久消失:对于 Activity ,是在 Activity 完成时;而对于 Fragment ,是在 Fragment 分离时。取决于Lifecycle
使用ViewModel
首先继承ViewModel ,数据成员是需要保存的界面数据。
public class TestViewModel extends ViewModel {
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
private int num = 0;
}
public class ViewModelStudy extends AppCompatActivity {
private TextView tv;
private TestViewModel testViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_model_study);
tv = findViewById(R.id.tv_test);
testViewModel = new ViewModelProvider(this).get(TestViewModel.class);
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int num = testViewModel.getNum();
tv.setText(Integer.toString(num));
testViewModel.setNum(++num);
}
});
}
}
上面这种方式可以实现界面数据的保存,但是旋转之后还是会变为最开始的样子。按下按钮之后数字是旋转前的数字加1,实现了数据的保存。为了严格的实现旋转后数字不变,那么需要使用Livedata ,这也是ViewModel 一般结合Livedata 使用的原因。LiveData 的优势,数据始终保持最新状态如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。适当的配置更改,如果由于配置更改(如设备旋转)而重新创建了 activity 或 fragment ,它会立即接收最新的可用数据。
public class TestViewModel extends ViewModel {
private MutableLiveData<Integer> num;
public MutableLiveData<Integer> getNum() {
if (num == null)
num = new MutableLiveData<Integer>(0);
return num;
}
public void setNum(Integer num) {
this.num.setValue(num);
}
}
public class ViewModelStudy extends AppCompatActivity {
private TextView tv;
private TestViewModel testViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_model_study);
tv = findViewById(R.id.tv_test);
testViewModel = new ViewModelProvider(this).get(TestViewModel.class);
testViewModel.getNum().observe(this,
new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
tv.setText(integer.toString());
}
}
);
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Integer num = testViewModel.getNum().getValue();
testViewModel.setNum(++num);
}
});
}
}
使用ViewModel进行Fragment之间的通信
Activity 中的两个或更多 Fragment 需要相互通信是一种很常见的现象。一般情况这两个 Fragment 都需要定义某种接口描述,并且所有者 Activity 必须将两者绑定在一起。此外,这两个 Fragment 都必须处理另一个 Fragment 尚未创建或不可见的情况。可以使用 ViewModel 对象解决这一常见的难点。这两个Fragment 可以使用其 Activity 范围共享 ViewModel 来处理此类通信。
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Integer> selected = new MutableLiveData<Integer>();
public void select(Integer item) {
selected.setValue(item);
}
public LiveData<Integer> getSelected() {
return selected;
}
}
public class ListFragment extends Fragment {
private SharedViewModel model;
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends Fragment {
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
model.getSelected().observe(getViewLifecycleOwner(), item -> {
});
}
}
Activity 不需要执行任何操作,也不需要对此通信有任何了解。除了 SharedViewModel 约定之外,Fragment 不需要相互了解。如果其中一个 Fragment 消失,另一个 Fragment 将继续照常工作。每个 fragment 都有自己的生命周期,而不受另一个 fragmen t 的生命周期的影响。如果一个fragment 替换另一个 fragment ,界面将继续工作而没有任何问题。
通信实例
public class LeftFragment extends Fragment {
private TestViewModel model;
private TextView tv;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_left, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
model = new ViewModelProvider(requireActivity()).get(TestViewModel.class);
tv = view.findViewById(R.id.tv_test);
view.findViewById(R.id.addone).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Integer num = model.getNum().getValue();
model.setNum(++num);
}
});
model.getNum().observe(getViewLifecycleOwner(), new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
tv.setText(integer.toString());
}
});
}
}
public class RightFragment extends Fragment {
private TextView tv;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_right, container, false);
}
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
TestViewModel model = new ViewModelProvider(requireActivity()).get(TestViewModel.class);
tv = view.findViewById(R.id.tv_test);
view.findViewById(R.id.addone).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Integer num = model.getNum().getValue();
model.setNum(++num);
}
});
model.getNum().observe(getViewLifecycleOwner(), new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
tv.setText(integer.toString());
}
});
}
}
|