1.MVP
Model-view-presenter (MVP) 是使用者接口设计模式的一种。 随着UI创建技术的功能日益增强,UI层也履行着越来越多的职责。为了更好地细分视图(View)与模型(Model)的功能,让View专注于处理数据的可视化以及与用户的交互,同时让Model只关系数据的处理,基于MVC概念的MVP 模式应运而生。
说明:
M层 对P层传递过来的信息(userInfo进行登录(网络请求))处理,处理完成之后将处理结果回调给P层
V层 负责响应用户的交互(获取数据—->提示操作结果)
P层 传递完数据给M层处理之后,实例化回调对象,成功了就通知V层登录成功,并将相关信息传给V层 ,失败了就通知V层显示错误信息,即数据逻辑的处理在P层,也相当于一个控制器,起到纽带的作用,这样也就使得M 层和V 层处于一个完全分离的状态,更好地专注其自身层的业务。
网上的这张图片说得更直观
2.MVP的优点编辑
1、模型与视图完全分离,我们可以修改视图而不影响模型
2、可以更高效地使用模型,因为所有的交互都发生在一个地方——Presenter内部
3、我们可以将一个Presenter用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁。
4、如果我们把逻辑放在Presenter中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试)
3.举个栗子
无图无真相,我们这是无代码无真理,下面简单地尝试实现一个用mvp 实现的登陆小栗子(ps:网上很多例子也是从登陆开始,估计比较简单吧)。
1.布局代码(用ConstraintLayout)
布局比较简单,可以直接忽略
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ECEDF1">
<TextView
android:id="@+id/tv_login_title"
android:layout_width="384dp"
android:layout_height="wrap_content"
android:background="#12B7F5"
android:gravity="center"
android:padding="20dp"
android:text="添加账号"
android:textColor="#ffffff"
android:textSize="18sp"
tools:ignore="HardcodedText,MissingConstraints"
tools:layout_editor_absoluteX="0dp"
tools:layout_editor_absoluteY="0dp" />
<TextView
android:id="@+id/tv_login_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#00000000"
android:padding="20dp"
android:text="取消"
android:textColor="#ffffff"
app:layout_constraintHorizontal_bias="0.99"
app:layout_constraintLeft_toLeftOf="@+id/tv_login_title"
app:layout_constraintRight_toRightOf="@+id/tv_login_title"
tools:ignore="HardcodedText,MissingConstraints"
tools:layout_editor_absoluteY="0dp" />
<EditText
android:id="@+id/et_login_name"
android:layout_width="0dp"
android:layout_height="32dp"
android:layout_marginBottom="16dp"
android:layout_marginTop="12dp"
android:background="#ffffff"
android:ems="10"
android:hint="账号/手机号/邮箱"
android:inputType="textPersonName"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/tv_login_title"
app:layout_constraintVertical_bias="0.13"
tools:ignore="HardcodedText,LabelFor,MissingConstraints,RtlHardcoded" />
<EditText
android:id="@+id/et_login_password"
android:layout_width="0dp"
android:layout_height="32dp"
android:layout_marginBottom="16dp"
android:layout_marginTop="2dp"
android:background="#ffffff"
android:ems="10"
android:hint="密码"
android:inputType="textPassword"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/et_login_name"
app:layout_constraintVertical_bias="0.0"
tools:ignore="HardcodedText,LabelFor" />
<Button
android:id="@+id/btn_login_to_login"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginBottom="16dp"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:background="@drawable/login_login_button_bg"
android:text="登 陆"
android:textColor="#ffffff"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.56"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/et_login_password"
app:layout_constraintVertical_bias="0.03"
tools:ignore="HardcodedText" />
<CheckBox
android:id="@+id/checkBox"
android:layout_width="wrap_content"
android:layout_height="32dp"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:text="@string/cb_login_agree"
android:textSize="8sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_login_to_login"
app:layout_constraintVertical_bias="0.03"
tools:ignore="MissingConstraints,SmallSp" />
</android.support.constraint.ConstraintLayout>
2.我们先看下包结构
简单的包结构如下:
各层次包名还算比较清晰,就不多说了。需要说明下,请求网络验证用户信息并没有做,只是做了模拟。
1.UserInfo.java 普通的bean,只有userName和userPassword字段以及get 和set方法。
2.ILoginModel.java
从名字上看就知道是 M层的接口,实现了登陆功能,代码如下
public interface ILoginModel {
//登录
void login(UserInfo userInfo, Result result);
}
3.LoginModel.java
M层接口实现类,负责处理网络请求返回相应结果
public class LoginModel implements ILoginModel {
@Override
public void login(UserInfo userInfo, Result result) {
//模拟时延
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//模拟网络请求部分
if("XX".equals(userInfo.getUserName())&&"123".equals(userInfo.getUserPassword())){
result.success();
}else {
result.error();
}
}
4.Reset.java
模拟网络请求结果返回
public interface Result {
void success();
void error();
}
5.ILoginPresenter.java
P 层接口,联通V 和M 层的桥梁。
public interface ILoginPresenter {
void login();
}
6.LoginPresenter.java
P层接口实现类,相关逻辑在此处理
public class LoginPresenter implements ILoginPresenter {
private ILoginModel loginModel;
private ILoginView loginView;
public LoginPresenter( ILoginView loginView) {
this.loginModel = new LoginModel();
this.loginView = loginView;
}
@Override
public void login() {
if(TextUtils.isEmpty(loginView.getUserLoginInfo().getUserName())){
loginView.showErrorMsg("用户名不能为空");
return;
}
if(TextUtils.isEmpty(loginView.getUserLoginInfo().getUserPassword())){
loginView.showErrorMsg("密码不能为空");
return;
}
loginModel.login(loginView.getUserLoginInfo(), new Result() {
@Override
public void success() {
loginView.showInfo("登陆成功!");
}
@Override
public void error() {
loginView.showErrorMsg("错误");
}
});
}
}
7.ILoginView.java
V层接口,View需要显示和用户响应操作都在这里。
public interface ILoginView {
void showInfo(String info);//提示用户
void showErrorMsg(String msg);//发生错误就显示错误信息
UserInfo getUserLoginInfo();//获取用户登录信息
}
8.MainActivity.java
V层实现类,也就是我们activity。
public class MainActivity extends AppCompatActivity implements ILoginView{
@Bind(R.id.et_login_name)
EditText etLoginName;
@Bind(R.id.et_login_password)
EditText etLoginPassword;
//一个p对象
private LoginPresenter presenter;
private Context mContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
mContext = this;
presenter = new LoginPresenter(this);
}
@OnClick({R.id.tv_login_cancel, R.id.btn_login_to_login, R.id.checkBox})
public void onClick(View view) {
switch (view.getId()) {
case R.id.tv_login_cancel:
finish();
break;
case R.id.btn_login_to_login:
presenter.login();
break;
case R.id.checkBox:
break;
}
}
@Override
public void showInfo(String info) {
Toast.makeText(mContext,"登陆"+info,Toast.LENGTH_SHORT).show();
}
@Override
public void showErrorMsg(String msg) {
Toast.makeText(mContext,"登陆错误"+msg,Toast.LENGTH_SHORT).show();
}
@Override
public UserInfo getUserLoginInfo() {
return new UserInfo(etLoginName.getText().toString().trim()
,etLoginPassword.getText().toString().trim());
}
}
一个简单mvp 模式的小栗子基本到这里就结束了,里面用到了ConstraintLayout布局和ButterKnife,需要在gradle 依赖中引入如下代码:
compile ‘com.android.support.constraint:constraint-layout:1.0.0-alpha9’
compile ‘com.jakewharton:butterknife:7.0.1’