一首凉凉送给自己,心累。
首先借鉴一下别人写的关于MVP的优缺点。。。
一、MVP模式优缺点
在说MVVM之前,简单回顾一下MVP分层,MVP总共分成三层:
a 、View: 视图层,对应xml文件与Activity/Fragment;
b 、Presenter: 逻辑控制层,同时持有View和Model对象;
c 、Model: 实体层,负责获取实体数据。
MVP模式有其很大的优点
1.解耦合,业务逻辑和视图分离;
2.项目代码结构(文件夹)清晰,一看就知道什么类干什么事情;
3.便于单元测试(其实还是第一点);
4.协同工作(例如在设计师没出图之前可以先写一些业务逻辑代码或者其他人接手代码改起来比较容易);
但是也有美中不足的部分,MVP模式的缺点如下:
1.Presente层与View层是通过接口进行交互的,接口粒度不好控制。粒度太小,就会存在大量接口的情况,使代码太过碎版化;粒度太大,解耦效果不好。因为View定义的方法并不一定全部要用到,可能只是后面要用到先定义出来(后面要不要删也未知),而且如果后面有些方法要删改,Presenter和Activity都要删改,比较麻烦;
2.V层与P层还是有一定的耦合度。一旦V层某个UI元素更改,那么对应的接口就必须得改,数据如何映射到UI上、事件监听接口这些都需要转变,牵一发而动全身。如果这一层也能解耦就更好了。
3.复杂的业务同时也可能会导致P层太大,代码臃肿的问题依然不能解决,这已经不是接口粒度把控的问题了,一旦业务逻辑越来越多,View定义的方法越来越多,会造成Activity和Fragment实现的方法越来越多,依然臃肿。
目前MVP模式已经用的很多了,大部分接触到的项目都是MVP,自己也写了很多MVP模式下的项目。一直都知道有MVVM这个框架,只是一直没有机会用,所以就自己学习一下。网上看了很多,都是简单的例子,所以在github上找了一个项目直接拿来练手了。
二、MVVM模式
OK,现在开始介绍MVVM,MVVM模式不是四层,同MVP一样也是三层,但是我不同意MVVM是MVP的升级版,二者有相同的地方,但是MVP的一些优点,MVVM也无法取代,MVVM的三层模型如下:
View
View层做的就是和UI相关的工作,我们只在XML和Activity或Fragment写View层的代码,View层不做和业务相关的事,也就是我们的Activity 不写和业务逻辑相关代码,也不写需要根据业务逻辑来更新UI的代码,因为更新UI通过Binding实现,更新UI在ViewModel里面做(更新绑定的数据源即可),Activity 要做的事就是初始化一些控件(如控件的颜色,添加 RecyclerView 的分割线),Activity可以更新UI,但是更新的UI必须和业务逻辑和数据是没有关系的,只是单纯的根据点击或者滑动等事件更新UI(如 根据滑动颜色渐变、根据点击隐藏等单纯UI逻辑),Activity(View层)是可以处理UI事件,但是处理的只是处理UI自己的事情,View层只处理View层的事。简单的说:View层不做任何业务逻辑、不涉及操作数据、不处理数据、UI和数据严格的分开。
ViewModel
ViewModel层做的事情刚好和View层相反,ViewModel 只做和业务逻辑和业务数据相关的事,不做任何和UI、控件相关的事,ViewModel 层不会持有任何控件的引用,更不会在ViewModel中通过UI控件的引用去做更新UI的事情。ViewModel就是专注于业务的逻辑处理,操作的也都是对数据进行操作,这些个数据源绑定在相应的控件上会自动去更改UI,开发者不需要关心更新UI的事情。DataBinding 框架已经支持双向绑定,这使得我们在可以通过双向绑定获取View层反馈给ViewModel层的数据,并进行操作。关于对UI控件事件的处理,我们也希望能把这些事件处理绑定到控件上,并把这些事件统一化,方便ViewModel对事件的处理和代码的美观。为此我们通过BindingAdapter 对一些常用的事件做了封装,把一个个事件封装成一个个Command,对于每个事件我们用一个ReplyCommand去处理就行了,ReplyCommand会把可能你需要的数据带给你,这使得我们处理事件的时候也只关心处理数据就行了,具体见MVVM Light Toolkit 使用指南的 Command 部分。再强调一遍ViewModel 不做和UI相关的事。
Model
Model 的职责很简单,基本就是实体模型(Bean)同时包括Retrofit 的Service ,ViewModel 可以根据Model 获取一个Bean的Observable( RxJava ),然后做一些数据转换操作和映射到ViewModel 中的一些字段,最后把这些字段绑定到View层上。
可以看到,MVVM模式的最大亮点是双向绑定
单向绑定上,数据的流向是单方面的,只能从代码流向UI;双向绑定的数据流向是双向的,当业务代码中的数据改变时,UI上的数据能够得到刷新;当用户通过UI交互编辑了数据时,数据的变化也能自动的更新到业务代码中的数据上。对于双向绑定,刚好可以使用DataBinding,DataBinding是一个实现数据和UI绑定的框架,是构建MVVM模式的一个关键的工具。所以Android中实现MVVM就方便多了,IOS中还要使用block回调,或者使用reactiveCocoa库。
三:MVVM体验:
1.配置:
在项目app中的build.gradle的android{}中加入:
dataBinding {
enabled = true
}
2.创建Model实体类:
实体类的创建和平时的没什么区别,这里取了个用户类做例子:
public class User implements Parcelable {
public long id;
public String name;
public String url;
public String email;
public String login;
public String location;
@SerializedName("avatar_url")
public String avatarUrl;
public User() {
}
public boolean hasEmail() {
return email != null && !email.isEmpty();
}
public boolean hasLocation() {
return location != null && !location.isEmpty();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(this.id);
dest.writeString(this.name);
dest.writeString(this.url);
dest.writeString(this.email);
dest.writeString(this.login);
dest.writeString(this.location);
dest.writeString(this.avatarUrl);
}
protected User(Parcel in) {
this.id = in.readLong();
this.name = in.readString();
this.url = in.readString();
this.email = in.readString();
this.login = in.readString();
this.location = in.readString();
this.avatarUrl = in.readString();
}
public static final Creator<User> CREATOR = new Creator<User>() {
public User createFromParcel(Parcel source) {
return new User(source);
}
public User[] newArray(int size) {
return new User[size];
}
};
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (id != user.id) return false;
if (name != null ? !name.equals(user.name) : user.name != null) return false;
if (url != null ? !url.equals(user.url) : user.url != null) return false;
if (email != null ? !email.equals(user.email) : user.email != null) return false;
if (login != null ? !login.equals(user.login) : user.login != null) return false;
if (location != null ? !location.equals(user.location) : user.location != null)
return false;
return !(avatarUrl != null ? !avatarUrl.equals(user.avatarUrl) : user.avatarUrl != null);
}
@Override
public int hashCode() {
int result = (int) (id ^ (id >>> 32));
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + (url != null ? url.hashCode() : 0);
result = 31 * result + (email != null ? email.hashCode() : 0);
result = 31 * result + (login != null ? login.hashCode() : 0);
result = 31 * result + (location != null ? location.hashCode() : 0);
result = 31 * result + (avatarUrl != null ? avatarUrl.hashCode() : 0);
return result;
}
3.创建布局:
和传统布局不同的是,这里的根布局是用layout来包裹:
<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">
//布局
</layout>
如果需要数据绑定,头部是这样的:
<data>
<variable
name="viewModel"
type="com.ckw.zfsoft.mvvmdemo.viewmodel.MainViewModel"/>
</data>
name可以随便取,type是你要用到的viewModel
使用DataBinding后,布局都是以标签作为根节点,这个布局 最终会生成一个Binding类,命名规则是:单词首字母大写,移除下划线,并在最后添加上Binding。我这里是activity_main.xml,所以生成的是ActivityMainBinding。
4.viewModel:
例如在xml中有一个控件是这样的:
<ProgressBar
android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/layout_search"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
app:visibility="@{viewModel.progressVisibility}"
/>
关键代码在这一行:
app:visibility="@{viewModel.progressVisibility}"
我们的viewModel中就可以定义一个参数,用于控制progress的可见性:
private ObservableInt progressVisibility;
初始设置为不可见:
progressVisibility = new ObservableInt(View.INVISIBLE);
设置可见:
progressVisibility.set(View.VISIBLE);
又比如,有个EditTextView:
<EditText
android:id="@+id/edit_text_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/button_search"
android:hint="@string/hit_username"
android:imeOptions="actionSearch"
android:inputType="text"
android:textColor="@color/white"
android:theme="@style/LightEditText"
android:onEditorAction="@{viewModel.onSearchAction}"
app:addTextChangedListener="@{viewModel.usernameEditTextWatcher}"
/>
设置监听:
android:onEditorAction="@{viewModel.onSearchAction}"
同样的,viewModel中需要写一个public方法:
public boolean onSearchAction(TextView view, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
String username = view.getText().toString();
if (username.length() > 0) loadGithubRepos(username);
return true;
}
return false;
}
这里的参数为什么要这么写呢?
对比一下我们正常的监听写法就知道啦:
EditText editText = new EditText(getApplicationContext());
editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
return false;
}
});
5.绑定:
最后我们还需要在Activity中对其进行绑定:
private ActivityMainBinding binding;
private MainViewModel mainViewModel;
在oncreate方法中:
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
mainViewModel = new MainViewModel(this, this);
binding.setViewModel(mainViewModel);
大体上就是这么使用的,科科