优点:作为谷歌官方推荐的下拉刷新控件,同时简单而又不失优雅的风格,下拉刷新动画非常的流畅
缺点:没有上拉加载
看了网上很多自动加载下一页实现类似谷歌的Paging分页库的功能,基本都是基于实现RecyclerView.OnScrollListener接口,通过RecyclerView的滑动监听实现无感分页,都会存在一个问题,当加载的时候断掉网络,滑动到底部再打开网络,着时候再上拉是不会继续加载了,除非实现上拉加载,所以我想到了通过事件的分发dispatchTouchEvent来监听是否是上拉取代RecyclerView的滑动监听实现
首先需要拿到控件移动的最小距离,手移动的距离大于这个距离才能拖动控件
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
mScaledTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
}
通过事件的分发dispatchTouchEvent获取移动的起点Y值和移动过程Y值,用于下面判断是否是上拉状态,
/**
* 是否是上拉
*/
private fun isPullUp(): Boolean = (mDownY - mMoveY) >= mScaledTouchSlop
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
when (ev?.action) {
MotionEvent.ACTION_DOWN -> {
// 移动的起点
mDownY = ev.y
}
MotionEvent.ACTION_MOVE -> {
mMoveY = ev.y
// 移动过程中判断时候能下拉加载更多
if (canLoadMore()) {
onLoadingMore()
}
}
}
return super.dispatchTouchEvent(ev)
}
判断是否可以加载,通过RecyclerView的layoutManager去处理当前页面可见的最后一个item位置,去判断是否进行加载,mLoadingMore 和isRefreshing判断是否正在加载中,避免多次加载
因为StaggeredGridLayoutManager的特殊性可能导致最后显示的item存在多个,所以这里取到的是一个数组,得到这个数组后再取到数组中position值最大的那个就是最后显示的position值了
/**
* 是否正在加载
*/
private var mLoadingMore = false
/**
* 是否可以加载
*/
private fun canLoadMore(): Boolean {
//布局管理器为空时,不能加载
if (mLayoutManager == null) {
return false
}
//已经加载完成
if (mLoadingMoreCompleteAll) {
return false
}
//正在刷新和加载中不能进行加载
if (isRefreshing || mLoadingMore) {
return false
}
if (!isPullUp()) {
return false
}
if (mLayoutManager is LinearLayoutManager) {
val layoutManager = mLayoutManager as LinearLayoutManager
if (layoutManager.findLastVisibleItemPosition() >= layoutManager.itemCount - prefetchDistance) {
return true
}
}
if (mLayoutManager is StaggeredGridLayoutManager) {
val layoutManager = mLayoutManager as StaggeredGridLayoutManager
val lastPosition =
findMax(layoutManager.findLastVisibleItemPositions(IntArray(layoutManager.spanCount)))
if (lastPosition >= layoutManager.itemCount - prefetchDistance) {
return true
}
}
if (mLayoutManager is GridLayoutManager) {
val layoutManager = mLayoutManager as GridLayoutManager
if (layoutManager.findLastVisibleItemPosition() >= layoutManager.itemCount - prefetchDistance) {
return true
}
}
return false
}
/**
* StaggeredGridLayoutManager 获取最后的位置
*
* 因为StaggeredGridLayoutManager的特殊性可能导致最后显示的item存在多个,所以这里取到的是一个数组
* 得到这个数组后再取到数组中position值最大的那个就是最后显示的position值了
*
*/
private fun findMax(lastPositions: IntArray): Int {
var max = lastPositions[0]
for (value in lastPositions) {
if (value > max) max = value
}
return max
}
预取距离,滑动时当前页面可见最后一个item距离全部的item中最后一个item的距离,如果距离小于预取距离,就进行加载
/**
* 预取距离,默认为5
*/
private var prefetchDistance = 5
/**
* 设置预取距离
*/
fun setPrefetchDistance(prefetch: Int) {
this.prefetchDistance = prefetch
}
完整的代码
/**
* 智能刷新布局
*/
class SmartSwipeRefreshLayout : SwipeRefreshLayout {
private var mLayoutManager: RecyclerView.LayoutManager? = null
/**
* 加载完成全部
*/
private var mLoadingMoreCompleteAll = false
/**
* 是否正在加载
*/
private var mLoadingMore = false
/**
* 表示控件移动的最小距离,手移动的距离大于这个距离才能拖动控件
*/
private var mScaledTouchSlop: Int = 0
/**
* 预取距离,默认为5
*/
private var prefetchDistance = 5
/**
* 下拉刷新监听
*/
private var refreshingListener: OnRefreshingListener? = null
/**
* 智能加载监听,滑动到预取距离时回调
*/
private var loadMoreListener: OnSmartLoadMoreListener? = null
/**
* 下拉刷新和智能加载监听
*/
private var refreshLoadMoreListener: OnSmartRefreshLoadMoreListener? = null
constructor(context: Context) : super(context, null)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
setColorSchemeColors(
getColor(android.R.color.holo_red_light),
getColor(android.R.color.holo_orange_dark),
getColor(android.R.color.holo_green_light)
)
mScaledTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
setOnRefreshListener { onRefreshing() }
}
private fun getColor(color: Int): Int = ContextCompat.getColor(context, color)
private var mDownY: Float = 0F
private var mMoveY: Float = 0F
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
when (ev?.action) {
MotionEvent.ACTION_DOWN -> {
// 移动的起点
mDownY = ev.y
}
MotionEvent.ACTION_MOVE -> {
mMoveY = ev.y
// 移动过程中判断时候能下拉加载更多
if (canLoadMore()) {
onLoadingMore()
}
}
}
return super.dispatchTouchEvent(ev)
}
/**
* 是否是上拉
*/
private fun isPullUp(): Boolean = (mDownY - mMoveY) >= mScaledTouchSlop
/**
* 是否可以加载
*/
private fun canLoadMore(): Boolean {
//布局管理器为空时,不能加载
if (mLayoutManager == null) {
return false
}
//已经加载完成
if (mLoadingMoreCompleteAll) {
return false
}
//正在刷新和加载中不能进行加载
if (isRefreshing || mLoadingMore) {
return false
}
if (!isPullUp()) {
return false
}
if (mLayoutManager is LinearLayoutManager) {
val layoutManager = mLayoutManager as LinearLayoutManager
if (layoutManager.findLastVisibleItemPosition() >= layoutManager.itemCount - prefetchDistance) {
return true
}
}
if (mLayoutManager is StaggeredGridLayoutManager) {
val layoutManager = mLayoutManager as StaggeredGridLayoutManager
val lastPosition =
findMax(layoutManager.findLastVisibleItemPositions(IntArray(layoutManager.spanCount)))
if (lastPosition >= layoutManager.itemCount - prefetchDistance) {
return true
}
}
if (mLayoutManager is GridLayoutManager) {
val layoutManager = mLayoutManager as GridLayoutManager
if (layoutManager.findLastVisibleItemPosition() >= layoutManager.itemCount - prefetchDistance) {
return true
}
}
return false
}
/**
* StaggeredGridLayoutManager 获取最后的位置
*
* 因为StaggeredGridLayoutManager的特殊性可能导致最后显示的item存在多个,所以这里取到的是一个数组
* 得到这个数组后再取到数组中position值最大的那个就是最后显示的position值了
*
*/
private fun findMax(lastPositions: IntArray): Int {
var max = lastPositions[0]
for (value in lastPositions) {
if (value > max) max = value
}
return max
}
private fun onRefreshing() {
mLoadingMore = false
mLoadingMoreCompleteAll = false
refreshingListener?.onRefreshing()
refreshLoadMoreListener?.onRefreshing()
}
private fun onLoadingMore() {
mLoadingMore = true
mLoadingMore = true
loadMoreListener?.onLoadMore()
refreshLoadMoreListener?.onLoadMore()
}
/**
* 加载刷新完成
*/
fun finishRefreshing() {
isRefreshing = false
}
/**
* 加载更多完成
*/
fun finishLoadingMore() {
mLoadingMore = false
}
/**
* 加载完成全部,不会继续加载,下拉刷新会重置
*/
fun finishLoadingMoreAll() {
mLoadingMoreCompleteAll = true
}
/**
* 设置预取距离
*/
fun setPrefetchDistance(prefetch: Int) {
this.prefetchDistance = prefetch
}
/**
* 设置下拉刷新监听
*/
fun setOnRefreshingListener(listener: OnRefreshingListener) {
this.refreshingListener = listener
}
/**
* 设置智能加载监听
*/
fun setOnLoadMoreListener(
recyclerView: RecyclerView,
listener: OnSmartLoadMoreListener
) {
this.mLayoutManager = recyclerView.layoutManager
this.loadMoreListener = listener
}
/**
* 设置下拉刷新和智能加载监听
*/
fun setOnRefreshLoadMoreListener(
recyclerView: RecyclerView,
listener: OnSmartRefreshLoadMoreListener
) {
this.mLayoutManager = recyclerView.layoutManager
this.refreshLoadMoreListener = listener
}
}
interface OnSmartRefreshLoadMoreListener : OnRefreshingListener, OnSmartLoadMoreListener
interface OnRefreshingListener {
fun onRefreshing()
}
interface OnSmartLoadMoreListener {
fun onLoadMore()
}
因为是需要智能加载才需要RecyclerView的layoutManager,所以在设置加载监听的时候传入即可,
刷新和加载完成时切记需要调用刷新结束的方法
/**
* 加载刷新完成
*/
fun finishRefreshing() {
isRefreshing = false
}
/**
* 加载更多完成
*/
fun finishLoadingMore() {
mLoadingMore = false
}
加载完成全部
/**
* 加载完成全部,不会继续加载,下拉刷新会重置
*/
fun finishLoadingMoreAll() {
mLoadingMoreCompleteAll = true
}
如果需要添加ListView的支持,可以通过传入ListView,使用listView?.lastVisiblePosition来获取最后一个可见的位置添加扩展,具体参考canLoadMore()方法里面recyclerView.layoutManager的实现
SmartSwipeRefreshLayout 的源码可以直接拷贝使用,也可以前往作者的 GitHub 仓库查看下载:
https://github.com/BugRui/SmartSwipeRefreshLayout