MVP模式的RecyclerView案例

第一篇博客讲一个用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

我们可以看到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();
    }

    //01转换男女
    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

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值