您的位置:首页 > 移动开发 > Android开发

MVP模式的RecyclerView案例

2016-10-11 00:24 531 查看
第一篇博客讲一个用MVP模式写RecyclerView的案例,通过这个例子了解一下MVP模式。

一、什么是MVP模式

MVP是模型(Model)、视图(View)、驱动器(Presenter)的缩写,分别代表项目中3个不同的模块。

模型(Model):负责处理数据的加载或者存储,比如从网络或本地数据库获取数据等;

视图(View):负责界面数据的展示,与用户进行交互,在presenter的控制下修改UI;

驱动器(Presenter):协调中心,是模型与视图之间的桥梁,将模型与视图分离开来。

MVP模式其实就是一套适合Android开发的成熟规范,MVP模式由MVC演变而来,它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。但是他们的内部联系不尽相同,我们先从两张图区分MVC和MVP





我们可以看到MVC是三端一环控制一环形成一个循环而MVP以Presenter为驱动,双向调动View和Model,其中在View层定义Presenter,调用Presenter的方法实现逻辑功能,在Presenter定义View和Model,Presenter则可调用Model获取数据后再调用View层的方法更新View层,而Model层中与Presenter的联系以回调实现。

二、优势

大大减少了Model与View层之间的耦合度。一方面可以使得View层和Model层单独开发与测试,互不依赖。View层只要写出所有界面变化的方法而不用考虑什么时候调用,而Presenter则只写出所有的逻辑代码,当需要数据时向Model层调用,需要更新界面时则调用View层方法,完全不需考虑界面的更新的实现和数据获取的代码实现。另一方面Presenter和Model层都可以封装复用,可以极大的减少代码量。

三、案例演示

先看效果图



接下来写接口:

三层统一用一个Contact接口,然后里面在写三个内部接口(View、Model、Presenter),到时候相应的每个实现类实现不同的内部接口即可,把每一层的方法都写上

public interface MyContact {
interface View {
//toast显示信息
void showToast(String string);
//设置recyclerView
void setAdapter(InvitationBaseBean invitationBaseBean);
//刷新adater
void notifyAdapter();
//停止刷新
void stopRefresh();
}
interface InvitationlModel{
//http请求获取数据
void getData(StringCallback stringCallback);
}
interface InvitationPresenter {
//连接、断开view
void attachView(@NonNull MainActivity View);
void detachView();
//获取数据
void getData();
//加载更多
void pullLoadMore();

}
}


1:view层:

View层主要是一个RecyclerView,SuperSwipeRefesh,然后每个Item里面也有一个用户头像的RecyclerView

MainActivity.Layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/activity_bg"
tools:context=".MainActivity">
<LinearLayout
android:id="@+id/invitation_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/custom_toolbar"/>
<net.mobctrl.views.SuperSwipeRefreshLayout
android:id="@+id/invitation_refresh"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.RecyclerView
android:id="@+id/activity_my_focus_recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</net.mobctrl.views.SuperSwipeRefreshLayout>
</LinearLayout>

<android.support.design.widget.FloatingActionButton
android:id="@+id/invitation_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:src="@drawable/launch" />

</android.support.design.widget.CoordinatorLayout>


Item.Layout

item里面在嵌套一个横向RecyclerView,显示参与用户头像

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
xmlns:card_view="http://schemas.android.com/tools"
android:id="@+id/item_invitation_root"
android:orientation="vertical"
android:layout_height="wrap_content"
android:paddingLeft="15dp"
android:background="@color/white"
android:paddingRight="15dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:layout_marginBottom="15dp">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/item_invitation_originator_imagVi"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/user_img" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginLeft="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/item_invitation_originator_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="aaa"
android:layout_marginTop="10dp"
android:textSize="15sp"
android:textColor="@color/invitation_item_originator_name"/>
<TextView
android:id="@+id/item_invitation_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发起了狼人杀"
android:layout_marginTop="10dp"
android:layout_marginLeft="10dp"
android:textSize="15sp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right">
<TextView
android:id="@+id/item_invitation_publish_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="19:30"
android:layout_alignParentRight="true"
android:layout_marginTop="10dp"
android:textSize="15sp"
android:textColor="@color/time_def"/>
<ImageView
android:id="@+id/item_invitation_expend"
android:layout_gravity="bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_expand_more"
android:visibility="gone"
android:layout_marginLeft="5dp" />
</LinearLayout>

