下拉刷新与自动加载

下拉刷新是Android App开发中非常常用的功能, 网上也有很多开源的下拉刷新控件. CoreLibs中原先使用的handmark pulltorefresh, 现在选用的则是 Ultra-Pull-To-Refresh. 新的PTR框架有如下优点:

  • 轻量

  • 理论上支持所有的View

  • 易扩展

  • 易自定义

  • 性能不错

具体的使用方法请参考上面的链接, 这里就不再赘述了. 但是呢, Ultra ptr也不是没有缺点, 比如库中提供了Lollipop风格的下拉头部, 如果想要在每一个下拉控件中使用还需要加入不少代码. 然后每次使用下拉组件的时候需要一些相似的配置代码. 最主要的是Ultra ptr只支持下拉, 而不支持加载更多. 因此我们需要扩展一下这个库, 目标有两个:

  1. 默认头部变为Lollipop风格, 去掉重复代码, 使用更简洁

  2. 加入自动加载更多 - auto load more.

ptr的扩展类均位于com.corelibs.views.ptr下. 以下是包结构:

| ptr
  | layout 扩展的布局
    -PtrAutoLoadMoreLayout //自动加载更多布局
    -PtrLollipopLayout //Lolipop头部风格布局
  | loadmore
    | adapter
      -GridViewAdapter //GridView系列适配器
      -ListViewAdapter //ListView系列适配器
      -LoadMoreAdapter //自动加载更多的适配类
      -RecyclerViewAdapter //RecyclerView系列适配器
    | widget
      -AutoLoadMoreGridView //自动加载更多的GridView
      -AutoLoadMoreListView //自动加载更多的ListView
      -AutoLoadMoreSwipeMenuListView //自动加载更多的带侧滑菜单的ListView
      -AutoLoadMoreRecyclerView //自动加载更多的RecyclerView
    -AutoLoadMoreHandler //自动加载更多的真正处理类
    -AutoLoadMoreHook //PtrAutoLoadMoreLayout的child需实现此类以供PtrAutoLoadMoreLayout获取AutoLoadMoreHandler
    -OnScrollListener //兼容AdapterView与RecyclerView的OnScrollListener

layout.PtrLollipopLayout

我们首要目标是去掉重复的配置代码, 使用起来更简洁. 首先看看一个简单的例子:

Activity代码:

效果如下:

图1

用法非常简单, 只需使用PtrLollipopLayout包裹任意你想要刷新的控件即可. 在代码中, 如果在声明PtrLollipopLayout时加上泛型, 如PtrLollipopLayout<TextView>, 就可以使用ptrLayout.getPtrView()将PtrLollipopLayout内的TextView取出. 如果不加泛型, 则需要单独为TextView设置id, 并使用ButterKnife bind出来. 两种方式均可.

PtrLollipopLayout内部默认使用了Lollipop风格的下拉头部, 并且做了一些配置工作. 我们同样可以在代码中为PtrLollipopLayout做一些个性化的配置, 如通过setHeaderView(View header)设置自己的头部, 请注意, 自定义的头部必须实现PtrUIHandler接口. 如果出现PtrLollipopLayout解决不了的滑动冲突, 可以调用setPtrHandler(PtrHandler ptrHandler)自行处理滑动.

以下是几个需要注意的点:

  1. 此控件只能包含一个子View.

  2. 此控件仅支持下拉刷新, 如果需要自动加载, 请使用PtrAutoLoadMoreLayout

  3. 如果出现横向滑动冲突, 请设置disableWhenHorizontalMove(boolean)为true.

  4. 如果不想为child设置id并使用findViewById取出, 可以在声明PtrLollipopLayout的时候带上child类型的泛型, 然后就可以使用getPtrView()取出child. 如PtrLollipopLayout<ScrollView>.

  5. 刷新完成或加载完成后请调用complete().

layout.PtrAutoLoadMoreLayout

接下来是第二个目标 - 自动加载更多. 先看栗子:

ListView item布局:

Activity代码:

效果:

图2

使用带自动加载的下拉刷新就要比单纯的下拉刷新复杂的多. 这种时候就不能使用PtrLollipopLayout而需要使用PtrAutoLoadMoreLayout. 一般情况下, 自动加载更多只会出现在有ListView/GridView的情况下. 因此PtrAutoLoadMoreLayout的子视图基本都是ListView/GridView, 或他们的派生类.

但是如果直接使用PtrAutoLoadMoreLayout加上ListView/GridView, 也是无法实现自动加载的, 如:

不仅没有效果, 还会报如下错误:

这是因为PtrAutoLoadMoreLayout只是一个外壳, 本身只带有下拉刷新的功能, 不带有自动加载的功能. PtrAutoLoadMoreLayout所有有关自动加载的api全部是代理至另外一个类 - AutoLoadMoreHandler. AutoLoadMoreHandler才是真正处理自动加载功能的类. PtrAutoLoadMoreLayout需要借助AutoLoadMoreHook来获取AutoLoadMoreHandler, 因此PtrAutoLoadMoreLayout的子控件必须实现AutoLoadMoreHook. AutoLoadMoreHook的定义:

现在PtrAutoLoadMoreLayout就可以通过getLoadMoreHandler来获取AutoLoadMoreHandler实现自动加载更多的功能了. 那么, 例子中的AutoLoadMoreListView又是什么鬼? AutoLoadMoreListView是CoreLibs中预定义好的一个控件, 它实现了AutoLoadMoreHook. 如果我们需要一个带自动加载的ListView, 就可以使用AutoLoadMoreListView. AutoLoadMoreListView全部代码:

