个人博客:CODE FRAMER BIGZ

MVP系列文章配套DEMO

Android 当中的 MVP 模式(一)基本概念


Android 当中的 MVP 模式(二)封装


Android 当中的 MVP 模式(三)基于分页列表的封装


Android 当中的 MVP 模式(四)插曲-封装 OkHttp


Android 当中的 MVP 模式(五)封装之后的 OkHttp 工具在 Model 层的使用


Android 当中的 MVP 模式(六)View 层 Activity 的基类— BaseMvpActivity 的封装


Android 当中的 MVP 模式(七)终篇—关于对 MVP 模式中代码臃肿问题的思考

摘要:在上一篇中对MVP模式进行了封装,然后通过封装之后的类,实现了一个网络请求,但是请求到网络数据之后,就直接展示到了 View 层,并没有其他的操作,然而我们在开发过程中, 经常会用到分页加载,一般在滑动控件向上滚动,加载更多事件触发是调用,并且这个过程设计到两个参数,一个是 PageIndex :页码;一个是 PageSize 一页数据的大小, 分页加载就是通过在某一具体事件触发时,调用修改这两个或者一个参数,重新请求网络,从而拿到下一页的数据,这边文章还是基于MVP模式,对分页数据的请求进行封装。

presenter 层作为 MVP 模式的桥梁, 那就先从这一层开始说起吧。

Presenter 层的封装

上一篇中对 Presenter 层的公共方法进行了抽取并且封装成了一个接口 IBasePresenter ,那么现在我们需要实现分页加载还有刷新的功能,那么在 IBasePresenter 接口的基础之上,在对其封装一个接口 IBasePeginationPresenter

/** * Created by fanyuzeng on 2017/10/23. * Function:在IBasePresenter的基础上扩展的接口,适用于分页加载的情况 */public interface IBasePaginationPresenter extends IBasePresenter {/** * 刷新数据的接口 * * @param param 访问服务器的参数 * @created at 2017/10/23 20:07 */void refresh(Param param);/** * 加载更多的接口 * * @created at 2017/10/23 20:07 */void loadingNext();/** * 用于判断服务器端是否还有更多的数据 * @return true -还有更多数据 - false 没有更多的数据 */boolean hasMoreData();}

也是一个泛型的接口,增加的三个方法 :

  1. refresh(Param param)View 层调用,用于通知 Model 层刷新数据
  2. loadingNext()View 层调用,用于通知 Model 层加载下一页数据
  3. hasMoreData()Model 层请求网络数据前调用做判断,是否还有下一页数据

有了针对分页刷新的接口之后,还需要有一个实现它的基类:

/** * @author:ZengFanyu  * @date:2017/10/20  */public abstract class BasePaginationPresenter implements IBasePaginationPresenter {    private static final String TAG = "BasePaginationPresenter";    private IBaseModel mBaseModel;    private IBaseView mBaseListView;    private Param mParam;    private Class mClazz;    private Handler mHandler = new Handler(Looper.getMainLooper());    private boolean mHasMoreData=true;    /**     * 子类中调用,用于传递服务器返回的,处理好的结果     *     * @param data View层需要的数据类型     * @created at 2017/10/23 20:10     */    public abstract void serverResponse(Data data);     /**      * 子类中调用,用于确认服务器端是否还有数据      *      * @return true-还有数据 false-没有数据      */    public abstract boolean serverHaveMoreData();           public BasePaginationPresenter(IBaseView baseListView, Class Clazz) {        this.mBaseListView = baseListView;        mClazz = Clazz;        mBaseModel = new SohuAlbumModel(this);    }    @Override    public void refresh(Param param) {        requestServer(param);    }    @Override    public void loadingNext() {        if (mParam != null) {            int pageIndex = mParam.getPageIndex();            mParam.setPageIndex(pageIndex + 1);            requestServer(mParam);        }    }    @Override    public void requestServer(@Nullable Param param) {        mBaseListView.showProgress(true);        mParam = param;        Log.d(TAG, ">> requestServer >> ");        getModel().sendRequestToServer(param);    }    @Override    public void accessSuccess(String responseJson) {        mBaseListView.showProgress(false);        Gson gson = new Gson();        serverResponse(gson.fromJson(responseJson, mClazz));        mBaseListView.showSuccess(true);    }    @Override    public void cancelRequest() {        mBaseModel.cancelRequest();    }    @Override    public void okHttpError(final int errorCode, final String errorDesc, final String errorUrl) {        mHandler.post(new Runnable() {            @Override            public void run() {                mBaseListView.showOkHttpError(errorCode, errorDesc, errorUrl);                mBaseListView.showProgress(false);                mBaseListView.showSuccess(false);            }        });    }    @Override    public IBaseModel getModel() {        return mBaseModel;    }    @Override    public HashMap getParams() {        return null;    }    @Override    public boolean hasMoreData() {        return ServerHaveMoreData();    }}