</LinearLayout>
<TextView
android:id="@+id/item_invitation_contents"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:textSize="15sp"/>
</LinearLayout>
</LinearLayout>
<android.support.v7.widget.CardView

android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/CardView"
android:layout_marginTop="9dp"
card_view:cardCornerRadius="@dimen/card_corner_radius"
card_view:cardElevation="@dimen/card_elevation">
<LinearLayout
android:id="@+id/item_invitation_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white">
<ImageView
android:id="@+id/item_invitation_icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/langrensha"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="6dp"
android:background="@color/invitation_detail_usercard_bg"
android:orientation="vertical">
<TextView
android:id="@+id/item_invitation_invitation_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="时间:2016-12-25 19:30"
android:layout_weight="1"
android:textSize="11sp"/>
<TextView
android:id="@+id/item_invitation_place"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="地点:九栋304"
android:layout_weight="1"
android:textSize="11sp"/>
<TextView
android:id="@+id/item_invitation_sex_require"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="性别要求:无"
android:layout_weight="1"
android:textSize="11sp"/>
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/yibaoming"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="15sp"
android:layout_gravity="center_vertical"
android:text="已报名:"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/item_invitation_userimglist"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_toRightOf="@+id/yibaoming"
android:layout_height="wrap_content"/>

<TextView
android:id="@+id/item_invitation_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_gravity="center_vertical"
android:layout_toLeftOf="@+id/baoming_btn"
android:text="6/12"/>
<ImageView
android:id="@+id/item_invitation_join_btn"
android:layout_alignParentRight="true"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:src="@drawable/join_unselected"/>
</LinearLayout>

</LinearLayout>


MainActivity.Class

activity则实现View层的所有方法,并在启动时调用Presenter的getData()实现加载数据的逻辑。

setAdapter()方法里面实现adapter的三个回调接口

onItemClick(): item点击事件

onJoinBtnClick(): item里面的参加手型按钮点击事件

setMemberAdapter(): 加载item里的横向头像RecyclerView

public class MainActivity extends AppCompatActivity implements MyContact.View {
@BindView(R.id.baseToolBar)
BaseToolBar baseToolBar;
@BindView(R.id.activity_main)
RelativeLayout activityMain;
@BindView(R.id.activity_my_focus_recyclerview)
RecyclerView mRecyclerView;
@BindView(R.id.invitation_refresh)
net.mobctrl.views.SuperSwipeRefreshLayout invitationRefresh;
@BindView(R.id.invitation_root)
LinearLayout invitationRoot;
@BindView(R.id.invitation_fab)
FloatingActionButton invitationFab;

private MyPresenter mMyPresenter;
private MyAdapter mMyAdapter;
private UserImgAdapter mUserImgAdapter;

private SuperSwipeRefreshLayout refreshLayout;//下拉刷新组件

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.invitation_activity);
ButterKnife.bind(this);
mMyPresenter = new MyPresenter();
mMyPresenter.attachView(this);
//获取数据
mMyPresenter.getData();
//下拉刷新,上拉加载更多
initSuperSwipeRefresh();
}

@Override
public void showToast(String string) {
Toast.makeText(this,string,Toast.LENGTH_SHORT).show();
}

