最近LZ在研究MVP设计模式,以前写代码的时候 只是做一些封装 基本没有接触过设计模式 , 尝试过MVP设计模式之后 突然感觉逼格高了一个等级,个人觉得只要肯下功夫 android 还是一门比较容易的语言. 好了直接开始我的MVP之旅 .
关于MVP设计模式的一些概念就不在详细说明了,不了解的先自行百度吧!
Model: 负责数据的检索,持久化等操作
View: 负责UI的绘制和用户的交互
Presenter: 作为Model和View的中间协调部分,负责两者之间的业务逻辑处理
先上基类: BasePresenter map集合是为okhttp的post请求准备的 后续会有
/**
*
* <p/>
* 明确泛型 V 是一个View角色要实现的接口类型
* (所有的View接口都要继承IBaseView接口可以把所有接口都要拥有的方法放到IBaseView)
*
* @param <T>代表view接口
* Created by ${苗春良}
* on 2016/11/23.
*/
public abstract class BasePresenter<T> {
/**
* post请求参数集合 使用前请清空
*/
protected Map<String,String> map=new HashMap<>();
//View接口类型的弱引用
protected Reference<T> mViewRef;
/**
* @param view presenter 与view进行绑定
*/
public void attachView(T view){
mViewRef=new WeakReference<T>(view);
}
protected T getView(){
return mViewRef.get();
}
/**
* @return 是否关联
*/
public boolean isViewAttached(){
return mViewRef!=null&&mViewRef.get()!=null;
}
/**
* 解除关联
*/
public void detachView(){
if (mViewRef!=null){
mViewRef.clear();
mViewRef=null;
}
}
}
基类 BaseActivity
/**
* Created by ${苗春良}
* on 2016/11/23.
*/
public abstract class BaseActivity<V, T extends BasePresenter<V>> extends AppCompatActivity {
protected T mPresenter;
/**
* 两次点击的间隔时间
*/
private static final int QUIT_INTERVAL = 2000;
/**
* 上次点击返回键的时间
*/
private long lastBackPressed;
@SuppressWarnings("unchecked")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPresenter = createPresenter();
//presenter层和view层绑定
mPresenter.attachView((V) this);
initVariables();
initViews(savedInstanceState);
initData();
initToolBar(isShowToolBar());
//开启友盟测试
MobclickAgent.setDebugMode(false);
}
/**
* @return 是否显示标题栏
*/
protected abstract boolean isShowToolBar();
/**
* 初始化titlebar
*/
private void initToolBar(boolean isShow) {
if (!isShow) {
return;
} else {
TextView mTvTitle = (TextView)findViewById(R.id.tv_title);
Toolbar mToolBar = (Toolbar)findViewById(R.id.toolbar);
initSetting(mToolBar, mTvTitle);
}
}
/**如果没有标题栏 此方法不实现
*
* 初始化设置 居中: 将mToolbar设置为空 标题使用mTvTitle设置
* 居左: mToolbar设置标题 mTvTitle设置为空
*/
protected abstract void initSetting(Toolbar mToolbar, TextView mTvTitle);
/**
* 加载数据操作
*/
protected abstract void initData();
/**
* @param savedInstanceState 初始化布局
*/
protected abstract void initViews(Bundle savedInstanceState);
/**
* 初始化变量 接收从其他界面传递过来的参数
*/
protected abstract void initVariables();
/**
* @return 创建presenter
*/
protected abstract T createPresenter();
@Override
protected void onDestroy() {
super.onDestroy();
if (mPresenter != null)
//解除绑定
mPresenter.detachView();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
long backPressed = System.currentTimeMillis();
if (backPressed - lastBackPressed > QUIT_INTERVAL) {
lastBackPressed = backPressed;
Toast.makeText(this, "再按一次退出", Toast.LENGTH_SHORT).show();
} else {
finish();
MobclickAgent.onKillProcess(this);
System.exit(0);
}
return true;
}
return super.onKeyDown(keyCode, event);
}
@Override
public void onBackPressed() {
long backPressed = System.currentTimeMillis();
if (backPressed - lastBackPressed > QUIT_INTERVAL) {
lastBackPressed = backPressed;
Toast.makeText(this, "再按一次退出", Toast.LENGTH_LONG).show();
} else {
finish();
MobclickAgent.onKillProcess(this);
System.exit(0);
}
}
/**
* 关闭当前界面 启动新界面
*
* @param clazz 启动界面方法 带有动画效果
*/
protected void startActivity(Class clazz) {
Intent intent = new Intent(UIUtils.getContext(), clazz);
startActivity(intent);
finish();
overridePendingTransition(R.anim.activity_open, R.anim.activity_close);
}
public void onResume() {
super.onResume();
mPresenter.attachView((V) this);
MobclickAgent.onResume(this);
}
public void onPause() {
super.onPause();
MobclickAgent.onPause(this);
}
BaseActivity中 主要对presenter和view进行绑定 和 解绑的操作 由于项目中需要修改标题 因此使用了toolbar 布局也很简单
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="@dimen/toolBarHeight"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:paddingTop="@dimen/toolBarPaddingTop"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:theme="@style/ThemeOverlay.AppCompat.ActionBar">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_gravity="center"
android:text="首页"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold" />
</android.support.v7.widget.Toolbar>
使用方式也很简单 代码中注释的很详细了只需要在需要的界面include布局 , 修改一下是否显示就行了
上一个Contract契约类 LZ认为这种写法看起来很爽 有什么功能一次性可以全看完
这个是一个baserecyclerViewAdapterHelperV2.4.4做的一个list列表 其实view层很好理解 就是直接与用户进行交互, 比如说用户开启一个新的界面 界面可交互时先刷新数据 刷新数据 就有展示进度条 隐藏进度条(当然不是每个都这样 自行修改) 获取数据成功 失败 ==的操作
model层的操作很简单 处理数据 包括获取数据 缓存数据 保存数据等等的操作 由于项目暂时没有加入缓存 就一笔带过吧
presenter层的操作就一条 调用model层获取数据的方法 OnLoadNewsListListener 这个借口是自己写的观测数据的接口 下面也有
/**
* Created by ${苗春良}
* on 2016/11/23.
* 信息列表 契约类
*/
public class 自己改名{
public interface View {
void showProgress();
void addData(List<BackvehicleBean.ListBean> newsList);
void hideProgress();
void showLoadFailMsg();
void showNetWorkException();
}
public interface Presenter{
void loadData(int page);
}
public interface Moudel {
void getDataFromNet(String url, Map<String, String> map, OnLoadNewListListener onLoadNewListListener);
}
}
OnLoadNewsListListener 新数据监听接口
/**
* 公用接口
* Created by ${苗春良}
* on 2016/11/22.
*/
public interface OnLoadNewListListener<T> {
/**
* @param list 获取数据成功 拿到集合
*/
void onSuccess(List<T> list);
/**
* 获取数据失败
* @param msg
* @param e
*/
void onFailure(String msg, Exception e);
/** 网络连接失败监听
* @param msg
*/
void onNetWorkException(String msg);
}
还有一个 提交数据监听的接口
/**
* 提交数据接口
* Created by ${苗春良}
* on 2016/11/23.
*/
public interface OnCommitDataListener {
/**
* 提交数据成功
*/
void onSuccess();
/**
* @param msg 提交数据失败 msg错误信息
*/
void onFailure(String msg);
}
model层代码
/**
* Created by root on 2016/11/23
*/
public class 实现 implements Contract.Model {
private BackvehicleBean mBackvehicleBean;
private List<BackvehicleBean.ListBean> mList;
private HttpUtil.HttpCallBack mHttpCallBack;
private String TAG = "miao";
@Override
public void getDataFromNet(String url, Map<String, String> map, final OnLoadNewsListListener onLoadNewsListListener) {
Log.i(TAG, "getDataFromNet: 调用");
HttpUtil httpUtil = new HttpUtil();
mHttpCallBack = new HttpUtil.HttpCallBack() {
@Override
public void onError(String meg) {
onLoadNewsListListener.onNetWorkException("网络连接失败!");
}
@Override
public void onSusscess(String data) {
Log.i(TAG, "onSusscess: 连接成功" + data);
//请求成功 解析数据
try {
JSONObject jsonObject = new JSONObject(data);
String code = jsonObject.getString("code");
if (code.equals("S")) {
processData(jsonObject.getString("data"), onLoadNewsListListener);
} else if (code.equals("F")) {
//请求失败
UIUtils.showToast(jsonObject.getString("msg"));
}
} catch (JSONException e) {
e.printStackTrace();
onLoadNewsListListener.onFailure("网络连接失败",e);
Log.i("miao", "异常: 解析异常 异常信息在上面");
}
}
};
httpUtil.postMap(url, map, mHttpCallBack);
}
//解析数据的方法
private void processData(String data, OnLoadNewsListListener onLoadNewsListListener) {
Gson gson = new Gson();
mBackvehicleBean = gson.fromJson(data, BackvehicleBean.class);
/**从服务器上获取到的新的数据*/
mList = mBackvehicleBean.getList();
//将新的数据放到接口里
onLoadNewListListener.onSuccess(mList);
}
}
操作很简单 就不解释了 自己看注释吧! 网络请求封装也给大家吧 在网上找的一个OKhttp封装 是哪位大神的不记得了
/**
* 网络请求工具类
* post请求 json数据为body
* post请求 map是body
* get 请求
* Created by root on 2016/11/11.
*
* @author mcl
*/
public class HttpUtil {
private OkHttpClient client;
//超时时间
public static final int TIMEOUT = 1000 * 50;
//json请求
public static final MediaType JSON = MediaType
.parse("application/json; charset=utf-8");
public HttpUtil() {
this.init();
}
private void init() {
client = new OkHttpClient();
//设置超时
client.newBuilder().connectTimeout(TIMEOUT, TimeUnit.SECONDS).
writeTimeout(TIMEOUT, TimeUnit.SECONDS).readTimeout(TIMEOUT, TimeUnit.SECONDS)
.build();
}
/**
* post请求 json数据为body
*/
public void postJson(String url, String json, final HttpCallBack callBack) {
RequestBody body = RequestBody.create(JSON, json);
final Request request = new Request.Builder().url(url).post(body).build();
OnStart(callBack);
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(okhttp3.Call call, IOException e) {
OnError(callBack, e.getMessage());
}
@Override
public void onResponse(okhttp3.Call call, Response response) throws IOException {
if (response.isSuccessful()) {
onSuccess(callBack, response.body().string());
} else {
OnError(callBack, response.message());
}
}
});
}
/**
* post请求 map是body
*
* @param url
* @param map
* @param callBack
*/
public void postMap(String url, Map<String, String> map, final HttpCallBack callBack) {
FormBody.Builder builder = new FormBody.Builder();
//遍历map
if (map != null) {
for (Map.Entry<String, String> entry : map.entrySet()) {
builder.add(entry.getKey(), entry.getValue().toString());
}
}
RequestBody body = builder.build();
Request request = new Request.Builder().url(url).post(body).build();
OnStart(callBack);
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(okhttp3.Call call, IOException e) {
OnError(callBack, e.getMessage());
}
@Override
public void onResponse(okhttp3.Call call, Response response) throws IOException {
if (response.isSuccessful()) {
onSuccess(callBack, response.body().string());
} else {
OnError(callBack, response.message());
}
}
});
}
/**
* get 请求
*
* @param url
* @param callBack
*/
public void getJson(String url, final HttpCallBack callBack) {
Request request = new Request.Builder().url(url).build();
OnStart(callBack);
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(okhttp3.Call call, IOException e) {
OnError(callBack, e.getMessage());
}
@Override
public void onResponse(okhttp3.Call call, Response response) throws IOException {
if (response.isSuccessful()) {
//onsuccess(final HttpCallBack callBack, final String data);
onSuccess(callBack, response.body().string());
} else {
OnError(callBack, response.message());
}
}
});
}
public void OnStart(HttpCallBack callBack) {
if (callBack != null) {
callBack.onstart();
}
}
public void onSuccess(final HttpCallBack callBack, final String data) {
if (callBack != null) {
UIUtils.runInMainThread(new Runnable() {
@Override
public void run() {
callBack.onSusscess(data);
}
});
}
}
public void OnError(final HttpCallBack callBack, final String msg) {
if (callBack != null) {
UIUtils.runInMainThread(new Runnable() {
@Override
public void run() {
callBack.onError(msg);
}
});
}
}
/**
* 请求开始 回调
* 请求成功回调
* 请求失败回调
*/
public static abstract class HttpCallBack {
//开始
public void onstart() {
}
//成功回调
public abstract void onSusscess(String data);
//失败
public void onError(String meg) {
}
;
}
}
接下来 presenter层实现 由于presenter层是连接view和model层的中间枢纽(就这么叫吧) 因此必然会持有这两层的引用对象 在构造方法中 创建和获取 注意实现的接口
import com.city.wuliubang.backtrip.base.BasePresenter;
import com.city.wuliubang.backtrip.contract.BackTripInfoContract;
import com.city.wuliubang.backtrip.listener.OnLoadNewsListListener;
import com.city.wuliubang.backtrip.model.BackTripInfoModelImpl;
import com.city.wuliubang.backtrip.utils.http.BranchPath;
import com.city.wuliubang.backtrip.utils.http.Constant;
import java.util.List;
/**
* 处理数据
* Created by root on 2016/11/23
*/
public class BackTripInfoPresenterImpl extends BasePresenter implements BackTripInfoContract.Presenter,OnLoadNewsListListener {
private BackTripInfoContract.View returnView;
private final BackTripInfoModelImpl mBackTripInfoModelImpl;
private String TAG="miao";
//构造方法获取 view层和model层的对象
public BackTripInfoPresenterImpl(BackTripInfoContract.View view) {
returnView=view;
mBackTripInfoModelImpl = new BackTripInfoModelImpl();
}
@Override
public void loadData(int page) {
map.clear();
map.put("page",String.valueOf(page));
mBackTripInfoModelImpl.getDataFromNet(Constant.ROOTPATH + BranchPath.SELECTBACKINFO,map,this);
returnView.showProgress();
}
@Override
public void onSuccess(List list) {
returnView.addData(list);
returnView.hideProgress();
}
@Override
public void onFailure(String msg, Exception e) {
returnView.showLoadFailMsg();
returnView.hideProgress();
}
@Override
public void onNetWorkException(String msg) {
returnView.showNetWorkException();
}
}
最后 View层Activity view层要持有presenter层的对象 基类baseactivity中的 creatPresenter方法
/**
* 回程信息列表界面 view层
*/
public class 列表界面extends BaseActivity implements BackTripInfoContract.View, SwipeRefreshLayout.OnRefreshListener, BaseQuickAdapter.RequestLoadMoreListener {
private ArrayList<BackvehicleBean.ListBean> mList = new ArrayList<>();
private SwipeRefreshLayout mSwipeRefreshWidget;
private RecyclerView mRecyclerView;
private LinearLayoutManager mLayoutManager;
private QuickAdapter mAdapter;
private int pageIndex = 1;
private BackTripInfoPresenterImpl mBackTripPresenter;
private boolean isRefresh = true;
private SwipeRefreshLayout.OnRefreshListener onRefreshListener;
@Override
protected boolean isShowToolBar() {
return true;
}
@Override
protected void initSetting(Toolbar mToolbar, TextView mTvTitle) {
//初始化标题栏
mToolbar.setTitle("");
mTvTitle.setText("返程车列表");
mToolbar.setNavigationIcon(R.mipmap.back_arrows);
//点击返回 关闭当前界面 回到前一个界面
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(BackTripApplyActivity.class);
}
});
}
@Override
protected void initData() {
//加载更多监听
mAdapter.setOnLoadMoreListener(this);
//下拉刷新监听
mSwipeRefreshWidget.setOnRefreshListener(this);
}
@Override
protected void initViews(Bundle convertView) {
setContentView(R.layout.activity_base_recycler_list);
mSwipeRefreshWidget = (SwipeRefreshLayout) findViewById(R.id.srf_refresh);
mSwipeRefreshWidget.setOnRefreshListener(this);
mSwipeRefreshWidget.setColorSchemeResources(R.color.google_blue,
R.color.google_green, R.color.google_red,
R.color.google_yellow);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
/**RecyclerView的尺寸在每次改变时,比如你加任何些东西。
setHasFixedSize 的作用就是确保尺寸是通过用户输入从而确保RecyclerView的尺寸是一个常数。
RecyclerView 的Item宽或者高不会变。每一个Item添加或者删除都不会变。
如果你没有设置setHasFixedSized没有设置的代价将会是非常昂贵的。
因为RecyclerView会需要而外计算每个item的size,
源码:
void triggerUpdateProcessor() {
if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
requestLayout();为重绘布局 消费高
*/
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
mAdapter = new QuickAdapter(mList);
// mAdapter.openLoadAnimation(new BaseAnimation() {
// @Override
// public Animator[] getAnimators(View view) {
//
// return new Animator[]{
// ObjectAnimator.ofFloat(view, "scaleY", 0.9f, 1.2f, 1),
// ObjectAnimator.ofFloat(view, "scaleX", 0.9f, 1.2f, 1),
// ObjectAnimator.ofFloat(view,"")
// };
// }
// });
mAdapter.openLoadAnimation(BaseQuickAdapter.SCALEIN);
// 注意 此处必须使用此方法 防止布局的match_parent被强改成wrap_content
mAdapter.setEmptyView(infalteView(R.layout.layout_empty_view));
//设置适配器
mRecyclerView.setAdapter(mAdapter);
//刷新数据 初始化界面数据
}
@Override
protected void initVariables() {
}
@Override
protected BasePresenter createPresenter() {
mBackTripPresenter = new BackTripInfoPresenterImpl(this);
return mBackTripPresenter;
}
@Override
public void showProgress() {
}
@Override
public void addData(List<BackvehicleBean.ListBean> newsList) {
//服务器每次返回15条 小于15条添加脚布局 证明已经没有更多数据
//判断是否为刷新操作
if (isRefresh) {
mAdapter.removeAllFooterView();
mAdapter.setNewData(newsList);
} else {
mAdapter.addData(newsList);
if (newsList.size() == 0 || newsList.size() < 15) {
mAdapter.addFooterView(View.inflate(getApplicationContext(), R.layout.end_view, null));
}
//重置为刷新操作
isRefresh = true;
}
}
@Override
public void hideProgress() {
mSwipeRefreshWidget.setRefreshing(false);
}
@Override
public void showLoadFailMsg() {
mAdapter.showLoadMoreFailedView();
}
@Override
public void showNetWorkException() {
mSwipeRefreshWidget.setRefreshing(false);
UIUtils.showToast("网络连接失败");
}
@Override
public void onRefresh() {
//刷新只加载第一页数据
pageIndex = 1;
mBackTripPresenter.loadData(pageIndex);
}
@Override
public void onLoadMoreRequested() {
//加载操作
isRefresh = false;
//更新索引
pageIndex += 1;
//调用presenter层获取数据 加载第一页的数据
mBackTripPresenter.loadData(pageIndex);
}
/**
* @param layoutId 布局id
* @return view对象
*/
private View infalteView(int layoutId) {
LayoutInflater inflater = LayoutInflater.from(this);
//防止布局的match_parent被强改成wrap_content
return inflater.inflate(layoutId, (ViewGroup) mRecyclerView.getParent(), false);//参3prarent跟view没有添加关系.
}
@Override
public void onResume() {
super.onResume();
onRefresh();
}
}
关于MVP设计模式的就差不多了
接下来简单说一下V2.4.4版本的BaseRecyclerViewAdapterHelper的刷新和加载以及QuickAdapter
//服务器每次返回15条 小于15条添加脚布局 证明已经没有更多数据
//判断是否为刷新操作
if (isRefresh) {
mAdapter.removeAllFooterView();
mAdapter.setNewData(newsList);
} else {
mAdapter.addData(newsList);
if (newsList.size() == 0 || newsList.size() < 15) {
mAdapter.addFooterView(View.inflate(getApplicationContext(), R.layout.end_view, null));
}
//重置为刷新操作
isRefresh = true;
}
注意setNewData方法和addData方法 是啥我就不说了 一眼就能看出来 不要纠结newList.size()的判断条件
好,到这里就基本完成了 另外推荐大家一个 代码混淆的studio插件AndroidProguard(一个非常牛逼的大神写的) 自己百度去吧 至于效果 阿哈哈哈哈哈哈哈!谁用谁知道!
本人刚出道菜鸟一枚 有什么建议欢迎交流 QQ1773345343