SmartSwipeRefreshLayout 实现列表分页无感加载

优点:作为谷歌官方推荐的下拉刷新控件,同时简单而又不失优雅的风格,下拉刷新动画非常的流畅
缺点:没有上拉加载

看了网上很多自动加载下一页实现类似谷歌的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

你可能感兴趣的:(SmartSwipeRefreshLayout 实现列表分页无感加载)