@Override
public void setAdapter(final InvitationBaseBean invitationBaseBean) {
mMyAdapter = new MyAdapter(this,invitationBaseBean);
mMyAdapter.setItemEvent(new MyAdapter.ItemEvent() {
@Override
public void onItemCLick(int position) {
showToast("itemClick");
}

@Override
public void setMemberAdater(int position,RecyclerView recyclerView) {
mUserImgAdapter = new UserImgAdapter(invitationBaseBean.getData().get(position).getMembers(),getApplicationContext());
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getApplicationContext());
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setAdapter(mUserImgAdapter);
}

@Override
public void onJoinBtnClick(int position) {
showToast("join");
}
});
mRecyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
mRecyclerView.setAdapter(mMyAdapter);
}

@Override
public void notifyAdapter() {
mMyAdapter.notifyDataSetChanged();
}

@Override
public void stopRefresh() {
invitationRefresh.setRefreshing(false);
invitationRefresh.setLoadMore(false);
}

/**
* 下拉刷新,上拉加载
*/
private void initSuperSwipeRefresh() {
invitationRefresh.setOnPullRefreshListener(new SuperSwipeRefreshLayout.OnPullRefreshListener() {
@Override
public void onRefresh() {
mMyPresenter.getData();
invitationRefresh.setRefreshing(false);
}

@Override
public void onPullDistance(int distance) {

}

@Override
public void onPullEnable(boolean enable) {

}
});
invitationRefresh.setOnPushLoadMoreListener(new SuperSwipeRefreshLayout.OnPushLoadMoreListener() {
@Override
public void onLoadMore() {
mMyPresenter.pullLoadMore();
}

@Override
public void onPushDistance(int distance) {

}

@Override
public void onPushEnable(boolean enable) {

}
});
}
@OnClick(R.id.invitation_fab)
public void onClick() {
showToast("发起活动");
}
}


2:Presenter:

我们这个案例presenter实现的功能有获取数据getData()和加载更多LoadMore(),具体实现为调用Model层通过okhttp获取数据,在通过实现回调方法完成获取数据后的处理,这里我们获取json数据后解析Json并调用View层的setAdapter方法实现界面更新。如果获取数据出错则调用View层的showToast()显示出错信息。

在这里我在啰嗦几句,关于RecyclerView的Adapter应该放在View层还是Presenter层,不同人有不同的见解,其实两种方式都可以,我自己实践了两种方法后是比较偏向于放在View层,因为放在Presenter的话Adapter的回调方法需要界面刷新时又得再去调用View层,过程繁琐了一点。

public class MyPresenter implements MyContact.InvitationPresenter {
private MyContact.View mActivity;
private MyContact.InvitationlModel mInvitationModel;
private InvitationBaseBean invitationBaseBean;

@Override
public void attachView(@NonNull MainActivity View) {
mActivity = View;
mInvitationModel = new InvitationModel();
}

@Override
public void detachView() {
mActivity = null;
}

@Override
public void getData() {
mInvitationModel.getData(new StringCallback() {
@Override
public void onError(Call call, Exception e, int id) {
e.printStackTrace();
mActivity.showToast("加载失败,请检查网络");
}

@Override
public void onResponse(String response, int id) {
invitationBaseBean = GsonUtil.toString(response,InvitationBaseBean.class);
if(invitationBaseBean.getCode().equals("200")) {
System.out.println("get到"+invitationBaseBean.getData().get(0).getOriginatorNickname());

// TODO 临时数据,增加item长度
for(int i = 0;i<2;i++)
invitationBaseBean.getData().addAll(invitationBaseBean.getData());

//加载数据成功,设置recyclerView的adater
mActivity.setAdapter(invitationBaseBean);
}else {
//加载数据失败,显示错误信息
mActivity.showToast(invitationBaseBean.getMsg());
}
}
});
}

@Override
public void onPositive(int position) {

}

/**
* 加载更多数据
*/
@Override
public void pullLoadMore() {
mInvitationModel.getData(new StringCallback() {
@Override
public void onAfter(int id) {
super.onAfter(id);
mActivity.stopRefresh();
}
@Override
public void onError(Call call, Exception e, int id) {

}

@Override
public void onResponse(String response, int id) {
InvitationBaseBean newData = GsonUtil.toString(response,InvitationBaseBean.class);
if(newData.getCode().equals("200")){
invitationBaseBean.getData().addAll(newData.getData());
mActivity.notifyAdapter();
}else {
mActivity.showToast(newData.getMsg());
}
}
});
}

}