- 在类申明时,可以看到 Param extends BasePeginationParam ,这里的 BasePeginationParam主要是封装了摘要中提到的 PageIndexPageSize 两个参数,以及他们的 Getter Seeter 方法。
- 重点看 IBasePeginationPresenter 中新增加的三个方法,refresh(Param param) 会重新调用一次 requestServer(Param param)此方法在上一篇也提过了,就是通知 Model 层获取数据);
- loadingNext() ,加载下一页数据的方法,就是将参数中的 PageIndex + 1 之后,重新调用 requestServer(Param param) 方法。此处只改变了页码,如果需要改变请求数据的条数,也是相应的在 loadingNext() 中修改 PageSize 的值。
- hasMoreData() ,这里返回抽象方法 serverhaveMoreData() ,这个方法是在子类中实现的,子类解析了数据之后,判断服务器是否还有数据返回。

然后有需要实现分页功能的 Presenter 就可以直接继承 BasePaginationPresenter

Model

由于 Model 层的职责比较单一,就是向数据源请求数据,并且返回给 Presenter,所以此处不需要额外封装接口或者是基类,只需要重新实现上一篇中提到的 IBaseModel 接口即可。

View

此处和请求一次数据相比较, View 层就是需要在两个事件触发的时候,重新设置参数通知 Presenter 去请求数据,然后再展示出来。这两个事件分别是:上拉到底时加载更多、下拉时刷新数据(当然可以别的)。

针对上一小节中封装类的具体实现

View 层的具体实现

主要是展示电视剧的主要信息,那么需要提供一个接口方法,给 Presenter 层调用,展示处理好的 JavaBean
/**
* 展示搜狐电视剧频道具体信息的接口
*
* @author:ZengFanyu
*/
public interface ISohuSerials extends IBaseView {

    /**     * 展示搜狐视频API电视剧主要信息的方法     *     * @param videoList 处理好的VideoInfo集合     */    void showAlbumMainInfo(List videoList);}

此处的 VideoInfo 是一个JavaBean,对应的就是电视剧信息的实体类。

