利用NestedScrollingParent2實(shí)現(xiàn)RecycleView下拉刷新 上拉加載

/**
 * @author : zyl
 * //感覺這種方式 雖然能實(shí)現(xiàn)下啦刷新 上拉加載 但NestedScrollingParent2 這套接口感覺主要是用于嵌套滾動(dòng)
 */
val BOTTOM_MAX_HEIGHT_DEFAULT = 100.dp
val HEAD_MAX_HEIGHT_DEFAULT = 100.dp

const val SCROLL_DEFAULT_DURATION = 400
const val RESISTANCE_FACTOR_DEFAULT = 0.6

class RefreshLayout(context: Context?, attrs: AttributeSet?) : ViewGroup(context, attrs),
    NestedScrollingParent2 {

    private val TAG = RefreshLayout::class.java.simpleName

    private var refreshState = RefreshState.NONE
        private set(value) {
            if (field != value) {
                field = value
                refreshStateChangeListener?.refreshStateChange(value)
            }
        }

    private var loadMoreState = LoadState.NONE
        private set(value) {
            if (field != value && canLoadMore.get()) {
                field = value
                refreshStateChangeListener?.loadMoreStateChange(value)
            }
        }

    /**
     * 是否能夠觸發(fā)加載更多的回調(diào)
     */
    private val canLoadMore = AtomicBoolean(true)

    private var headHeight = 0

    private var footHeight = 0

    /**
     * 阻力系數(shù)
     */
    private var resistance = RESISTANCE_FACTOR_DEFAULT

    private var bottomMaxHeight = BOTTOM_MAX_HEIGHT_DEFAULT

    private var headMaxHeight = HEAD_MAX_HEIGHT_DEFAULT

    private var targetView: View? = null

    private val overScroller = OverScroller(context)

    private val parentHelper = NestedScrollingParentHelper(this).apply {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            isNestedScrollingEnabled = true
        }
    }

    var refreshListener: OnRefreshListener? = null

    /**
     * 刷新狀態(tài)變化通知 方便外部頭部view 做狀態(tài)變化
     */
    var refreshStateChangeListener: RefreshStateChangeListener? = null

    /**
     * 添加刷新頭
     */
    fun addRefreshHeadView(view: View) {
        removeAllViews()
        addView(view)
        addView(targetView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
        requestLayout()
    }

    /**
     * 添加刷新頭和尾
     */
    fun addRefreshHeadAndFooter(headView: View, footerView: View) {
        removeAllViews()
        addView(headView)
        addView(targetView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
        addView(footerView)
        requestLayout()
    }

    /**
     * 布局加載完畢了 此時(shí)找到加需要刷新頭的那個(gè)targetView
     */
    override fun onFinishInflate() {
        super.onFinishInflate()
        if (childCount > 1) throw Exception("xml only support one child")
        targetView = getChildAt(0)
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        var selfWidth = 0
        var selfHeight = 0
        for (index in 0 until childCount) {
            val child = getChildAt(index)
            //忽略margin值的策略
            measureChild(child, widthMeasureSpec, heightMeasureSpec)
            selfWidth = max(child.measuredWidth, selfWidth)
            selfHeight += child.measuredHeight
        }
        setMeasuredDimension(selfWidth, selfHeight)
        //防止沒有刷新頭的情況
        if (childCount > 1) headHeight = getChildAt(0).measuredHeight
        //得到刷新尾部的高度
        if (childCount > 2) footHeight = getChildAt(2).measuredHeight
    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        //隱藏頭部布局
        var top = -headHeight
        //擺放子控件 豎直排列
        for (index in 0 until childCount) {
            val child = getChildAt(index)
            //居中擺放
            child.layout(
                measuredWidth / 2 - child.measuredWidth / 2,
                top,
                measuredWidth / 2 + child.measuredWidth / 2,
                top + child.measuredHeight
            )
            top += child.measuredHeight
        }
    }

    /**
     * 當(dāng)是刷新狀態(tài)時(shí),不允許用戶操作
     */
    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        if (refreshState == RefreshState.REFRESHING
            && ev.actionMasked == MotionEvent.ACTION_DOWN
            && headHeight > 0
        ) return true
        if (loadMoreState == LoadState.LOADING
            && ev.actionMasked == MotionEvent.ACTION_DOWN
            && footHeight > 0
        ) return true
        return super.onInterceptTouchEvent(ev)
    }

    /**
     * 重置列表刷新加載狀態(tài)
     */
    fun onRefreshDone() {
        if (scrollY > 0) {
            loadMoreState = LoadState.DONE
        } else {
            refreshState = RefreshState.REFRESHING_COMPLETE
        }
        overScroller.startScroll(0, scrollY, 0, -scrollY, SCROLL_DEFAULT_DURATION)
        postInvalidateOnAnimation()
    }


    /**
     * 設(shè)置是否觸發(fā)加載更多 if true 觸發(fā)
     */
    fun setTriggerLoadMore(trigger: Boolean) {
        canLoadMore.set(trigger)
    }

    override fun computeScroll() {
        if (overScroller.computeScrollOffset()) {
            scrollTo(overScroller.currX, overScroller.currY)
            postInvalidateOnAnimation()
        }
    }


    // NestedScrollingParent2

    override fun onNestedScrollAccepted(child: View, target: View, axes: Int, type: Int) {
        parentHelper.onNestedScrollAccepted(child, target, axes, type)
    }

    override fun onStartNestedScroll(child: View, target: View, axes: Int, type: Int): Boolean {
        //目前只支持豎直方向
        return nestedScrollAxes and ViewCompat.SCROLL_AXIS_VERTICAL != 0
    }

    //目前只支持豎直方向
    override fun getNestedScrollAxes(): Int {
        return ViewCompat.SCROLL_AXIS_VERTICAL
    }

    override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray?, type: Int) {
        //此方法 我的理解 是滑動(dòng)之前 需要問下父空間是否需要處理 目前沒有作為子空間的場(chǎng)景 所以不需要處理
        Log.v(TAG, "onNestedPreScroll target = $target,dy = $dy,scrollY = ${scrollY},type = $type")
        if (scrollY != 0) {
            consumed?.let {
                it[1] = dy
                onNestedScroll(target, dx, 0, 0, dy, TYPE_TOUCH)
            }
        }
    }

    //控制fling
    override fun onNestedPreFling(target: View, velocityX: Float, velocityY: Float): Boolean {
        return scrollY != 0
    }

    override fun onNestedScroll(
        target: View,
        dxConsumed: Int,
        dyConsumed: Int,
        dxUnconsumed: Int,
        dyUnconsumed: Int,
        type: Int
    ) {
        if (dyUnconsumed != 0) {
            //真正處理滑動(dòng)事件
            var fixDy = fixYOffset(dyUnconsumed)
//            scrollBy(0, fixDy)
            scrollBy(0, fixDy - (fixDy*resistance).toInt())
            if (scrollY < 0) {
                refreshState = if (headHeight > 0 && -scrollY >= headHeight) {
                    RefreshState.RELEASE_TO_REFRESH
                } else {
                    RefreshState.PULL
                }
            } else {
                loadMoreState = if (footHeight in 1..scrollY) {
                    LoadState.RELEASE_TO_LOADING
                } else {
                    LoadState.PULL
                }
            }
        }
        //處理完剩余的事件 看看父親控件 是否需要 ,此情況暫不考慮
    }

    override fun onStopNestedScroll(target: View, type: Int) {
        Log.v(TAG, "onStopNestedScroll target = $target,scrollY = $scrollY")
        if (refreshState == RefreshState.RELEASE_TO_REFRESH) {
            Log.v(TAG, "onStopNestedScroll refreshing")
            //觸發(fā)刷新回調(diào)
            refreshState = RefreshState.REFRESHING
            //滾動(dòng)到指定高度
            overScroller.startScroll(0, scrollY, 0, -scrollY - headHeight, SCROLL_DEFAULT_DURATION)
            postInvalidateOnAnimation()
            refreshListener?.reFresh()
        } else if (loadMoreState == LoadState.RELEASE_TO_LOADING) {
            Log.v(TAG, "onStopNestedScroll loadMore")
            loadMoreState = LoadState.LOADING
            //底部滑動(dòng)到指定位置
            overScroller.startScroll(
                0,
                scrollY,
                0,
                -(scrollY - footHeight),
                SCROLL_DEFAULT_DURATION
            )
            postInvalidateOnAnimation()
            refreshListener?.loadMore()
        } else {
            //不觸發(fā)刷新 或是加載
            if (scrollY > 0) {
                loadMoreState = LoadState.NONE
            } else {
                refreshState = RefreshState.NONE
            }
            overScroller.startScroll(0, scrollY, 0, -scrollY, SCROLL_DEFAULT_DURATION)
            postInvalidateOnAnimation()
        }
        parentHelper.onStopNestedScroll(target, type)
    }

    /**
     * 修正滑動(dòng)位置 防止滑動(dòng)越界
     */
    private fun fixYOffset(dyUnconsumed: Int): Int {
        var fixDy = dyUnconsumed
        if (scrollY < 0) {
            fixDy = if (dyUnconsumed < 0) {
                if (headMaxHeight >= -(dyUnconsumed + scrollY)) {
                    dyUnconsumed
                } else {
                    0
                }
            } else {
                if (dyUnconsumed + scrollY <= 0) {
                    dyUnconsumed
                } else {
                    -scrollY
                }
            }
        } else if (scrollY > 0) {
            fixDy = if (dyUnconsumed > 0) {
                if (bottomMaxHeight >= scrollY + dyUnconsumed) {
                    dyUnconsumed
                } else {
                    0
                }
            } else {
                if (scrollY + dyUnconsumed >= 0) {
                    dyUnconsumed
                } else {
                    -scrollY
                }
            }
        }
        return fixDy
    }

}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容