- 什么是MVVM?
Model–View–ViewModel软件架构模式。
MVVM将图形用户界面的开发与业务逻辑或后端逻辑的开发分离。
M(Model)层:模型,定义数据结构。(如Activity、Fragment)
C(Controller)层:实现业务逻辑。在MVVM模式中一般把C层算在M层中。
ViewModel层:ViewModel层的职责是用来处理中转逻辑,将真正获取数据的操作交给Model去执行,然后将获取到的数据更改到自身的LiveData中去。LiveData中的数据改变后,会去通知监听它改变的、当前状态是可见的View们去更改UI。
V(View)层:将ViewModel通过特定的GUI展示出来,并在GUI控件上绑定视图交互事件。UI响应式更新。
注:View层不做任何业务逻辑、不涉及操作数据、不处理数据,UI和数据严格的分开
MVVM利用数据绑定、依赖属性、命令、路由事件等新特性,打造了一个更加灵活高效的架构。MVVM是一种架构模式,DataBinding是一个实现数据和UI绑定的框架,是构建MVVM模式的一个工具,两者不能混为一谈。
数据驱动
在常规的开发模式中,数据变化需要更新UI的时候,需要先获取UI控件的引用,然后再更新UI。在MVVM中,这些都是通过数据驱动来自动完成的,数据变化后会自动更新UI,UI的改变也能自动反馈到数据层,也就是双向绑定。数据成为主导因素。这样MVVM层在业务逻辑处理中只要关心数据,不需要直接和UI打交道,在业务处理过程中简单方便很多。
低耦合度
MVVM模式中,数据是独立于UI的。
数据和业务逻辑处于一个独立的ViewModel中,ViewModel只需要关注数据和业务逻辑,不需要和UI或者控件打交道。UI想怎么处理数据都由UI自己决定,ViewModel不涉及任何和UI相关的事,也不持有UI控件的引用。即便是控件改变了(比如:TextView换成EditText),ViewModel也几乎不需要更改任何代码,以此解耦了View层和ViewModel。
更新UI
在MVVM中,数据发生变化后,我们在工作线程直接修改ViewModel的数据即可,不需要考虑切到主线程更新UI,框架已经帮助我们做了。
2、MVVM基类(以Activity为例)
(1)页面常用接口类ViewBehavior
public interface ViewBehavior {
/** 是否显示Loading视图*/
void showLoadingUI(String msg);
/**消失Loading视图*/
void dismissLoadingUI();
/**弹出Toast提示*/
void showToast(String msg);
/**页面跳转*/
void openActivity(Intent intent);
void openActivityForResult(Intent intent, int requestCode);
/**设置回调*/
void vmSetResult(int resultCode, Intent data) ;
/**返回键点击*/
void backPressed();
/**关闭页面*/
void finishPage();
}
(2)ViewModel感知生命周期接口
public interface ViewModelLifecycle extends LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_ANY)
void onAny(LifecycleOwner owner, Lifecycle.Event event);
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
void onCreate();
@OnLifecycleEvent(Lifecycle.Event.ON_START)
void onStart();
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
void onResume();
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
void onPause();
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
void onStop();
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
void onDestroy();
}
(3)基于MVVM模式的Activity的基类
public abstract class BaseBVMActivity<B extends ViewDataBinding, VM extends BaseViewModel> extends BaseBindingActivity<B> implements ViewBehavior {
protected VM mViewModel;
@Override
protected void init(Bundle savedInstanceState) {
initialize(savedInstanceState);
injectViewModel();
viewModelInitialized(savedInstanceState);
initInternalObserver();
}
/**此方法可直接使用viewModel*/
protected void viewModelInitialized(Bundle savedInstanceState) {
}
protected void injectViewModel() {
VM vm = createViewModel();
//通过ViewModelProvider.Factory创建ViewModel,这样实例化ViewModel的时候, 就可以构造函数传参
mViewModel = (VM) new ViewModelProvider(this, BaseViewModel.createViewModelFactory(vm)).get(vm.getClass());
mViewModel.mContext = getApplication();
getLifecycle().addObserver(mViewModel);
mBinding.setVariable(getVariableId(), mViewModel);
}
protected void initInternalObserver() {
mViewModel.loadingEvent.observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
showLoadingUI(s);
}
});
ViewBehavior接口事件处理
}
}
@Override
protected void onDestroy() {
super.onDestroy();
getLifecycle().removeObserver(mViewModel);
}
/**初始化操作*/
protected abstract void initialize(Bundle savedInstanceState);
**创建ViewModel实例*/
protected abstract VM createViewModel();
/**初始化ViewModel的id*/
public abstract int getVariableId();
}
(4)ViewModel基类
/**
ViewModel只做和业务逻辑和业务数据相关的事,不做任何和UI相关的事情,ViewModel 层不会持有任何控件的引用,更不会在ViewModel中通过UI控件的引用去做更新UI的事情。
*/
public class BaseViewModel extends ViewModel implements ViewModelLifecycle,
ViewBehavior {
// loading视图显示Event
protected MutableLiveData<String> loadingEvent = new MutableLiveData<>();
// 弹出Toast提示Event
protected MutableLiveData<String> showToastEvent = new MutableLiveData<>();
// 生命周期感知
protected LifecycleOwner mLifecycleOwner;
protected Application mContext;
@Override
public void showLoadingUI(String msg) {
loadingEvent.postValue(msg);
}
@Override
public void showToast(String msg) {
showToastEvent.postValue(msg);
}
@Override
public void onAny(LifecycleOwner owner, Lifecycle.Event event) {
this.mLifecycleOwner = owner;
}
@Override
public void onCreate() {
}
onResume、onPause、onStart、onStop...
@Override
public void onDestroy() {
}
public static ViewModelProvider.Factory createViewModelFactory(BaseViewModel baseViewModel) {
return new ViewModelFactory(baseViewModel);
}
private static class ViewModelFactory implements ViewModelProvider.Factory {
protected BaseViewModel viewModel;
public ViewModelFactory(BaseViewModel viewModel) {
this.viewModel = viewModel;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T) viewModel;
}
}
}
(5)BindingAdapter
3、M—V—VM之间交互
· Model与ViewModel之间的双向关系
Model持有ViewModel对象,使用ViewModel里的公有方法及成员变量。
ViewModel可以获取到Model生命周期,MutableLiveData数据监控,从而影响到Model
Activity创建对应ViewModel对象,并在基类里将其与xml布局进行关联。
上层创建
@Override
protected AnchorPersonViewModel createViewModel() {
return new AnchorPersonViewModel();
}
基类关联
protected void injectViewModel() {
VM vm = createViewModel();
//通过ViewModelProvider.Factory创建ViewModel,这样实例化ViewModel的时候,就可以构造函数传参
mViewModel = (VM) new ViewModelProvider(this, BaseViewModel.createViewModelFactory(vm)).get(vm.getClass());
mViewModel.mContext = getApplication();
getLifecycle().addObserver(mViewModel);
mBinding.setVariable(getVariableId(), mViewModel);
}
· ViewModel与View之间的双向关系
ViewModel中的数据改变,可以同时改变View上的显示内容。
View上的内容改变(比如输入框中的内容),也可以同时改变ViewModel中对应的数据。即双向绑定。
双向绑定标识符:@={xxx}
示例:
<EditText
android:layout_width="match_parent"
android:layout_height="50dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_view"
android:id="@+id/et_view"
android:text="@={viewModel.text}"
android:textSize="22sp"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/et_view"
android:id="@+id/tv_view2"
android:text="@{viewModel.text}"
android:textSize="22sp"
/>
此时EditText进行了双向绑定,TextView单向绑定。在EditText中编辑文本TextView会同步更改。
4、MutableLiveData简介
- API说明
postValue()的特性如下:
1.此方法可以在其他线程中调用
2.如果在主线程执行发布的任务之前多次调用此方法,则仅将分配最后一个值。
3.如果同时调用 .postValue(“a”)和.setValue(“b”),一定是值b被值a覆盖。
setValue():此方法只能在主线程里调用
getValue():返回当前值。
observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer)
设置此LiveData数据当前activity或者Fragment的观察者,会给此activity或者Fragment在前台时回调数据。
removeObserver(@NonNull final Observer<? super T> observer) 移除指定的观察者
- 使用
MutableLiveData在ViewModel中进行定义,数据填充。在Activity中进行数据监听。在xml中通过获取到的MutableLiveData数据对View进行改变。这里MutableLiveData里存储的可以是网络接口、数据库等任意渠道获取的数据
public final MutableLiveData<Boolean> viewShow = new MutableLiveData<>(true);
public View.OnClickListener viewShowClick = v -> viewShow.setValue(!viewShow.getValue());
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="绑定了MutableLiveData组件数据"
android:id="@+id/tv_view"
android:visibility="@{viewModel.viewShow?View.VISIBLE:View.GONE}"
android:textSize="22sp"
android:textColor="#382176"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_show_view"
/>
@Override
protected void initInternalObserver() {
super.initInternalObserver();
//MutableLiveData组件数据监听
mainViewModel.viewShow.observe(this, new Observer<Boolean>() {
@Override
public void onChanged(Boolean aBoolean) {
Log.i(TAG, "onChanged: " + aBoolean);
showToast("view show change:" + aBoolean);
}
});
}
5、使用
(1)dataBinding库引入:
android {
......
buildFeatures {
dataBinding = true
}
}
- xml中引用ViewModel
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.sf.mvvm_demo.viewmodel.MainViewModel" />
<import type="android.view.View" />
</data>
<TextView/>......
</layout>
- 绑定点击事件,并通过MutableLiveData组件改变View
public final MutableLiveData<Boolean> viewShow = new MutableLiveData<>(true);
public View.OnClickListener viewShowClick = v -> viewShow.setValue(!viewShow.getValue());
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="绑定了viewShowClick点击事件"
android:onClick="@{viewModel.viewShowClick}"
android:id="@+id/tv_show_view"
android:textSize="22sp"
android:textColor="@color/black"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="绑定了MutableLiveData组件数据"
android:id="@+id/tv_view"
android:visibility="@{viewModel.viewShow?View.VISIBLE:View.GONE}"
android:textSize="22sp"
android:textColor="#382176"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_show_view"
/>
- RecyclerView示例
RecyclerView需要绑定adapter(适配器)、itemBinding(布局)、items(数据)
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="400dp"
app:layout_constraintTop_toBottomOf="@id/tv_view2"
app:layout_constraintStart_toStartOf="parent"
android:id="@+id/rv_mvvm_demo"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:adapter="@{viewModel.mvvmDemoAdapter}"
app:itemBinding="@{viewModel.itemBinding}"
app:items="@{viewModel.listMutableLiveData}"
/>
绑定adapter:
通过app:adapter属性绑定adapter。这里使用的是BindingRecyclerViewAdapter。adapter不负责填充数据。
在onBindBinding方法里完成列表条目的业务逻辑,很多简单的列表可以不需要adapter也可以完成数据展示。
public class MvvmDemoAdapter extends BindingRecyclerViewAdapter<MvvmDemoBean> {
@Override
public void onBindBinding(@NonNull ViewDataBinding binding, int variableId, int layoutRes, int position, MvvmDemoBean item) {
super.onBindBinding(binding, variableId, layoutRes, position, item);
View rootView = binding.getRoot();
rootView.findViewById(R.id.tv_name).setBackgroundColor(Color.RED);
}
}
app:adapter="@{viewModel.mvvmDemoAdapter}"
绑定列表数据
通过app:items属性绑定ViewModel中的集合数据,此时RecyclerView的数据展示随着listMutableLiveData 变化而变化,只需要保证listMutableLiveData 数据正常即可。
public final MutableLiveData<List<MvvmDemoBean>> listMutableLiveData = new MutableLiveData<>();
@Override
public void onCreate() {
super.onCreate();
listMutableLiveData.setValue(getListData());
}
public View.OnClickListener addListDataClick = v -> addListData(listMutableLiveData);
public void addListData(MutableLiveData<List<MvvmDemoBean>> list) {
List<MvvmDemoBean> mvvmDemoBeanList = new ArrayList<>(list.getValue());
for (int i = 0; i < 2; i++) {
MvvmDemoBean mvvmDemoBean = new MvvmDemoBean();
mvvmDemoBean.setName("李四" + i);
mvvmDemoBean.setCountry("伊拉克" + i);
mvvmDemoBeanList.add(mvvmDemoBean);
}
list.setValue(mvvmDemoBeanList);
}
app:items="@{viewModel.listMutableLiveData}"
绑定xml布局
通过app:itemBinding属性绑定列表条目xml布局。bindExtra是Object类型,可以通过bindExtra添加点击事件、图形变换(如圆角)等任意事件。
public final ItemBinding<Object> itemBinding = ItemBinding.of(BR.item, R.layout.mvvm_item)
// .bindExtra(BR.transform, ImageTransform.ROUNDED_CORNERS_4DP)
//注意顺序,需要先定义点击事件,然后绑定
.bindExtra(BR.onItemClick,onItemClick)
.bindExtra(BR.onItemNameClick,onItemNameClick)
.bindExtra(BR.onItemCountryClick,onItemCountryClick);
app:itemBinding="@{viewModel.itemBinding}"
开发中遇到列表中需要不同布局的情况时:
public final OnItemBind<MvvmDemoBean> itemBindingType = new OnItemBind<MvvmDemoBean>() {
@Override
public void onItemBind(@NonNull ItemBinding itemBinding, int position, MvvmDemoBean item) {
switch (item.getType()) {
case 0:
itemBinding.set(BR.item, R.layout.mvvm_item);
break;
case 1:
itemBinding.set(BR.item, R.layout.mvvm_item2);
break;
}
itemBinding
.bindExtra(BR.onItemClick,onItemClick)
.bindExtra(BR.onItemNameClick,onItemNameClick)
.bindExtra(BR.onItemCountryClick,onItemCountryClick);
}
};
- MVVM下的Activity→Fragment、Fragment→Fragment数据通讯
在MVVM框架下,Fragment是可以获取到Activity的ViewModel的,
传统数据通讯Intent、单例、静态变量:操作繁琐、数据大小有限制、容易内存泄漏
ViewModel数据通讯:
横竖屏切换数据不会销毁;
直接通过ViewModelProvider获取共同的数据对象,无需主动进行数据传递;
数据生命周期由ViewModel内部掌控,无需手动管理销毁;
@Override
protected void initInternalObserver() {
super.initInternalObserver();
MainViewModel mainViewModel = new ViewModelProvider(getActivity()).get(MainViewModel.class);
mainViewModel.shareData.observe(this, new Observer<Boolean>() {
@Override
public void onChanged(Boolean aBoolean) {
mBinding.tvData.setText("Fragment1监听到数据变化" + aBoolean);
}
});
}
MainActivityViewModel中的数据对象
public final MutableLiveData<Boolean> shareData = new MutableLiveData<>(false);
public View.OnClickListener shareDataClick = v -> shareData.setValue(!shareData.getValue());