public class VideoInfo {   @SerializedName("main_actor")   private String mMainActor;   @SerializedName("total_video_count")   private int mTotalVideoCount;   @SerializedName("album_name")   private String mAlbumName;   @SerializedName("director")   private String mDirector;   @SerializedName("publish_time")   private String mPublishTime;    //Getter and setter methods}

之前映射数据需要保证字段名和 Json 数据的字段名一致,其实本来把这个类的字段名改得一致就行啦,但是服务器端返回的数据字段,很多都是以“_”进行连接,而不是使用驼峰命名法则,这个时候 Gson@SerializedName 注解就派上用场了,注解中用服务器端返回值字段,成员变量仍然使用驼峰命名法。

但是上个周末安装了最近 Alibaba 10 月 14 日 推出的 Coding Guidelines 插件,发现代码中很多不规范的地方,并且人家规定了成员变量就必须要使用驼峰命名!所以我决定要按照这个插件的规范来写代码了,虽然现在进不了大厂,但是先熟悉大厂的代码规范也是好事,哈哈~ 咳咳,按照大厂的代码规范,成员变量的命名必须使用驼峰命名法!

这个插件是真心好用,比如对类名要 javadoc 注释 参数、返回值、异常说明、此方法做什么事情、实现什么功能(领域模型相关命名除外,比如:DO、BO、DAO),并且是全中文的!直接在 ASInspection Results 窗口中显示,这 IDE 内置功能啥时候讲过中文反馈结果的?

《阿里巴巴Java开发规约》插件全球首发!

广告时间结束,言归正传!

这个 Activity 实现了 ISohuSerials 接口,布局文件和上一篇一样,只是把 ListView 换成了自定义的 PullLoadRecyclerView 了,这个RecycyclerView 支持上拉加载更多和下拉刷新, 这里不展开说了。

/** * @author:ZengFanyu  */public class SohuAlbumInfoActivity extends AppCompatActivity implements ISohuSerials {    private static final String TAG = "SohuAlbumInfoActivity";    private PullLoadRecyclerView mRecyclerView;    private Context mContext;    private ProgressBar mProgressBar;    private TextView mTip;    private RelativeLayout mContainer;    private AlbumPresenter mAlbumPresenter;    private BasePaginationParam mParam= new BasePaginationParam(1, 10);    private VideoInfoAdapter mAdapter;    Handler mHandler = new Handler(Looper.getMainLooper());    private boolean mIsFromRefresh = false;    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_album_view);        mContext = this;        mAlbumPresenter = new AlbumPresenter(this, Album.class);        mContainer = (RelativeLayout) findViewById(R.id.id_success_content);        mTip = (TextView) findViewById(R.id.id_tip);        mProgressBar = (ProgressBar) findViewById(R.id.id_progress_bar);        mRecyclerView = (PullLoadRecyclerView) findViewById(R.id.id_recycler_view);        mRecyclerView.setLinearLayout();        mAdapter = new VideoInfoAdapter(mContext);        mAlbumPresenter.requestServer(mParam);        mRecyclerView.setAdapter(mAdapter);        mRecyclerView.setOnPullLoadMoreListener(new PullLoadRecyclerView.OnPullLoadMoreListener() {            @Override            public void onRefresh() {                mIsFromRefresh = true;                mParam.setPageIndex(1);                mAlbumPresenter.refresh(mParam); //通知Presenter层刷新数据                mRecyclerView.setRefreshCompleted();            }            @Override            public void onLoadMore() {                mAlbumPresenter.loadingNext();                mRecyclerView.setLoadMoreCompleted(); //通知Presenter层加载下一页数据            }        });    }    @Override    public void showAlbumMainInfo(List albumList) {        if (mIsFromRefresh) {            mAdapter.cleanData();            mIsFromRefresh = false;        }        if (albumList != null && albumList.size() > 0) {            for (VideoInfo videoInfo : albumList) {                mAdapter.addData(videoInfo);            }            mHandler.post(new Runnable() {                @Override                public void run() {                    mAdapter.notifyDataSetChanged();                }            });        }    }    @Override    public void showProgress(final boolean isShow) {        mHandler.post(new Runnable() {            @Override            public void run() {                if (isShow) {                    mProgressBar.setVisibility(View.VISIBLE);                } else {                    mProgressBar.setVisibility(View.GONE);                }            }        });    }    @Override    public void showOkHttpError(final int errorCode, final String errorDesc, final String errorUrl) {        mHandler.post(new Runnable() {            @Override            public void run() {                mTip.setText("http err:" + "errCode:" + errorCode + ",errDesc:" + errorDesc + ",errUrl:" + errorUrl);            }        });    }    @Override    public void showServerError(final int errorCode, final String errorDesc) {        mHandler.post(new Runnable() {            @Override            public void run() {                mTip.setText("server err:" + "errCode:" + errorCode + ",errDesc:" + errorDesc);            }        });    }    @Override    public void showSuccess(final boolean isSuccess) {        mHandler.post(new Runnable() {            @Override            public void run() {                if (isSuccess) {                    mContainer.setBackgroundResource(android.R.color.white);                    mTip.setText("Sohu Serials album");                } else {                    mContainer.setBackgroundResource(R.color.colorAccent);                }            }        });    }}

在上面代码中可以看到:

  • PullLoadRecycler.OnPullLoadMoreListenreonRefresh() 回调方法中,核心代码就是这一行 mAlbumPresenter.refresh(mParam); ,通知 Presenter 层去刷新数据, 至于 Presenter 层如何刷新。。 关我 View 层 X 事~
  • PullLoadRecycler.OnPullLoadMoreListenreonLoadMore() 回调方法中,也是直接调用 mAlbumPresenter.loadingNext()

下面说说 Presenter 层的代码

Presenter 层的具体实现

/** * @author:ZengFanyu * Function: */public class AlbumPresenter extends BasePaginationPresenter {    private ISohuSerials mBaseListView;    private Handler mHandler = new Handler(Looper.getMainLooper());    private int mTotalCount;    public AlbumPresenter(ISohuSerials baseListView, Class CLazz) {        super(baseListView, CLazz);        this.mBaseListView = baseListView;        getModel().setRequestMethod(Constants.HTTP_GET_METHOD);        getModel().setRequestUrl(Constants.SOHU_SERIALS_URL);    }    @Override    public void serverResponse(Album album) {        mBaseListView.showAlbumMainInfo(album.getData().getVideos());        mHandler.post(new Runnable() {            @Override            public void run() {                mBaseListView.showProgress(false);            }        });        mTotalCount = album.getData().getCount();    }    @Override    public boolean serverHaveMoreData() {        //此处pageIndex是从1开始的, 实际使用需要注意pageIndex的起始值        int pageSize = mParam.getPageSize();        int pageIndex = mParam.getPageIndex();        return (pageIndex * pageSize) <= mTotalCount;    }}
  • 首先是要继承之前编写的 BasePaginationPresenter类,泛型参数 BasePaginationParam 可以根据实际需求进行拓展,基本使用在前面已经介绍过,此处不做赘述。
  • Album 是搜狐视频电视剧频道返回数据的实体类,上面提到的 VideoInfo 包含在 Album 里面,因为现在只需要展示 VideoInfo 里的信息, 所以在 serverRespomse 方法里,有一个转换 mBaseListView.showAlbumMainInfo(album.getData().getVideos());
  • 实现父类 BasePaginationPresenter 中的抽象方法 serverHaveMoreData() ,思路就是 当前页面数 * 每一页的数据量,然后和 数据总量 比较大小。

Model 层的具体实现

  1 /**  2  * @author:ZengFanyu  3  */  4 public class SohuAlbumModel implements IBaseModel {  5     private static final String TAG = "SohuAlbumModel";  6     private String url;  7     private int method;  8     private IBasePaginationPresenter mPaginationPresenter;  9  10     public SohuAlbumModel(IBasePaginationPresenter paginationPresenter) { 11         mPaginationPresenter = paginationPresenter; 12     } 13  14     @Override 15     public void sendRequestToServer(Param param) { 16         String validUrl = null; 17         if (param != null && !TextUtils.isEmpty(url)&&mPaginationPresenter.hasMoreData()) { 18             validUrl = getValidUrl(url, param); 19             Log.d(TAG, ">> sendRequestToServer >> " + "ValidUrl:" + validUrl); 20         } 21         Log.d(TAG,">> sendRequestToServer >> " + "check param,url and server have data or not!") 22         if (!TextUtils.isEmpty(validUrl)) { 23             HttpUtils.executeByGet(validUrl, new Callback() { 24                 @Override 25                 public void onFailure(Call call, IOException e) { 26                     Log.d(TAG, ">> onFailure >> "); 27                     e.printStackTrace(); 28                     mPaginationPresenter.okHttpError(Constants.URL_ERROR, e.getMessage(), url); 29                 } 30  31                 @Override 32                 public void onResponse(Call call, Response response) throws IOException { 33                     if (!response.isSuccessful()) { 34                         Log.d(TAG, ">> onResponse >> " + "Not successful"); 35                         mPaginationPresenter.okHttpError(Constants.SERVER_ERROR, response.message(), url); 36                     } 37  38                     String responseJson = response.body().string(); 39                     Log.d(TAG, ">> onResponse >> " + "responseJson:" + responseJson); 40                     mPaginationPresenter.accessSuccess(responseJson); 41  42                 } 43             }); 44         } else { 45             Log.d(TAG, ">> sendRequestToServer >> " + "Valid Url is empty"); 46         } 47     } 48  49     private String getValidUrl(String url, Param param) { 50         return String.format(url, param.getPageIndex(), param.getPageSize()); 51     } 52  53  54     @Override 55     public void setRequestUrl(String url) { 56         this.url = url; 57     } 58  59     @Override 60     public void setRequestMethod(int method) { 61         this.method = method; 62     } 63  64     @Override 65     public void cancelRequest() { 66         HttpUtils.cancelCall(); 67     } 68 }

Model 层的实现还是跟之前的一样,直接实现 IBaseModel 接口即可。

  • 17 行可以看到,mPaginationPresenter.hasMoreData() ,这个就是对服务器点是否还有数据可以返回的判断,如果这里返回 false 那么就不回去进行网络请求,然后在 22 行打印个 Log 提醒。
  • 在看看 49 行的 getVaildUrl 方法,这个方法主要就是把传进来的 param 参数拼接进 url 中,形成有效的,可以请求到数据的 Url

效果图

Item 就展示了一下电视剧的 主演、名字、导演、集数、更新时间的信息。

小结

通过上面的封装和例子,起码证明了这一套封装能够跑的通了,以后如果还有关于分页请求的需求,可以直接继承上面的基类来实现,无非就是修改paramData 两个泛型的参数。

  • 前者是请求 url 的参数,根据具体的业务需求,封装 BasePaginationParam 的子类即可。
  • 后者是服务器端返回数据的实体类,也是根据数据的结构来封装的,在 Android Studio 中有 Gson Formatter 这个插件,封装 JavaBean 插件也轻松很多,在结合上面提到的 Gson 注解,全套了。

下一篇准备封装一下 OkHttp ,然后将封装之后的 OkHttp 整合到当前框架中,当然了,还是以分页接在为例

个人博客地址 :CODER FRAMER BIGZ

更多相关文章

  1. 图解IntelliJ IDEA 13版本对Android SQLite数据库的支持
  2. Android存储数据的三种方式
  3. Android数据库升级
  4. Android存储-SQLite数据库存储数据(三)
  5. Android NDK相关的库方法
  6. Android (SQLite 数据库与ContentProvider)
  7. Android控件EditText之点击软键盘中的回车键不换行,而是跳到下一
  8. android对html支持接口总结
  9. Android 匿名共享内存Java接口分析

随机推荐

  1. Android(安卓)乱码
  2. Android情景分析之深入解析system_server
  3. ADT的安装和配置
  4. android获取友盟渠道名以及获取applicati
  5. android之uriMathcer详解及使用
  6. ANdroid网易客户端
  7. Android(安卓)资源类型 整理
  8. Kotlin入门配置与简单实战
  9. Android(安卓)ApiDemos示例解析(179):Vie
  10. android Snapshot