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

版权声明:本文为博主原创文章,若有不对,敬请指出! https://blog.csdn.net/sinat_33661267/article/details/78452247

个人博客: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();

}

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

  1. refresh(Param param)View 层调用,用于通知 Model 层刷新数据
  2. loadingNext()View 层调用,用于通知 Model 层加载下一页数据
  3. 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主要是封装了摘要中提到的 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<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),并且是全中文的!直接在 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<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.OnPullLoadMoreListenreonRefresh() 回调方法中,核心代码就是这一行 mAlbumPresenter.refresh(mParam); ,通知 Presenter 层去刷新数据, 至于 Presenter 层如何刷新。。 关我 View 层 X 事~
  • PullLoadRecycler.OnPullLoadMoreListenreonLoadMore() 回调方法中,也是直接调用 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 就展示了一下电视剧的 主演、名字、导演、集数、更新时间的信息。

小结

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

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

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

个人博客地址 :CODER FRAMER BIGZ

阅读更多

没有更多推荐了,返回首页