Android-CoordinatorLayout.……

利用NestedScrollingParent2实现Recycl

2020-08-03  本文已影响0人  卖炭少年炭治郎
/**
 * @author : zyl
 * //感觉这种方式 虽然能实现下啦刷新 上拉加载 但NestedScrollingParent2 这套接口感觉主要是用于嵌套滚动
 */
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)
            }
        }

    /**
     * 是否能够触发加载更多的回调
     */
    private val canLoadMore = AtomicBoolean(true)

    private var headHeight = 0

    private var footHeight = 0

    /**
     * 阻力系数
     */
    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

    /**
     * 刷新状态变化通知 方便外部头部view 做状态变化
     */
    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()
    }

    /**
     * 布局加载完毕了 此时找到加需要刷新头的那个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
        }
    }

    /**
     * 当是刷新状态时,不允许用户操作
     */
    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)
    }

    /**
     * 重置列表刷新加载状态
     */
    fun onRefreshDone() {
        if (scrollY > 0) {
            loadMoreState = LoadState.DONE
        } else {
            refreshState = RefreshState.REFRESHING_COMPLETE
        }
        overScroller.startScroll(0, scrollY, 0, -scrollY, SCROLL_DEFAULT_DURATION)
        postInvalidateOnAnimation()
    }


    /**
     * 设置是否触发加载更多 if true 触发
     */
    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) {
        //此方法 我的理解 是滑动之前 需要问下父空间是否需要处理 目前没有作为子空间的场景 所以不需要处理
        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) {
            //真正处理滑动事件
            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")
            //触发刷新回调
            refreshState = RefreshState.REFRESHING
            //滚动到指定高度
            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
            //底部滑动到指定位置
            overScroller.startScroll(
                0,
                scrollY,
                0,
                -(scrollY - footHeight),
                SCROLL_DEFAULT_DURATION
            )
            postInvalidateOnAnimation()
            refreshListener?.loadMore()
        } else {
            //不触发刷新 或是加载
            if (scrollY > 0) {
                loadMoreState = LoadState.NONE
            } else {
                refreshState = RefreshState.NONE
            }
            overScroller.startScroll(0, scrollY, 0, -scrollY, SCROLL_DEFAULT_DURATION)
            postInvalidateOnAnimation()
        }
        parentHelper.onStopNestedScroll(target, type)
    }

    /**
     * 修正滑动位置 防止滑动越界
     */
    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
    }

}
上一篇下一篇

猜你喜欢

热点阅读