美文网首页
SmartSwipeRefreshLayout 实现列表分页无感

SmartSwipeRefreshLayout 实现列表分页无感

作者: BugRui | 来源:发表于2019-01-03 20:39 被阅读0次

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

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

          本文链接:https://www.haomeiwen.com/subject/pzxacqtx.html