分页
分页也是APP中必不可少的功能, 一般配合 滚动到底部自动加载的控件 一起使用. 但是光有控件还不够, 我们还需要在Presenter中去控制页数, 什么时候能加载, 什么时候不能加载. 下拉刷新的时候还需要重置页数. 这些代码都是可以复用的, 因此就有了BasePaginationPresenter.
旧的模式
旧的分页模式将分页的通用逻辑都写在BasePaginationPresenter中,如控制页数,判断页数等。一旦分页逻辑有变化就需要改动BasePaginationPresenter,甚至可能影响到Presenter的写法,甚是丑陋。如果想扩展另外一种分页模式,除了改代码别无他法。因此催生的重构分也逻辑的想法。
新的模式
新的分页模式将分页的逻辑抽取出来,并使用策略+工厂模式封装,并将BasePaginationPresenter继续细化为使用不同分页策略的Presenter,这样使得一旦分页逻辑有变,只需要修改策略或者BasePaginationPresenter的子类,而不用去动BasePaginationPresenter。扩展也只需要新建一个策略以及BasePaginationPresenter的子类。
下面来看看写法,这里以页数分页为例-即根据服务器返回的总页数以及当前页数来判断是否能分页:
首先,Presenter需要使用PagePresenter,PagePresenter是BasePaginationPresenter的子类,BasePaginationPresenter中需要的是BasePaginationView类型的view接口,也就是说Activity/Fragment必须实现继承自BasePaginationView的view接口.
BasePaginationView定义:
/**
* 带有分页功能的BaseView
*/
public interface BasePaginationView extends BaseView {
/**
* 一次页面加载完成操作
*/
void onLoadingCompleted();
/**
* 所有页面均加载完成
*/
void onAllPageLoaded();
}
一般情况下, 我们需要在onLoadingCompleted()中隐藏加载框, 在onAllPageLoaded()中禁止自动加载.
如何使用PagePresenter和BasePaginationView? 首先我们需要这样定义我们的Activity/Fragment以及Presenter和View:
public class HomeFragment extends BaseFragment<HomeView, HomePresenter>
implements HomeView
public interface HomeView extends BasePaginationView
public class HomePresenter extends PagePresenter<HomeView>
接着按照下面的代码来写Presenter中需要做分页的方法. 如获取热门产品的方法:
public void getTopProducts(final boolean reload) {
if (!doPagination(reload)) return;
if (reload) view.showLoading();
api.getTopProducts(getPageNo(), getPageSize())
.compose(new ResponseTransformer<>(this.<BaseData<List<String>>>bindToLifeCycle()))
.subscribe(new PaginationSubscriber<BaseData<List<String>>>(view, this, reload) {
@Override
protected void onDataNotNull(BaseData<List<String>> data) {
view.renderData(reload, data.data);
}
@Override
protected Object getCondition(BaseData<List<String>> data, boolean dataNotNull) {
return data.page.pageCount;
}
@Override
protected List getListResult(BaseData<List<String>> data, boolean dataNotNull) {
if (dataNotNull) return data.data;
return null;
}
});
}
关键的地方就在if (!doPagination(reload)) return;
这一句。doPagination方法位于BasePaginationPresenter中,翻页的通用逻辑就定义在该方法中。如果doPagination返回了false, 则意味当前不能再加载数据了, 需要return退出函数。如果为true, 则加载下一页数据。
一般分页接口都需要当前的页数, 以及每页加载多少条数据, 我们只需要使用PagePresenter的getPageNo()与getPageSize()方法即可。当然不同的分页策略可能需要的参数都不一样,我们需要根据具体的策略具体对待。
与普通网络请求不一样,这里需要使用PaginationSubscriber而不是ResponseSubscriber来订阅请求结果。接着请求下一页数据成功之后, 服务器会返回总页数, 这个时候我们需要在PaginationSubscriber的getCondition中返回总页数。并且在getListResult中返回服务器返回的List集合。最后,翻页成功并且返回的数据不为空的情况下,会回调PaginationSubscriber的onDataNotNull方法。如果返回为空,会回调onDataIsNull方法。默认此方法会调用BaseView的showEmptyHint方法,当然也可以选择覆写。
到此翻页的逻辑算是完成了一大部分. 接着看看Activity/Fragment如何去实现BasePaginationView的两个方法:
@Override
public void onLoadingCompleted() {
ptrAutoLoadMoreLayout.complete();
}
@Override
public void onAllPageLoaded() {
ptrAutoLoadMoreLayout.disableLoading();
}
全部是转发PtrAutoLoadMoreLayout的方法. 我们也可以覆写BaseView的showLoading与hideLoading方法, 覆盖BaseActivity的默认加载框, 而使用PtrAutoLoadMoreLayout的下拉式加载:
@Override
public void showLoading() {
ptrAutoLoadMoreLayout.setRefreshing();
}
@Override
public void hideLoading() {
ptrAutoLoadMoreLayout.complete();
}
接下来, 由于要配合PtrAutoLoadMoreLayout一起实现分页功能, 因此肯定需要实现PtrAutoLoadMoreLayout的RefreshLoadCallback接口:
@Override
public void onRefreshing(PtrFrameLayout frame) {
ptrAutoLoadMoreLayout.enableLoading();
if (!frame.isAutoRefresh()) presenter.getTopProducts(true);
}
@Override
public void onLoading(PtrFrameLayout frame) {
presenter.getTopProducts(false);
}
注意: 由于PtrLollipopLayout/PtrAutoLoadMoreLayout的setRefreshing会回调RefreshCallback/RefreshLoadCallback的onRefreshing方法, 所以在onRefreshing中我们需要判断当前是不是自动刷新的if (!frame.isAutoRefresh())
, 自动刷新就不要请求数据了, 不然就会重复执行两次. 又因为我们覆写了BaseActivity的showLoading方法为ptrAutoLoadMoreLayout.setRefreshing()
, 同样每次showLoading的话就会回调onRefreshing一次, 因此在Presenter中建议加上if (reload) view.showLoading()
.
到此, 整个分页功能就完成了. 可以看到整体还是不是很简单的. BasePaginationPresenter也只是抽象了通用的分页的逻辑,实现则交由具体的分页策略。下面看看关键方法doPagination:
/**
* 分页操作, 具体页面效果需自行实现
* @param reload 是否是刷新操作, true为刷新, false则为加载下一页
* @return 分页是否成功, 当当前页码超过总页数时会返回false
*/
protected boolean doPagination(boolean reload) {
boolean can = strategy.canDoPagination(reload);
if (can) strategy.doPagination(reload);
else view.onAllPageLoaded();
return can;
}
接着来看看分页策略PaginationStrategy接口的定义:
public interface PaginationStrategy {
/**
* 根据分页条件判断是否能分页
*/
boolean canDoPagination(boolean reload);
/**
* 分页具体逻辑
*/
void doPagination(boolean reload);
/**
* 获取分页条件
*/
Object getCondition();
/**
* 设置分页条件
*/
void setCondition(Object c);
}
根据页数分页的具体逻辑实现类则是PageStrategy。PageStrategy内部维护了一个Page类,有当前页数,每页个数,总页数等字段。PageStrategy实现了PaginationStrategy,接着来看看canDoPagination,doPagination以及setCondition的实现:
protected Page page = new Page(1, 15, -1);
@Override
public boolean canDoPagination(boolean reload) {
return reload || page.pageNo < page.pageCount;
}
@Override
public void doPagination(boolean reload) {
if (reload) page.pageNo = 1;
else page.pageNo++;
}
@Override
public void setCondition(Object c) {
page.pageCount = (int) c;
}
细心的朋友可能会疑惑了, doPagination方法中仅仅回调了onAllPageLoaded方法, 而没有onLoadingCompleted方法. 那么onLoadingCompleted方法是在何处调用的? 下面看看ResponseHandler类的resetLoadingStatus方法(不知道ResponseHandler的同学可以参考ResponseSubscriber,PaginationSubscriber继承自ResponseSubscriber):
public void resetLoadingStatus() {
if (view != null) {
if (view instanceof BasePaginationView) {
BasePaginationView paginationView = (BasePaginationView) view;
paginationView.onLoadingCompleted();
}
view.hideLoading();
}
}
到此, 整个分页的逻辑就很清晰了. 分页功能是由分页策略(BasePaginationPresenter,PaginationStrategy), Activity/Fragment(BasePaginationView), PaginationSubscriber以及PtrAutoLoadMoreLayout四方合作的结果.
扩展分页策略
如何扩展分页策略?这里以页书分页的一个变种为例--根据数据集合个数来判断是否能分页,分页逻辑则和页数分页一样。这里我们只需要新建两个类:
ListResultStrategy
ListResultStrategy需要实现PaginationStrategy,但由于其分页逻辑与PageStrategy一致,因此可以直接继承PageStrategy:
public class ListResultStrategy extends PageStrategy { private List lastResult; @Override public boolean canDoPagination(boolean reload) { return reload || (lastResult != null && lastResult.size() >= page.getPageSize()); } @Override public void setCondition(Object c) { lastResult = (List) c; } }
ListPagePresenter
由于两种分页需要的条件一样,因此ListPagePresenter类似于PagePresenter
public abstract class ListPagePresenter<T extends BasePaginationView> extends BasePaginationPresenter<T> { private ListResultStrategy pageStrategy; public ListPagePresenter() { setPaginationStrategy(StrategyFactory.getStrategy(StrategyFactory.ListResultStrategy)); pageStrategy = (ListResultStrategy) strategy; } /** 获取当前页数下标 **/ public int getPageNo() { return pageStrategy.getCondition().getPageNo(); } /** 获取当前每页个数 **/ public int getPageSize() { return pageStrategy.getCondition().getPageSize(); } /** 设置每页个数 **/ public void setPageSize(int size) { pageStrategy.setPageSize(size); } }
只需要在构造函数里设置策略为ListResultStrategy。
StrategyFactory
前面提到分页使用了策略+工厂模式,StrategyFactory就是一个工厂类,一个非常简单的没有接口的工厂类,并且该工厂是一个枚举。这里为何要多此一举的使用工厂模式,而不是直接new出来具体的策略?主要是因为策略模式的缺陷:具体的策略必须暴露出去,并且还要由上层模块初始化,这不合适,高层次模块对低层次模块应该仅仅处在“接触”的层次上,而不是“耦合”的关系,否则维护的工作量会非常大。正好工厂方法可以帮我们生产指定的对象。
在新建了策略类和Presenter之后,就需要在StrategyFactory添加一个属性
ListResultStrategy("com.corelibs.pagination.strategy.ListResultStrategy")
,策略名加上路径即可。使用不同的策略在写法上的不同只有继承不同的Presenter,获取不同的分页条件,以及getCondition的实现不同。
Last updated
Was this helpful?