三:Model层:

model层通过网络获取数据后通过stringCallback与Presenter层联系,在Presenter实现回调方法

public class InvitationModel implements MyContact.InvitationlModel {
private InvitationBaseBean mData;
@Override
public void getData(StringCallback stringCallback) {
OkHttpUtils.post()
.url("http://rap.taobao.org/mockjs/7569/invitation/getInvitations")
.build()
.execute(stringCallback);

}
}


四:adapter:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private InvitationBaseBean mData;
private Context context;
private ItemEvent mItemEvent;

public MyAdapter(Context context ,InvitationBaseBean mInvitationBaseBean){
this.mData = mInvitationBaseBean;
System.out.println("mdata"+mData.getData().get(0).getMembers().get(0).getHeadUrl());
this.context = context;
}

public void setItemEvent(ItemEvent itemEvent){
this.mItemEvent = itemEvent;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
MyViewHolder holder = new MyViewHolder(LayoutInflater.from(
parent.getContext()).inflate(R.layout.invitation_item, parent,
false));
return holder;
}

@Override
public void onBindViewHolder(MyViewHolder holder, final int position) {
String sex = getSexrequire(mData.getData().get(position).getSexRequire());
boolean join = mData.getData().get(position).isJoin();
if(join) {
holder.itemInvitationJoinBtn.setImageResource(R.drawable.join_selected);
}
else {
holder.itemInvitationJoinBtn.setImageResource(R.drawable.join_unselected);
}
holder.itemInvitationOriginatorName.setText(mData.getData().get(position).getOriginatorNickname());
holder.itemInvitationTitle.setText(mData.getData().get(position).getTitle());
String contents = StringUtil.cutContents(mData.getData().get(position).getContent(),57);
holder.itemInvitationContents.setText(contents);
holder.itemInvitationInvitationTime.setText("活动时间:"+mData.getData().get(position).getInvitationTime());
holder.itemInvitationPublishTime.setText(TimeUtil.getTime(mData.getData().get(position).getPublishTime()));
holder.itemInvitationPlace.setText("活动地点:"+mData.getData().get(position).getPlace());
holder.itemInvitationSexRequire.setText("性别要求:"+sex);
holder.itemInvitationNumber.setText(mData.getData().get(position).getCurrentNumber()+"/"+mData.getData().get(position).getTotalNumber());
holder.itemInvitationJoinBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mItemEvent.onJoinBtnClick(position);
}
});
holder.itemInvitationRoot.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mItemEvent.onItemCLick(position);
}
});
Picasso.with(context)
.load(mData.getData().get(position).getOriginatorHeadUrl())
.placeholder(R.drawable.user0)
.into(holder.itemInvitationOriginatorImagVi);
Picasso.with(context)
.load(mData.getData().get(position).getIconUrl())
.placeholder(R.drawable.langrensha)
.into(holder.itemInvitationIcon);
//调用加载头像横向recyclerView接口     mItemEvent.setMemberAdater(position,holder.itemInvitationUserimglist);
}

@Override
public int getItemCount() {
return mData.getData().size();
}

//0、1转换男女
private String getSexrequire(String sexRequire) {
String result = "不限";
switch (sexRequire){
case "0":
result = "男生";
break;
case "1":
result = "女生";
break;
case "2":
result = "不限";
break;
}
return result;
}

//回调接口
public interface ItemEvent{
void onItemCLick(int position);
void setMemberAdater(int position,RecyclerView recyclerView);
void onJoinBtnClick(int position);

}

public class MyViewHolder extends RecyclerView.ViewHolder {
...
}
}


第一次写博客,仅作为记录交流所用,写得不好多多包涵,欢迎各位的留言。

项目地址:https://github.com/Tinlok/RecyclerViewOnMvp
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  mvp Android recyclerVi