关于water的mvp框架的使用
注意:此文档供同组的 Android 开发 人员 阅读,其他人阅读可能会比较困难。
注意:本文档配合Databinding 配合 lambda表达式进行实现。
开发工具:Andriod Studio 3.1.3, Gradle 版本 4.4
由于标准的MVP模式比较麻烦,要创建Contract 类,并且要包含View和Presenter两个接口。结构上比较麻烦,所以,对其进行优化。
1.添加依赖:
(1) Databinding 使用添加依赖
//支持dataBinding
dataBinding {
enabled = true
}
(2) lambda表达式 使用添加依赖
//支持jdk1.8
compileOptions {
targetCompatibility 1.8
sourceCompatibility 1.8
}
注意:以上两个依赖都添加到要使用的 模块
例如:
android{
//其他依赖 省略
//支持dataBinding
dataBinding {
enabled = true
}
//支持jdk1.8
compileOptions {
targetCompatibility 1.8
sourceCompatibility 1.8
}
}
2.创建layout文件
以下 a_login.xml 布局文件(处理用户登录的页面)
注意:此页面比较复杂,不需要关注其全部内容,只需关注标红处的内容即可。
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="viewModel"
type="java.lang.String"/>
<variable
name="click"
type="android.view.View.OnClickListener"/>
</data>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--<include layout="@layout/title_bar"/>-->
<RelativeLayout
android:id="@+id/tool_bar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/white">
<com.wxkj.login.view.TextImageView
android:id="@+id/btnLeft"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:onClick="@{click}"
android:paddingLeft="18dp"
android:paddingRight="18dp"/>
<android.support.v7.widget.AppCompatTextView
android:id="@+id/toolbar_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="@color/grey_1"
android:textSize="16sp"/>
<com.wxkj.login.view.TextImageView
android:id="@+id/btnRight"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_gravity="right"
android:paddingLeft="18dp"
android:paddingRight="18dp"/>
</RelativeLayout>
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/view"
android:layout_alignLeft="@+id/line1"
android:layout_alignStart="@+id/line1"
android:background="@null"
android:text="登录"
android:textColor="@color/grey_1"
android:textSize="24sp"/>
<TextView
android:id="@+id/login_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_below="@id/tool_bar"
android:layout_marginRight="20dp"
android:layout_marginTop="25dp"
android:onClick="@{click}"
android:text="验证码登录"
android:textColor="@color/orange"
android:textSize="16sp"/>
<View
android:id="@+id/view"
android:layout_width="wrap_content"
android:layout_height="0.5dp"
android:layout_alignLeft="@id/login_type"
android:layout_alignParentRight="true"
android:layout_alignRight="@id/login_type"
android:layout_below="@id/login_type"
android:layout_marginRight="20dp"
android:layout_marginTop="2dp"
android:background="@color/orange"/>
<ImageView
android:id="@+id/line1"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_below="@+id/title"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="40dp"
android:background="@color/grey_3"/>
<LinearLayout
android:id="@+id/ll_telphone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/line1"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="15dp"
android:drawableLeft="@mipmap/tab_icon_login_iphone"
android:drawablePadding="15dp"
android:text="+86"
android:textColor="@color/grey_1"
android:textSize="18sp"/>
<LinearLayout
android:layout_width="0.5dp"
android:layout_height="match_parent"
android:layout_marginBottom="10dp"
android:layout_marginLeft="20dp"
android:layout_marginTop="10dp"
android:background="@color/grey_3"/>
<EditText
android:id="@+id/et_telphone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:gravity="right|center_vertical"
android:inputType="phone"
android:maxLength="11"
android:hint="请输入手机号"
android:paddingBottom="20dp"
android:paddingRight="20dp"
android:paddingTop="20dp"
android:text=""/>
</LinearLayout>
<ImageView
android:id="@+id/line2"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_below="@+id/ll_telphone"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:background="@color/grey_3"/>
<LinearLayout
android:id="@+id/et_password_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/line2"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp">
<EditText
android:id="@+id/et_password"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_below="@+id/line2"
android:layout_weight="3"
android:background="@null"
android:drawableLeft="@mipmap/tab_icon_login_password"
android:drawablePadding="10dp"
android:inputType="textPassword"
android:paddingBottom="20dp"
android:hint="请输入密码(6-15位数字与字母组合,如acb123)"
android:paddingLeft="10dp"
android:paddingTop="20dp"
android:text=""/>
<TextView
android:id="@+id/bt_get_checknum"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:gravity="right|center"
android:onClick="@{click}"
android:text="获取验证码"
android:textColor="@color/orange"
android:textSize="15sp"
android:visibility="gone"/>
</LinearLayout>
<ImageView
android:id="@+id/line3"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_below="@+id/et_password_layout"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:background="@color/grey_3"/>
<TextView
android:id="@+id/tvForgetPwd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_below="@+id/line3"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:onClick="@{click}"
android:text="忘记密码?"
tools:ignore="RtlHardcoded"/>
<!--<TextView-->
<!--android:id="@+id/tvTermsOfService"-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--android:layout_below="@+id/btn_register"-->
<!--android:layout_centerHorizontal="true"-->
<!--android:layout_marginTop="20dp"/>-->
<Button
android:id="@+id/btn_login"
style="?android:attr/borderlessButtonStyle"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_below="@+id/tvForgetPwd"
android:layout_centerHorizontal="true"
android:layout_marginTop="40dp"
android:background="@drawable/shape_button_orange_1"
android:onClick="@{click}"
android:text="登录"
android:textColor="@color/orange"/>
<Button
android:id="@+id/btn_register"
style="?android:attr/borderlessButtonStyle"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_below="@+id/btn_login"
android:layout_centerHorizontal="true"
android:layout_marginTop="10dp"
android:background="@drawable/shape_button_grey"
android:onClick="@{click}"
android:text="注册"
android:textColor="@color/grey_1"/>
<LinearLayout
android:id="@+id/ll_user_agreement"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/btn_register"
android:layout_marginTop="20dp"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="注册登录即表示同意"
android:textColor="@color/grey_2"
android:textSize="@dimen/sp_14"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TextView
android:id="@+id/tv_a_home_user_agreement"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{click}"
android:text="xxx服务条款协议"
android:textColor="@color/grey_2"
android:textSize="@dimen/sp_14"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@id/tv_a_home_user_agreement"
android:layout_marginTop="-2dp"
android:background="#000"
android:paddingBottom="0dp"/>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</layout>
3.创建Activity 引入布局文件。
/**
* 登录页面(主要处理页面展示)
* 作者:Laughing on 2018/9/4 17:42
* 邮箱:719240226@qq.com
*/
public class A_Login extends BaseActivity implements LoginView {
private LoginPresenter mPresenter;
private ALoginBinding mBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);//软键盘弹出后,让屏幕整体上移
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.a_login);
initView();
initData();
initListener();
}
private void initData() {
mPresenter = new LoginPresenter(this, this, this);
}
private void initListener() {
//mBinding.setClick() 方法 对应a_login.xml中
//<variable
// name="click"
// type="android.view.View.OnClickListener"/>
mBinding.setClick(view -> {
//点击事件处理 方法1:在presenter中 进行点击事件处理,这里通常传递一个 view 参数即可,因为要做效验的逻辑处理, //所以本方法把电话号码的文本也传递了过去。
mPresenter.click(view, mBinding.etTelphone.getText().toString().trim());
//点击事件处理 方法2: 在activity中进行逻辑处理 使得activity中的逻辑比较多
if (view.getId() == R.id.btn_login) {
//登录
if (mBinding.loginType.getText().equals("密码登录")) {
//验证码登录
login(0);
} else {
//密码登录
login(1);
}
} else if (view.getId() == R.id.btnLeft) {
// 左上角 X 号
// finish();
Intent intent = null;
try {
intent = new Intent(mContext, Class.forName("com.wxkj.ycw.ui.activity.A_Home"));
intent.getBooleanExtra("refreshAPK", false);
startActivity(intent);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} else if (view.getId() == R.id.login_type) {
//登录方式
loginType();
} else if (view.getId() == R.id.tvForgetPwd) {
// 忘记密码? 页面跳转
Intent intent = new Intent(A_Login.this, RegisterMobileActivity.class);
intent.putExtra(EXTRA_RETRIEVE_PASSWORD, true);//忘记密码
startActivity(intent);
// finish();
}
//点击事件处理 方法3:此方法不推荐,只是展示出来给大家了解。
//单独的点击事件跳转到 用户协议 页面
mBinding.tvAHomeUserAgreement.setOnClickListener(v1 -> {
// 跳转到用 户协议页面 在这里可以做任何自己想要的处理
Intent intent = new Intent(mContext, WebActivity.class);
intent.putExtra(WebActivity.EXTRA_TITLE, "用户协议");
intent.putExtra(WebActivity.EXTRA_WEB_URL, BaseApplication.H5_URL + "?type=userAgreement");
A_Login.this.startActivity(intent);
});
});
}
/**
* 获取验证码 成功
*/
@Override
public void getVerificationCodeSuccess() {
TimerUtil.countDown(mBinding.btGetChecknum);//开始倒计时
ToastUtil.showToast(mContext, "验证码已发送");
}
}
4.LoginView.java(负责LoginPresenter与A_Activity之间通讯)
/**
* 登录页面
* 作者:Laughing on 2018/9/4 17:54
* 邮箱:719240226@qq.com
*/
public interface LoginView {
/**
* 获取验证码 成功
*/
void getVerificationCodeSuccess();
}
5.LoginPresenter.java(负责逻辑处理与网络请求)
/**
* 登录页面
* 作者:Laughing on 2018/9/4 17:53
* 邮箱:719240226@qq.com
*/
public class LoginPresenter {
private LoginView mView;
private BaseActivity activity;
private LifecycleProvider lifecycleProvider;
private final HttpManager mManager;
public LoginPresenter(LoginView mView, BaseActivity activity, LifecycleProvider lifecycleProvider) {
this.mView = mView;
this.activity = activity;
this.lifecycleProvider = lifecycleProvider;
mManager = new HttpManager(activity, lifecycleProvider);
}
public void click(View view, String phoneNum) {
if (view.getId() == R.id.btn_register) {
//注册
RegisterMobileActivity.startActivity(activity);
// activity.finish();//去注册时 关闭登录页面
} else if (view.getId() == R.id.bt_get_checknum) {
//获取验证码
if (StrUtil.isMobileNo(phoneNum))
getVerificationCode(phoneNum);
else
ToastUtil.showToast(activity, "请输入正确的手机号");
}
}
/**
* 发送网络请求 获取验证码
*
* @param phoneNum
*/
private void getVerificationCode(String phoneNum) {
HashMap<String, String> map = new HashMap<>();
map.put("telephone", phoneNum);
// mManager.doHttpDeal(RetrofitHelper.getApiService().getVerificationCode(phoneNum)
mManager.doHttpDeal(RetrofitHelper.getApiService().getVerificationCode(map)
, new DefaultObserver<Object>(activity) {
@Override
public void onSuccess(Object response) {
//网络访问成功后需要处理页面刷新 或做跳转,此时回调 LoginView 接口中的方法即可。
mView.getVerificationCodeSuccess();
}
});
}
/**
* 去登录
*
* @param telephone
* @param password
* @param type
*/
public void sendHttpLogin(final String telephone, final String password, final int type) {
// LoginReq loginReq = new LoginReq("", "", telephone, password, "1");
HashMap<String, Object> map = new HashMap<>();
if (type == 1) {
//密码 登录
map.put("telephone", telephone);
map.put("password", MD5Util.MD5(password));
map.put("action", "1");
// mManager.doHttpDeal(RetrofitHelper.getApiService().login2(telephone, MD5Util.MD5(password), "1")
mManager.doHttpDeal(RetrofitHelper.getApiService().login2(map)
// mManager.doHttpDeal(RetrofitHelper.getApiService().login2(loginReq)
, new DefaultObserver<UserLoginRep>(activity) {
@Override
public void onSuccess(UserLoginRep response) {
loginSuccess(response);
// ToastUtil.showToast(activity, "click--->" + response.getTelephone());
}
});
} else if (type == 0) {
//验证码 登录
map.put("mobile", telephone);
map.put("code", password);
map.put("openId", "");
// mManager.doHttpDeal(RetrofitHelper.getApiService().login(telephone, MD5Util.MD5(password), "0")
mManager.doHttpDeal(RetrofitHelper.getApiService().login(map)
, new DefaultObserver<UserLoginRep>(activity) {
@Override
public void onSuccess(UserLoginRep response) {
loginSuccess(response);
}
});
}
}
/**
* 登录成功后保存数据
*
* @param response
*/
private void loginSuccess(UserLoginRep response) {
Toast.makeText(activity, "登录成功", Toast.LENGTH_SHORT).show();
AppConfig.flag = true;
Intent intent = null;
try {
intent = new Intent(activity, Class.forName("com.wxkj.ycw.ui.activity.A_Home"));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// intent.putExtra("data", bean);
activity.startActivity(intent);
//登录成功保存 token 与用户信息
saveUserInfo(response);
activity.finish();
}
/**
* 保存用户信息
*
* @param response
*/
private void saveUserInfo(UserLoginRep response) {
//缓存数据
PreferencesManager preferencesManager = PreferencesManager.getInstance(BaseApplication.getAppContext());
preferencesManager.put("token", response.getToken());//
PreferencesManagerForever.getInstance(activity).put("mobile", response.getTelephone());//手机号
LogUtil.e("TAG", "token----------------------> " + response.getToken());
}
}
6.BaseActivity.java (负责Activity生命周期控制与其他处理)
/**
* 基础的activity 实现Retrofit生命周期
* Created by Water on 2018/3/26.
*/
public class BaseActivity extends AppCompatActivity implements LifecycleProvider<ActivityEvent> {
protected Context mContext = this;
protected String TAG;
protected ProgressDialog proDia;
/**
* 全局的LayoutInflater对象,已经完成初始化.
*/
protected LayoutInflater mInflater;
@Override
@CallSuper
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//8.1不能使用透明主题
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
this.setTheme(R.style.MyAppTheme);//不透明
} else {
this.setTheme(R.style.MyAppThemeTranslucent);//透明主题
}
AppManager.getAppManager().addActivity(this);
lifecycleSubject.onNext(ActivityEvent.CREATE);
// 不显示标题
getSupportActionBar().hide();
// 设置竖屏显示
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
TAG = this.getClass().getSimpleName();
mInflater = LayoutInflater.from(this);
proDia = new ProgressDialog(this);
// proDia.setTitle("正在请求网络");
proDia.setMessage("努力加载中。。。");
//状态栏入侵
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
&& isFullStatusBar()) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
}
/**
* 是否入侵状态栏,默认入侵
* true 入侵
* false 不入侵
*/
protected boolean isFullStatusBar() {
return true;
}
private final BehaviorSubject<ActivityEvent> lifecycleSubject = BehaviorSubject.create();
@Override
@NonNull
@CheckResult
public final Observable<ActivityEvent> lifecycle() {
return lifecycleSubject.hide();
}
@Override
@NonNull
@CheckResult
public final <T> LifecycleTransformer<T> bindUntilEvent(@NonNull ActivityEvent event) {
return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
}
@Override
@NonNull
@CheckResult
public final <T> LifecycleTransformer<T> bindToLifecycle() {
return RxLifecycleAndroid.bindActivity(lifecycleSubject);
}
@Override
@CallSuper
protected void onStart() {
super.onStart();
lifecycleSubject.onNext(ActivityEvent.START);
}
@Override
@CallSuper
protected void onResume() {
super.onResume();
lifecycleSubject.onNext(ActivityEvent.RESUME);
}
@Override
@CallSuper
protected void onPause() {
lifecycleSubject.onNext(ActivityEvent.PAUSE);
super.onPause();
}
@Override
@CallSuper
protected void onStop() {
lifecycleSubject.onNext(ActivityEvent.STOP);
super.onStop();
}
@Override
@CallSuper
protected void onDestroy() {
lifecycleSubject.onNext(ActivityEvent.DESTROY);
super.onDestroy();
}
}
7.总体描述
本设计主要作用是对页面展示 与 业务逻辑进行分离, activity 负责页面展示与刷新,presenter 负责业务逻辑处理与网络访问获取数据。view类是presenter与activity之间的桥梁,负责回传presenter 的处理结果。
activity通过调用presenter中的方法进行业务逻辑处理或网络请求,之后再通过 view类把 数据的结果反馈给activity,activity再进行数据展示。
最后:网络访问通过Retrofit进行。此处不再粘贴代码。