前言
通过一个小案例,帮助大家了解MVVM。最终实现一个MVVM通用框架。代码:github。(如有错误之处,请在评论区指出,谢谢。如果感觉写的不错,请点赞,关注,谢谢。)
上一个小节,只是理论介绍了MVVM,DataBinding,ViewModel,LiveData,这一小节,讲解一下他们的使用方式。如果已经对这些基础知识很了解,可以直接看下一节。
目录:
Android MVVM-编程思想1(入门介绍MVVM,DataBinding,ViewModel,LiveData)
Android MVVM-编程思想2(入门实战MVVM,DataBinding,ViewModel,LiveData)
Android MVVM-编程思想3(封装基类BaseMvvmActivity,BaseMvvmFragment)
DataBinding 使用
首先开启app模块的gradle配置文件android 节点下开启
dataBinding {
enabled true
}
(1)最简单的使用JDK定义好的类型
xml直接使用
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="title"
type="String" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="45dp"
android:background="@color/colorAccent"
android:gravity="center"
android:text="@={title}"
android:textColor="#ffffff"
android:textSize="18sp" />
</LinearLayout>
</layout>
UserActivity 中使用,DataBindingUtil.setContentView(this, R.layout.activity_user),AndroidStudio 构建之后会自动根据XML中的配置生成绑定关系。并返回管理类ActivityUserBinding
ActivityUserBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_user);
binding.setTitle("DataBinding绑定演示");
(2)如何使用自定义的类?
定义一个类User
普通的类,跟JDK内部的类一样,只是残疾版的绑定,什么意思? Model中的数据可以显示到View上。Model 的数据变动不会实时变化到View上。如何实现数据驱动,如何实现双向绑定?,继续看,后面会讲
User类
public class User {
private String name;
private String sex;
private int age;
public User(String name, String sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Bindable
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
XML中使用
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="title"
type="String" />
<variable
name="user"
type="chongchong.wei.mvvm_aac.base.User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="45dp"
android:background="@color/colorAccent"
android:gravity="center"
android:text="@={title}"
android:textColor="#ffffff"
android:textSize="18sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="45dp"
android:gravity="center"
android:text='@{"名称:"+user.name}'
android:textSize="18sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="45dp"
android:gravity="center"
android:text="@{user.name}"
android:textSize="18sp" />
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={user.name}" />
<TextView
android:layout_width="match_parent"
android:layout_height="45dp"
android:gravity="center"
android:text='@{""+user.age}'
android:textSize="18sp" />
<Button
android:id="@+id/mButton"
android:layout_width="100dp"
android:layout_height="40dp"
android:text="模拟请求网络" />
</LinearLayout>
</layout>
UserActivity类
public class UserActivity extends AppCompatActivity {
ActivityUserBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user);
binding = DataBindingUtil.setContentView(this, R.layout.activity_user);
binding.setTitle("DataBinding绑定演示");
binding.setUser(new User("小明", "男", 16));
binding.mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
requestHttp();
}
});
}
/**
* 模拟请求网络刷新数据
*/
private void requestHttp() {
binding.getUser().setName("改变Name");//User没有继承BaseObservable ,View不会变化
}
}
(3)实现User数据变化,View自动更改,加上注解@Bindable,set方法手动更新notifyPropertyChanged
实现数据变化自动驱动 UI 刷新的方式有三种:BaseObservable、ObservableField、ObservableCollection
BaseObservable 提供了 notifyChange() 和 notifyPropertyChanged() 两个方法,前者会刷新所有的值域,后者则只更新对应 BR 的 flag,该 BR 的生成通过注释 @Bindable 生成,可以通过 BR notify 特定属性关联的视图
下面学习一下第一种方式:
User类
public class User extends BaseObservable {
@Bindable
private String name;
@Bindable
private String sex;
@Bindable
private int age;
public User(String name, String sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(chongchong.wei.mvvm_aac.BR.name);
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
notifyPropertyChanged(chongchong.wei.mvvm_aac.BR.sex);
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
notifyPropertyChanged(chongchong.wei.mvvm_aac.BR.age);
}
}
双向数据绑定如何实现?
双向绑定的意思即为当数据改变时同时使视图刷新,而视图改变时也可以同时改变数据
需求:界面上有两个控件,EditText 用于获取用户输入,TextView 用于把用户输入展示出来,绑定变量的方式比单向绑定多了一个等号:@={user.name}
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={user.name}" />
这里只是简单介绍DataBinding 的作用和使用方式。还有很多其他场景的使用方式,大家去官网自己查看吧。本系列的目的是搭建MVVM框架,所以就不在这上面讲解过多了。
ViewModel使用
ViewModel 其实就是MVVM中的VM 层 ,一般和LiveData 一起使用,根据官方介绍,VeiwModel是具有生命周期感知的。验证一下
UserVM
public class UserVM extends ViewModel {
@Override
protected void onCleared() {
super.onCleared();
Log.i("weicc_mvvm_", "onCleared");
}
}
UserActivity中使用
public class UserActivity extends AppCompatActivity {
ActivityUserBinding binding;
UserVM userVM;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user);
binding = DataBindingUtil.setContentView(this, R.layout.activity_user);
binding.setTitle("DataBinding绑定演示");
userVM = ViewModelProviders.of(this).get(UserVM.class);
binding.setUserVM(userVM);
Log.i("weicc_mvvm_", "onCreate_hashcode:" + ViewModelProviders.of(this).get(UserVM.class).hashCode());
}
@Override
protected void onStart() {
super.onStart();
Log.i("weicc_mvvm_", "onStart_hashcode:" + ViewModelProviders.of(this).get(UserVM.class).hashCode());
}
@Override
protected void onResume() {
super.onResume();
Log.i("weicc_mvvm_", "onResume_hashcode:" + ViewModelProviders.of(this).get(UserVM.class).hashCode());
}
@Override
protected void onPause() {
super.onPause();
Log.i("weicc_mvvm_", "onPause_hashcode:" + ViewModelProviders.of(this).get(UserVM.class).hashCode());
}
@Override
protected void onStop() {
super.onStop();
Log.i("weicc_mvvm_", "onStop_hashcode:" + ViewModelProviders.of(this).get(UserVM.class).hashCode());
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.i("weicc_mvvm_", "onDestroy_hashcode:" + ViewModelProviders.of(this).get(UserVM.class).hashCode());
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.i("weicc_mvvm_", "onConfigurationChanged_hashcode:" + ViewModelProviders.of(this).get(UserVM.class).hashCode());
}
}
启动运行,然后旋转屏幕–》点击back销毁。可以看出旋转屏幕不会重建ViewModel。
打印:
2019-12-20 15:59:43.502 11594-11594/chongchong.wei.mvvm_aac I/weicc_mvvm_: onCreate_hashcode:86643892
2019-12-20 15:59:43.503 11594-11594/chongchong.wei.mvvm_aac I/weicc_mvvm_: onStart_hashcode:86643892
2019-12-20 15:59:43.506 11594-11594/chongchong.wei.mvvm_aac I/weicc_mvvm_: onResume_hashcode:86643892
2019-12-20 15:59:49.704 11594-11594/chongchong.wei.mvvm_aac I/weicc_mvvm_: onConfigurationChanged_hashcode:86643892
2019-12-20 15:59:49.707 11594-11594/chongchong.wei.mvvm_aac I/weicc_mvvm_: onPause_hashcode:86643892
2019-12-20 15:59:49.718 11594-11594/chongchong.wei.mvvm_aac I/weicc_mvvm_: onStop_hashcode:86643892
2019-12-20 15:59:49.719 11594-11594/chongchong.wei.mvvm_aac I/weicc_mvvm_: onDestroy_hashcode:86643892
2019-12-20 15:59:49.764 11594-11594/chongchong.wei.mvvm_aac I/weicc_mvvm_: onCreate_hashcode:86643892
2019-12-20 15:59:49.766 11594-11594/chongchong.wei.mvvm_aac I/weicc_mvvm_: onStart_hashcode:86643892
2019-12-20 15:59:49.771 11594-11594/chongchong.wei.mvvm_aac I/weicc_mvvm_: onResume_hashcode:86643892
2019-12-20 15:59:53.086 11594-11594/chongchong.wei.mvvm_aac I/weicc_mvvm_: onPause_hashcode:86643892
2019-12-20 15:59:53.299 11594-11594/chongchong.wei.mvvm_aac I/weicc_mvvm_: onStop_hashcode:86643892
2019-12-20 15:59:53.302 11594-11594/chongchong.wei.mvvm_aac I/weicc_mvvm_: onCleared
2019-12-20 15:59:53.302 11594-11594/chongchong.wei.mvvm_aac I/weicc_mvvm_: onDestroy_hashcode:231874846
LiveData 使用
UserActivity代码
public class UserActivity extends AppCompatActivity {
ActivityUserBinding binding;
MutableLiveData<User> mutableLiveData;
User user;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i("weicc_mvvm_", "onCreate");
setContentView(R.layout.activity_user);
binding = DataBindingUtil.setContentView(this, R.layout.activity_user);
binding.setTitle("DataBinding绑定演示");
user = new User();
user.setName("小明");
user.setAge(12);
user.setSex("男");
mutableLiveData = new MutableLiveData();
mutableLiveData.setValue(user);
mutableLiveData.observe(this, new Observer<User>() {
@Override
public void onChanged(User user) {
binding.setUser(user);//user没有继承BaseObservable 需要手动刷新数据到界面
Log.i("weicc_mvvm_", "监听user改变:" + user.toString());
}
});
binding.mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
requestHttp();
}
});
}
/**
* 模拟请求网络刷新数据
*/
private void requestHttp() {
Log.i("weicc_mvvm_", "点击button的时候改变name");
user.setName("改变name");
mutableLiveData.postValue(user);
}
@Override
protected void onStop() {
super.onStop();
Log.i("weicc_mvvm_", "onStop的时候改变name");
user.setName("改变name");
mutableLiveData.postValue(user);
}
}
日志分析:LiveData onStop的数据改变不会推送,所以并不会监听到,正如LiveData官方说明的一样,是具有生命周期感知的观察者。
优势:1,当Activity不在屏幕上时(不可见),LiveData不会出发没必要的界面更新;2,当Activity被销毁时,LiveData将自动清空与Observer的连接
2019-12-20 16:46:42.312 22352-22352/chongchong.wei.mvvm_aac I/weicc_mvvm_: onCreate
2019-12-20 16:46:42.407 22352-22352/chongchong.wei.mvvm_aac I/weicc_mvvm_: 监听user改变:User{name='小明', sex='男', age=12}
2019-12-20 16:46:59.912 22352-22352/chongchong.wei.mvvm_aac I/weicc_mvvm_: 点击button的时候改变name
2019-12-20 16:46:59.919 22352-22352/chongchong.wei.mvvm_aac I/weicc_mvvm_: 监听user改变:User{name='改变name', sex='男', age=12}
2019-12-20 16:47:02.736 22352-22352/chongchong.wei.mvvm_aac I/weicc_mvvm_: onStop的时候改变name
如何把ViewModel,LiveData,DataBinding 的优势结合起来?
聪明的观察者应该已经发现,由于要使用ViewModel,LiveData。所以不能使用DataBinding双向绑定的优势。而LiveData每次都要去写监听,使用起来好像很麻烦。google怎么会忍受,所以google给出了LifecycleOwner组件。
核心代码
binding.setLifecycleOwner(this);//这样就不用每次都写监听
binding.setUserVM(userVM);
// userVM.mUser.observe(this, new Observer<User>() {
// @Override
// public void onChanged(User user) {
// binding.setUser(user);//user没有继承BaseObservable 需要手动刷新数据到界面
// Log.i("weicc_mvvm_", "监听user改变:" + userVM.toString());
// }
// });
User类
public class User {
private String name;
private String sex;
private int age;
public User(String name, String sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
UserVM类
public class UserVM extends ViewModel {
public final MutableLiveData<User> mUser = new MutableLiveData<>();
@Override
protected void onCleared() {
super.onCleared();
Log.i("weicc_mvvm_", "onCleared");
}
public void getUser() {
//todo http 获取数据
User user = new User("小明", "男", 12);
mUser.setValue(user);
}
}
UserActivity类
public class UserActivity extends AppCompatActivity {
private ActivityUserBinding binding;
private UserVM userVM;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user);
binding = DataBindingUtil.setContentView(this, R.layout.activity_user);
binding.setTitle("DataBinding绑定演示");
userVM = ViewModelProviders.of(this).get(UserVM.class); //结合ViewModel
binding.setLifecycleOwner(this);
binding.setUserVM(userVM);
binding.mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
userVM.getUser();
}
});
}
}
XML 文件
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="title"
type="String" />
<variable
name="userVM"
type="chongchong.wei.mvvm_aac.user.UserVM" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="45dp"
android:background="@color/colorAccent"
android:gravity="center"
android:text="@={title}"
android:textColor="#ffffff"
android:textSize="18sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="45dp"
android:gravity="center"
android:text='@{"名称:"+userVM.mUser.name}'
android:textSize="18sp" />
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={userVM.mUser.name}" />
<TextView
android:layout_width="match_parent"
android:layout_height="45dp"
android:gravity="center"
android:text='@{""+userVM.mUser.age}'
android:textSize="18sp" />
<Button
android:id="@+id/mButton"
android:layout_width="100dp"
android:layout_height="40dp"
android:text="模拟请求网络" />
</LinearLayout>
</layout>
总结一下,先通过单个的使用介绍,到最后把DataBinding,ViewModel,LiveData整合起来。基本把MVVM的入门版本完成了。
为什么有些人不推崇DataBinding?
因为如果在XML中过多使用DataBinding的表达式,会降低View的复用性,增加View与Model的耦合度,有些违背,高内聚低耦合的原则,所以大家要根据业务场景,灵活使用。这也是一些人放弃使用DataBinding的原因之一吧。个人觉得手动更新也不错。如有其它不同见解,请在评论区支出谢谢!
下一小节,开始真正的封装之旅。