AutoLoadMoreListView的代码非常简单, 除了三个继承自ListView需要实现的构造函数外, 就只有一个实现了AutoLoadMoreHook的getLoadMoreHandler方法. 该方法中也只是new了一个AutoLoadMoreHandler对象并返回而已.

如果我们有一个自定义的ListView, 实现了侧滑菜单, 名字叫SwipeMenuListView, 我们想要为SwipeMenuListView加上下拉和自动加载怎么办? 很简单, 定义一个新的继承自SwipeMenuListView, 并且实现了AutoLoadMoreHook的控件, 然后我们在PtrAutoLoadMoreLayout中包含该控件即可. 使用方法除了SwipeMenuListView自己的API外, 其他与默认的ListView完全一样. 代码如下:

接着看看AutoLoadMoreHandler的定义与构造函数:

AutoLoadMoreHandler的构造函数需要两个参数, 第一个context不用多说, 第二个LoadMoreAdapter又是何方神圣? LoadMoreAdapter是一个接口, 类似于适配器, 因此取名叫做Adapter:

可以看到LoadMoreAdapter的方法基本都是对ListView/GridView的一些方法的包装. LoadMoreAdapter的存在就是为了适配各种ListView/GridView. AutoLoadMoreHandler内部会在需要添加底部视图的时候去调用LoadMoreAdapter的addFooterView, 在需要计算何时要显示底部视图的时候调用getLastVisiblePosition, getRowCount等. 至于具体实现, 则交由各种实现类去处理. CoreLibs中目前定义好了两个实现类 - ListViewAdapter和GridViewAdapter. 两个类的代码差不多, 这里只贴ListViewAdapter的:

代码逻辑比较简单, 基本都是直接调用listView的方法而没有很多逻辑判断. 可以看到, AutoLoadMoreSwipeMenuListView中的getLoadMoreHandler的返回语句new AutoLoadMoreHandler<>(getContext(), new ListViewAdapter<SwipeMenuListView>(this))也是使用的ListViewAdapter, 只不过泛型传递的是SwipeMenuListView. 这意味着, 只要是继承自ListView的控件都可以使用ListViewAdapter. GridView同理. 如果以后有了其他类型的控件可以创建一个新的LoadMoreAdapter的实现类.

接下来看看RecylerView的Adapter, 相较于ListView/GridView的会复杂一下:

由于RecyclerView与传统的AdapterView的OnScrollListener不一样, 所以在setOnScrollListener代码中做了一些额外的适配工作. 如果需要使用自定义的LayoutManager, 建议继承自系统默认的三个 - LinearLayoutManager, GridLayoutManager以及StaggeredGridLayoutManager, 因为RecyclerViewAdapter中只适配了这几个的LayoutManager. 或者可以新建一个Adapter专门对自定义的LayoutManager做处理.

接着, 我们来看看具体实现自动加载更多功能的AutoLoadMoreHandler的关键代码:

上述代码逻辑也不是很复杂, 相信配合注释不难理解. 需要注意的是, 在构造函数中, AutoLoadMoreHandler并没有做一些初始化操作, 仅仅是赋值. 初始化操作init是在setup方法中被调用的. 那么setup何时会被调用? 这需要看看PtrAutoLoadMoreLayout的部分代码:

可以看到, 在PtrAutoLoadMoreLayout被从xml解析完成之后, 就会去调用setupHook()获取AutoLoadMoreHook, 并通过AutoLoadMoreHook的getLoadMoreHandler来获取AutoLoadMoreHandler对象. 然后调用setup. setupHook方法中的mContent就是被PtrAutoLoadMoreLayout包裹的子控件. 到此, 整个自动加载更多就解释的差不多了.

以下是使用PtrAutoLoadMoreLayout需要注意的几个地方:

  1. 如果只需下拉刷新功能, 请使用PtrLollipopLayout

  2. 此控件中的child view必须实现AutoLoadMoreHook

  3. 此控件是对AutoLoadMoreHandler功能的转发. AutoLoadMoreHook的getLoadMoreHandler()需要的就是AutoLoadMoreHandler.

  4. 刷新完成或加载完成后请调用complete(), 而不是refreshComplete()或loadingFinished().

总结

下面总结一下PtrLollipopLayout与PtrAutoLoadMoreLayout在使用上的相同点与区别:

  1. 只有下拉刷新功能时应该使用PtrLollipopLayout, 带有自动加载更多时应该使用PtrAutoLoadMoreLayout.

  2. 两者都应该使用complete()方法来结束刷新或者加载状态.

  3. PtrLollipopLayout使用setRefreshCallback(RefreshCallback callback)来设置回调.

  4. PtrAutoLoadMoreLayout使用setRefreshLoadCallback(RefreshLoadCallback callback)来设置回调.

  5. RefreshLoadCallback继承自RefreshCallback, 比RefreshCallback多了onLoading方法.

  6. RefreshCallback定义在PtrLollipopLayout内, RefreshLoadCallback定义在PtrAutoLoadMoreLayout内.

  7. PtrLollipopLayout与PtrAutoLoadMoreLayout都只能包含一个子视图.

  8. PtrLollipopLayout子视图可以是任意View, 但是PtrAutoLoadMoreLayout的子视图必须实现AutoLoadMoreHook接口.

Last updated

Was this helpful?