oynix

于无声处听惊雷,于无色处见繁花

手把手教你实现RecyclerView的下拉刷新和上拉加载更多


纵观多数App,下拉刷新和上拉加载更多是很常见的功能,但是谷歌官方只有一个SwipeRefreshLayout用来下拉刷新,上拉加载更多还要自己做。

基于RecyclerView简单封装了这两个操作,下拉刷新支持LinearLayoutManager、GridLayoutManager和StaggeredGridLayoutManager;上拉加载更多只支持前两者。


话不多说先上效果图 数据来自干货集中营


(下拉刷新)


(上拉加载更多 – LinearLayoutManager)


(上拉加载更多 – GridLayoutManager)


(一) 使用方式,很简单 如下:

  • 1. 下拉刷新 3步走

1.1 布局文件

1
2
3
4
5
6
7
8
9
10
11
12
// 用SwipeRefreshLayout包裹RecyclerView
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/gank_swipe_refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.v7.widget.RecyclerView
android:id="@+id/gank_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"/>
</android.support.v4.widget.SwipeRefreshLayout>

1.2 给SwipeRefreshLayout 添加监听 增加触发刷新时的操作(比如重新请求数据)

1
2
3
4
5
6
7
SwipeRefreshLayout swipeRefreshLayout = findViewById();
swipeRefreshLayout.setOnRefreshListener(new swipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
// do something, such as re-request from server or other
}
});

1.3 刷新操作(重新请求数据)完成后要回调来停止隐藏刷新动画(中上方圆形悬浮进度条旋转动画)

1
swipeRefreshLayout.setRefreshing(false);

至此下拉刷新完成

  • 2.上拉加载3步走

2.1 初始化AdapterWrapperSwipeToLoadHelper

1
2
3
4
5
6
// adapter是你自己为RecyclerView写的Adapter
RecyclerView.Adapter adapter = new YourOwnAdapter();
AdapterWrapper adapterWrapper = new AdapterWrapper(adapter);
RecyclerView recyclerView = findViewById();
// 将RecyclerView和刚创建的adapterWrapper传入
SwipeToLoadHelper helper = new SwipeToLoadHelper(recyclerView, adapterWrapper);

2.2 设置加载动作触发后的监听

1
2
3
4
5
6
helper.setLoadMoreListener(new SwipeToLoadHelper.LoadMoreListener() {
@Override
public void onLoad() {
// do something, such as request more data from server or other.
}
})

2.3 加载更多内容完成后要回调方法停止动画

1
helper.setLoadMoreFinish()

至此上拉加载完成 (注意更新数据时要调用AdapterWrapper.notifyDataSetChanged)


(二) 简明扼要的实现思路(上拉加载操作)

RecyclerViewitemView的显示情况分为四种:

  1. 第1个可见的(部分显示或者完全显示都算可见)
  2. 第1个可见的且是完整的(完全显示算作完整的)
  3. 最后1个可见的
  4. 最后1个可见的且是完整的
  • 1. 回弹效果
    即手指抬起滑动停止,上拉加载更多部分显示时,将上拉加载更多滚动到不显示,使上面挨着它的itemView为最后1个可见且是最后1个完整可见。

    监听RecyclerView的滚动,当RecyclerView处于SCROLL_STATE_IDLE 状态时,获取最后1个完整可见的itemView:如果是倒数第2个item则计算该item的下边距到RecyclerView底部的距离deltaY,然后将RecyclerView向下滚动deltaY;如果是上拉加载更多则触发加载操作;其他情况不用处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 关键代码 rv : recyclerView
int lcp = layoutManager.findLastCompletelyVisibleItemPosition();
if (lcp == layoutManager.getItemCount() - 2) {
// 倒数第2项
int fcp = layoutManager.findFirstCompletelyVisibleItemPosition();
View child = layoutManager.findViewByPosition(lcp);
int deltaY = rv.getBottom() - rv.getPaddingBottom() -
child.getBottom();
// fcp为0时说明列表滚动到了顶部, 不再滚动
if (deltaY > 0 && fcp != 0) {
rv.smoothScrollBy(0, -deltaY);
}
} else if (lcp == layoutManager.getItemCount() - 1) {
// 最后一项完全显示, 触发操作, 执行加载更多操作
if (listener != null) {
listener.onLoad();
}
}

  • 2. 添加底部加载更多itemView

2.1 AdapterWrapper重写了getItemCount方法,保证得到itemView的数量包括加载更多。当是LinearLayoutManager类型时直接加1;当是GridLayoutManager类型时,如果需要则先将列表最后一行填满,再加1。比如:列表每行有3个itemView,最后一行只有1个,这时就需要先加2,再加1,来保证加载更多占据完整的一行。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 关键代码 其中的adapter为构造函数中传入的原生RecyclerView.Adapter
if (adapterType == ADAPTER_TYPE_LINEAR) {
// 线性布局
return adapter.getItemCount() + 1;
} else {
// 网格布局 spanCount为每行itemView的个数
int remain = adapter.getItemCount() % spanCount; // 余数
if (remain == 0) {
return adapter.getItemCount() + 1;
}
// 余数不为0时,先凑满再加1
return adapter.getItemCount() + 1 + (spanCount - remain);
}

2.2 AdapterWrapper重写了getItemViewType方法,当是最后一个位置时返回ITEM_TYPE_LOAD

1
2
3
4
5
6
7
8
9
// 关键代码
public int getItemViewType(int position) {
// 位置是最后一个时, wrapper进行拦截
if (osition == getItemCount() - 1) {
return ITEM_TYPE_LOAD;// 要避免和原生adapter返回值可能重复
}
// 其他情况交给原生adapter处理
return adapter.getItemViewType(position);
}

2.3 AdapterWrapper重写了onCreateViewHolder方法,当类型为ITEM_TYPE_LOAD时返回加载更多ViewHolder,其他情况交给原生的adapter处理。

1
2
3
4
5
6
7
8
// 关键代码
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ITEM_TYPE_LOAD) {
return new LoadMoreHolder();
} else {
return adapter.onCreateViewHolder(parent, viewType);
}
}

2.4 AdapterWrapper重写了onBindViewHolder,这里有三种可能的情况:1. 正常的数据项itemView,交给adapter处理;2. GridView的空白itemView,隐藏处理;3. 底部的加载更多,目前不需要做什么处理。

1
2
3
4
5
6
7
8
9
// 关键代码
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (position == getItemCount() - 1) {
} else if (position < adapter.getItemCount()){
adapter.onBindViewHolder(holder, position);
} else {
holder.itemView.setVisibility(View.INVISIBLE);
}
}

(三) 额外的两个说明

  • SwipeFreshLayout有个setEnable(boolean)方法,设置为false的时候就下拉刷新功能就没有了,等同于普通的RecyclerView
  • 同样SwipeToLoadHelper有个setSwipeToLoadEnabled(boolean)方法,设置为false的时候上拉加载功能就没有了, 等同于普通的RecyclerView

如有问题,欢迎指正~

项目仓库地址,如有需要请自取~

------------- (完) -------------
  • 本文作者: oynix
  • 本文链接: https://oynix.com/2017/10/f49b7256166f/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

欢迎关注我的其它发布渠道