Android当中的MVP模式(三)基于分页列表的封装
来源:互联网 发布:java要学到什么程度 编辑:程序博客网 时间:2024/05/17 00:54
个人博客: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<Param> extends IBasePresenter<Param> {/** * 刷新数据的接口 * * @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();}
也是一个泛型的接口,增加的三个方法 :
refresh(Param param)
在View
层调用,用于通知Model
层刷新数据loadingNext()
在View
层调用,用于通知Model
层加载下一页数据hasMoreData()
在Model
层请求网络数据前调用做判断,是否还有下一页数据
有了针对分页刷新的接口之后,还需要有一个实现它的基类:
/** * @author:ZengFanyu * @date:2017/10/20 */public abstract class BasePaginationPresenter<Param extends BasePeginationParam, Data> implements IBasePaginationPresenter<Param> { private static final String TAG = "BasePaginationPresenter"; private IBaseModel mBaseModel; private IBaseView mBaseListView; private Param mParam; private Class<Data> 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<Data> 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<String, String> getParams() { return null; } @Override public boolean hasMoreData() { return ServerHaveMoreData(); }}
- 在类申明时,可以看到 Param extends BasePeginationParam
,这里的 BasePeginationParam
主要是封装了摘要中提到的 PageIndex
和 PageSize
两个参数,以及他们的 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<VideoInfo> 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),并且是全中文的!直接在AS
的Inspection 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<VideoInfo> 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.OnPullLoadMoreListenre
的onRefresh()
回调方法中,核心代码就是这一行mAlbumPresenter.refresh(mParam);
,通知Presenter
层去刷新数据, 至于Presenter
层如何刷新。。 关我View
层 X 事~ - 在
PullLoadRecycler.OnPullLoadMoreListenre
的onLoadMore()
回调方法中,也是直接调用mAlbumPresenter.loadingNext()
。
下面说说 Presenter
层的代码
Presenter
层的具体实现
/** * @author:ZengFanyu * Function: */public class AlbumPresenter extends BasePaginationPresenter<BasePaginationParam, Album> { private ISohuSerials mBaseListView; private Handler mHandler = new Handler(Looper.getMainLooper()); private int mTotalCount; public AlbumPresenter(ISohuSerials baseListView, Class<Album> 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<Param extends BasePaginationParam> implements IBaseModel<Param> { 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
就展示了一下电视剧的 主演、名字、导演、集数、更新时间的信息。
小结
通过上面的封装和例子,起码证明了这一套封装能够跑的通了,以后如果还有关于分页请求的需求,可以直接继承上面的基类来实现,无非就是修改param
和 Data
两个泛型的参数。
- 前者是请求
url
的参数,根据具体的业务需求,封装BasePaginationParam
的子类即可。 - 后者是服务器端返回数据的实体类,也是根据数据的结构来封装的,在
Android Studio
中有Gson Formatter
这个插件,封装JavaBean
插件也轻松很多,在结合上面提到的Gson
注解,全套了。
下一篇准备封装一下
OkHttp
,然后将封装之后的OkHttp
整合到当前框架中,当然了,还是以分页接在为例
个人博客地址 :CODER FRAMER BIGZ
- Android当中的MVP模式(三)基于分页列表的封装
- Android 当中的 MVP 模式(二)封装
- Android当中的MVP模式(四)插曲-封装OkHttp
- Android当中的MVP模式(六)View 层 Activity 的基类--- BaseMvpActivity 的封装
- Android当中的MVP模式(五)封装之后的OkHttp工具在Model层
- Android当中的MVP模式(一)基本概念
- Android当中的MVP模式(七)终篇---关于对MVP模式中代码臃肿
- 基于OKhttp的MVP封装
- 基于谷歌todoapp的android mvp(三)
- 基于MVP架构的OKHttp3的封装
- Android的MVP模式
- Android的MVP模式
- Android的MVP模式
- Android的MVP模式
- 基于MVP设计模式的Android应用架构设计
- 软件设计模式:基于MVP的Android项目架构
- 基于MVP开发模式的简单Android项目
- RxBus-mvp模式下对Rxjav的封装(一)
- Android 当中的 MVP 模式(二)封装
- 一台电脑同时运行多个jdk
- jQuery.Ajax下载文件
- java多线程管理 concurrent包用法详解
- 求一个三位整数的个位、十位、百位之和
- Android当中的MVP模式(三)基于分页列表的封装
- 动态规划专栏(3)-背包问题(1)
- web开发之防止表单重复提交
- 第一章 第三课 准备和了解Scratch环境
- QT定时器
- 设计模式--职责链与组合
- vue基础语法以及父子组件如何相互传值
- Flying to the Mars
- mmap