Android Jetpack 之 ViewModel
简介:
官方解释:ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。
ViewModel 类让数据可在发生屏幕旋转等配置更改后继续存在。
其实简单来讲就是解决下面的问题:
1、Activity配置更改重建时(比如屏幕旋转)保留数据;
2、UI组件(Activity与Fragment、Fragment与Fragment)间实现数据共享。
第一种情况下我们一般是通过onSaveInstanceState保存数据,当activity页面重新启动时再
通过onCreate()方法的bundle取出,但如果数据量较大的话,操作会变得复杂,性能也会受一定
的影响。
第二种情况下我们在不用ViewModel的情况我们页面共享数据,如果新增一个共享数据,那我们每个
页面都需要重新声明新增的共享数据,还有就是当我们共享数据发生修改时,其他页面无法及时的感知到,
需手动添加观察者模式,而ViewModel结合LiveData就可以轻松实现这一点。
这里插一句题外,MVVM与MVP这两个架构最大的区别就是用ViewModel(简称VM)代替了原来
的P层(Presenter),这里的VM就是ViewModel。一句话概括它的特点---对数据状态的持有和维护。换言之,
它将原来Presenter层关于数据的逻辑运算与处理统一放到了VM中,而剩余的V层的操作建议使用Databinding,
从而形成最为简洁高效的MVVM架构。
话不多说,我们先来看第一种操作,我是写了一个页面,点击按钮更新数据,更新完毕后我们旋转一下屏幕,然后
看看结果,先看下代码:
简单的xml布局代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我是原始数据"
/>
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点击修改原始数据"/>
</LinearLayout>
接下来是页面代码,简单的操作,点击按钮更新数据。然后我们看看运行过程
public class ViewModelActivity extends AppCompatActivity {
private TextView tv;
private Button btn;
private Student stu;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_viewmodel);
tv = findViewById(R.id.tv);
btn = findViewById(R.id.btn);
initView(stu);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
stu = new Student("白展堂", 18);
tv.setText("我是已经修改的数据,我的名字是"+stu.getName());
}
});
}
private void initView(Student stu) {
if (stu != null){
tv.setText("我是已经修改的数据,我的名字是"+stu.getName());
}
}
}
运行结果:如下,可以正常显示数据。
接下来,我们把屏幕旋转了一下,变成以下内容
我们发现我们刚才操作的数据更新没有改变,而是变成原来的默认数据。这里就出现了我们上面所说的第一种情况,而ViewModel的出现可以为我们保存Activity配置更改重建时(比如屏幕旋转)的数据;那么,我们来看一下ViewModel怎么为我们工作的,同样,先看一下代码,首先我们操作的类需要继承ViewModel
public class PersonViewModel extends ViewModel {
Student student;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
}
接下来是页面代码:
public class ViewModelActivity extends AppCompatActivity {
private TextView tv;
private Button btn;
private PersonViewModel personViewModel;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_viewmodel);
tv = findViewById(R.id.tv);
btn = findViewById(R.id.btn);
personViewModel = ViewModelProviders.of(ViewModelActivity.this).get(PersonViewModel.class);
initView(personViewModel);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Student stu = new Student("白展堂", 18);
personViewModel.setStudent(stu);
tv.setText("我是已经修改的数据,我的名字是"+personViewModel.getStudent().getName());
}
});
}
private void initView(PersonViewModel personViewModel) {
if (personViewModel.getStudent() != null){
tv.setText("我是已经修改的数据,我的名字是"+personViewModel.getStudent().getName());
}
}
}
我们看一下运行结果和旋转后的结果:
我们可以看到,即使我们旋转了屏幕,数据依然存在。这就是我们要的效果。
接下来我们看下它的第二个用途,共享数据,Activity 中的两个或更多 Fragment 需要相互通信是一种很常见的情况。想象一下主从 Fragment 的常见情况,假设我们有一个 Fragment,在该 Fragment 中,用户从修改了显示内容,还有另一个 Fragment,也是有一个单独模板用于显示该项的内容。这种情况不太容易处理,因为这两个 Fragment 都需要定义某种接口描述,并且所有者 Activity 必须将两者绑定在一起。
此外,这两个 Fragment 都必须处理另一个 Fragment 尚未创建或不可见的情况。
这种情况,我们就可以用到ViewModel配合LiveData来处理了,我们接着往下看。
我这个例子是用tab控制两个不同的Fragment,然后它们共享了一个资源数据。我们看下代码,首先是操作页面简单
的布局xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.TabLayout
android:id="@+id/mytab"
android:layout_width="match_parent"
android:layout_height="wrap_content"
></android.support.design.widget.TabLayout>
<android.support.v4.view.ViewPager
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:id="@+id/mViewPager"
></android.support.v4.view.ViewPager>
</LinearLayout>
然后就是第一个FragmentOne的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="ONE"
android:gravity="center"
android:textSize="30sp"
android:textStyle="bold"
/>
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="修改"/>
</LinearLayout>
接下来是第二个FragmentTwo的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TWO"
android:gravity="center"
android:textSize="30sp"
android:textStyle="bold"
/>
<TextView
android:id="@+id/tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="修改的值:"
android:gravity="center"
android:textSize="16sp"
android:textStyle="bold"
/>
</LinearLayout>
好了,到这里我们页面的布局就好了。
然后我们看一下我们页面共享的Model代码,继承于ViewModel,配合LiveData进行数据更新监听。
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Person> person = new MutableLiveData<Person>();
public void setPerson(Person p){
person.setValue(p);
}
public LiveData<Person> getPerson(){
return person;
}
}
到这里之后我们看一下具体的页面和操作代码,首先是操作Activity,只是简单将Fragment、ViewPager和TabLayout绑定起来:
public class ViewModelActivty2 extends AppCompatActivity {
TabLayout mytab;
ViewPager mViewPager;
List<String> mTitle;
List<Fragment> mFragment;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_viewmodel2);
mytab = (TabLayout) findViewById(R.id.mytab);
mViewPager = (ViewPager) findViewById(R.id.mViewPager);
mTitle = new ArrayList<>();
mTitle.add("ONE");
mTitle.add("TWO");
mFragment = new ArrayList<>();
mFragment.add(new FragmentOne());
mFragment.add(new FragmentTwo());
mViewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
@Override
public Fragment getItem(int position) {
return mFragment.get(position);
}
@Override
public int getCount() {
return mFragment.size();
}
@Override
public CharSequence getPageTitle(int position) {
return mTitle.get(position);
}
});
mytab.setupWithViewPager(mViewPager);
}
}
接下来是FragmentOne的操作代码,这里的话是点击了按钮进行数据更新操作,然后调用LiveData的setValue()进行数据更新。
public class FragmentOne extends Fragment implements View.OnClickListener{
private SharedViewModel model;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_one,container,false);
Button btn = view.findViewById(R.id.btn);
btn.setOnClickListener(this);
return view;
}
@Override
public void onClick(View view) {
Person person = new Person();
person.setName("李大嘴");
person.setSex("男");
model.setPerson(person);
}
}
最后是FragmentTwo的操作页面代码,这里的话主要操作是对LiveData的数据更新进行观察监听,从而更新UI:
public class FragmentTwo extends Fragment {
SharedViewModel model;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_two,container,false);
final TextView tv = view.findViewById(R.id.tv);
model.getPerson().observe(this, new Observer<Person>() {
@Override
public void onChanged(@Nullable Person person) {
tv.setText("修改的值: 名字"+person.getName()+" 性别:"+person.getSex());
}
});
return view;
}
}
到这里,流程结束。我们看一下运行效果。
请注意,这两个 Fragment 都会检索包含它们的 Activity。这样,当这两个 Fragment 各自获取 ViewModelProvider 时,它们会收到相同的 SharedViewModel 实例(其范围限定为该 Activity)。
此方法具有以下优势:
1、Activity 不需要执行任何操作,也不需要对此通信有任何了解。
2、除了 SharedViewModel 约定之外,Fragment 不需要相互了解。如果其中一个 Fragment 消失,另一个 Fragment 将继续照常工作。
3、每个 Fragment 都有自己的生命周期,而不受另一个 Fragment 的生命周期的影响。如果一个 Fragment 替换另一个 Fragment,界面将继续工作而没有任何问题。
好了。今天对ViewModel的介绍就写到这里。共勉