RecyclerView 下拉刷新和上拉加载
RecyclerView已经出来很久了,许许多多的项目都开始从ListView转战RecyclerView,那么,上拉加载和下拉刷新是一件很有必要的事情。
在ListView上,我们可以通过自己添加addHeadView
和addFootView
去添加头布局和底部局实现自定义的上拉和下拉,或者使用一些第三方库来简单的集成,例如android-pulltorefresh
或者android-Ultra-Pull-to-Refresh
,后者的自定义更强,但需要自己实现上拉加载。
而在下面我们将用两种方式来实现上拉加载和下拉刷新
SwipeRefreshLayout
+滑动底部自动加载使用第三方库
SwipeToLoadLayout
实现上拉加载和下拉刷新。
SwipeRefreshLayout
+滑动底部自动加载
SwipeRefreshLayout实现很简单,重点是滑动到底部自动加载应该如何实现,其实其实现的方式类似于ListView
的实现方式。
看一下activity_recycle_swiperefresh.xml
文件:
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swipe_refresh"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/swipe_target"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none" />
</android.support.v4.widget.SwipeRefreshLayout>
布局文件就两个控件,SwipeRefreshLayout
中嵌套RecyclerView
。
在代码中初始化RecyclerView
以及实现adapter
等,这不是重点,不再贴代码。
在RecyclerView
中有方法addOnScrollListener
,该方法类似于ListView
的setOnScrollListener
方法,OnScrollListener
中有两个方法的回调
onScrolled(RecyclerView recyclerView, int dx, int dy)
:滚动的回调,dx和dy表示手指滑动水平和垂直的偏移量。onScrollStateChanged(RecyclerView recyclerView, int newState)
:滑动状态的回调。
那么,我们的着重点就在这个两个方法上了。
对于向上加载更多,我们需要有如下判断
- 是否是向上滑动
- 是否滑动到底部
- 当前是否正在加载数据
- 当前状态是否是滑动停止的状态
实现比较复杂,定义一个类LoadDataScrollController
,继承类RecyclerView.OnScrollListener
,
因为onScrollStateChanged
实在状态改变时的回调,无法时时的获取显示的条目以及位置,所以我们在onScrolled
中获取相应位置,
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
/**
* 获取布局参数
*/
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
//如果为null,第一次运行,确定布局类型
if (mLayoutManagerType == null) {
if (layoutManager instanceof LinearLayoutManager) {
mLayoutManagerType = LayoutManagerType.LINEAR_LAYOUT;
} else if (layoutManager instanceof GridLayoutManager) {
mLayoutManagerType = LayoutManagerType.GRID_LAYOUT;
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
mLayoutManagerType = LayoutManagerType.STAGGERED_GRID_LAYOUT;
} else {
throw new RuntimeException("LayoutManager should be LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager");
}
}
//对于不太能够的布局参数,不同的方法获取到当前显示的最后一个条目数
switch (mLayoutManagerType) {
case LINEAR_LAYOUT:
mLastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
break;
case GRID_LAYOUT:
mLastVisibleItemPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();
break;
case STAGGERED_GRID_LAYOUT:
StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
if (mLastPostions == null) {
mLastPostions = new int[staggeredGridLayoutManager.getSpanCount()];
}
staggeredGridLayoutManager.findLastVisibleItemPositions(mLastPostions);
mLastVisibleItemPosition = findMax(mLastPostions);
break;
default:
break;
}
}
首先获取布局管理器,并判断是那种类型的,因为有三种类型,定义枚举来保存布局类型的参数
/**
*
* RecycleView的布局管理器的类型
* Created by Alex_MaHao on 2016/5/10.
*/
public enum LayoutManagerType {
LINEAR_LAYOUT,
GRID_LAYOUT,
STAGGERED_GRID_LAYOUT
}
然后根据布局惯例其的类型获取其当前显示的最大条目,对于瀑布流来说,他如果是垂直的两列瀑布的话,我们需要获取两列中分别最大条目数,进行比较,选出最大条目数。
/**
* 当是瀑布流时,获取到的是每一个瀑布最下方显示的条目,通过条目进行对比
*/
private int findMax(int[] lastPositions) {
int max = lastPositions[0];
for (int value : lastPositions) {
if (value > max) {
max = value;
}
}
return max;
}
拿到当前最大的条目数之后,在onScrollStateChange
中进行判断状态等,
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
//RecycleView 显示的条目数
int visibleCount = layoutManager.getChildCount();
//显示数据总数
int totalCount = layoutManager.getItemCount();
// 四个条件,分别是是否有数据,状态是否是滑动停止状态,显示的最大条目是否大于整个数据(注意偏移量),是否正在加载数据
if(visibleCount>0
&&newState==RecyclerView.SCROLL_STATE_IDLE
&&mLastVisibleItemPosition>=totalCount-1
&&!isLoadData){
//可以加载数据
isLoadData = true;
}
}
注释很清楚,在加载数据的地方,我们将isLoadData
设为true
,同时利用接口回调加载数据,等数据加载完成,通过setLoadDataStatus
方法设置为false
public void setLoadDataStatus(boolean isLoadData){
this.isLoadData = isLoadData;
}
如果这样就结束了,感觉很麻烦,对于刷新和加载更多,我们需要在调用的地方分别设置监听,那么我们可以让LoadDataScrollController
实现SwipeRefreshLayout
的刷新监听方法,在利用我们定义的统一的上拉刷新和加载数据接口进行处理
/**
* 实现上拉加载的监听:加载条件:滑动到最后,且是停止状态,则开始加载数据
* Created by Alex_MaHao on 2016/5/10.
*/
public class LoadDataScrollController extends RecyclerView.OnScrollListener implements SwipeRefreshLayout.OnRefreshListener {
/**
* 当前布局管理器的类型
*/
private LayoutManagerType mLayoutManagerType;
/**
* 当前RecycleView显示的最大条目
*/
private int mLastVisibleItemPosition;
/**
* 每列的最后一个条目
*/
private int[] mLastPostions;
/**
* 是否正在加载数据 包括刷新和向上加载更多
*/
private boolean isLoad