MVP模式的RecyclerView案例
2016-10-11 00:24
531 查看
第一篇博客讲一个用MVP模式写RecyclerView的案例,通过这个例子了解一下MVP模式。
模型(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的联系以回调实现。
接下来写接口:
三层统一用一个Contact接口,然后里面在写三个内部接口(View、Model、Presenter),到时候相应的每个实现类实现不同的内部接口即可,把每一层的方法都写上
1:view层:
View层主要是一个RecyclerView,SuperSwipeRefesh,然后每个Item里面也有一个用户头像的RecyclerView
MainActivity.Layout
Item.Layout
item里面在嵌套一个横向RecyclerView,显示参与用户头像
MainActivity.Class
activity则实现View层的所有方法,并在启动时调用Presenter的getData()实现加载数据的逻辑。
setAdapter()方法里面实现adapter的三个回调接口
onItemClick(): item点击事件
onJoinBtnClick(): item里面的参加手型按钮点击事件
setMemberAdapter(): 加载item里的横向头像RecyclerView
2:Presenter:
我们这个案例presenter实现的功能有获取数据getData()和加载更多LoadMore(),具体实现为调用Model层通过okhttp获取数据,在通过实现回调方法完成获取数据后的处理,这里我们获取json数据后解析Json并调用View层的setAdapter方法实现界面更新。如果获取数据出错则调用View层的showToast()显示出错信息。
在这里我在啰嗦几句,关于RecyclerView的Adapter应该放在View层还是Presenter层,不同人有不同的见解,其实两种方式都可以,我自己实践了两种方法后是比较偏向于放在View层,因为放在Presenter的话Adapter的回调方法需要界面刷新时又得再去调用View层,过程繁琐了一点。
三:Model层:
model层通过网络获取数据后通过stringCallback与Presenter层联系,在Presenter实现回调方法
四:adapter:
第一次写博客,仅作为记录交流所用,写得不好多多包涵,欢迎各位的留言。
项目地址:https://github.com/Tinlok/RecyclerViewOnMvp
一、什么是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模式下的RecyclerView案例
- MVP模式 + RecyclerView瀑布流 + 上拉加载、下拉刷新
- 单列模式,RecyclerView适配器封装,BroadcastReceiver介绍,Mvp+Retrofit+rxjava+okhttp框架的梳理总结
- MVP模式的OKhttp请求网络数据,xrecyclerview上拉刷新,下拉加载
- 展示数据使用:recyclerview,retrofit,greendao,butterknife,eventbus,fresco。实现效果图列表。MVP模式。
- 关于MVP设计模式 和 BaseRecyclerViewAdapterHelperV2.4.4 Android
- Retrofit,RecyclerViewMVP模式
- MVP模式在Android中的应用(附UML高清大图,使用RecyclerView举例)
- android 用mvp模式来架构自己的app+打造Recyclerview万能适配器
- MVP模式在Android中的应用(附UML高清大图,使用RecyclerView举例)
- MODEL-View-Presenter(MVP)模式在FLEX下的开发实例
- RecyclerView实例-实现可下拉刷新上拉加载更多并可切换线性流和瀑布流模式(1)
- .NET平台上的Model-View-Presenter(MVP)模式实践
- 设计模式:Model View Presenter(MVP)
- android中MVP模式的小案例(一)
- 【iOS开发-54】案例学习:通过UIScrollView的缩放图片功能练习代理模式的具体实现
- ItemTouchHelper类及RecyclerView的item滑动删除和拖拽案例
- 探索MVP(Model-View-Presenter)设计模式在SharePoint平台下的实现
- 探索MVP(Model-View-Presenter)设计模式在SharePoint平台下的实现
- Dagger2, Retrofit和MVP设计模式案例分析