RecyclerView吸顶终极方案Adsorbent

吸顶终极方案github地址: Adsorbent

1. Single RecyclerView:简单模式
【利用RecyclerView.OnScrollListener监听滑动位置,吸顶View被 ViewHolder和Activity复用】

2. Double RecyclerView:RecyclerView嵌套RecyclerView
【事件分发,吸顶View是个单独ViewHolder,无须做其他处理】

3. Viewpager RecyclerView:RecyclerView嵌套ViewPager(其中包含的页面内容是RecyclerView)
【事件分发,吸顶View是个单独ViewHolder,无须做其他处理】

1.onScrolled机制

abstract class SingleAdsorbentListener : RecyclerView.OnScrollListener(){

    /** 获取被吸顶ViewGroup*/
    abstract fun getUiViewGroup():ViewGroup
    /** 获取吸顶View*/
    abstract fun getPinView(): View
    /** 获取吸顶View在RecyclerView中的位置*/
    abstract fun getPinViewPosition():Int
    /** 吸顶的时候 停止滚动并定位在吸顶位置*/
    fun stopWhenAdsorbent():Boolean = true

    fun getPinViewLayoutParam():ViewGroup.LayoutParams =
        ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT)

    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        val manager = recyclerView.layoutManager
        if(manager is LinearLayoutManager) {
            val first = manager.findFirstVisibleItemPosition()
            val last = manager.findLastVisibleItemPosition()
            //val down = recyclerView.canScrollVertically(1)
            //val up = recyclerView.canScrollVertically(-1)
            //Log.e("xx", "first=$first, vertically down=$down, up=$up, dy=$dy")
            val position = getPinViewPosition()
            if (first >= position && dy > 0) {
                addPin2Ui(recyclerView,position)
            } else if (position in (first + 1)..last) {
                addPin2ViewHolder(recyclerView,position-first)
            }
        }
    }

    /** 吸顶*/
    private fun addPin2Ui(recyclerView: RecyclerView,position :Int){
        val uiView = getUiViewGroup()
        val pinView = getPinView()
        if(uiView.indexOfChild(pinView) < 0){
        /** 吸顶的时候 停靠在吸顶viewholder位置,符合吸顶习惯*/
            if(stopWhenAdsorbent()) {
                recyclerView.stopScroll()
                recyclerView.scrollToPosition(position)
            }
            val p = pinView.parent
            if(p is ViewGroup){
                p.removeView(pinView)
            }
            uiView.addView(pinView, getPinViewLayoutParam())
        }
    }

    /** 恢复或添加 到ViewHolder*/
    private fun addPin2ViewHolder(recyclerView: RecyclerView,childPosition:Int){
        val uiView = getUiViewGroup()
        val pinView = getPinView()
        val holderView = recyclerView.getChildAt(childPosition)
        if(uiView.indexOfChild(pinView) > 0){
            uiView.removeView(pinView)
        }
        if(holderView is ViewGroup && holderView.indexOfChild(pinView) < 0) {
            holderView.addView(pinView, getPinViewLayoutParam())
        }
    }
}

2.事件分发机制,支持fling

class ParentRecyclerView :RecyclerView, OnInterceptListener {
    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle)

    private var isChildTop = true
    private var startdy = 0f
    private var startdx = 0f
    private var velocity : VelocityTracker? = null
    private var velocityY = 0
    private var isMoveY = false
    private var isSelfTouch = true

    /** true 开启滑动冲突处理*/
    var enableConflict = true
    /** 开启快速滚动parent带动child联动效果(默认false)*/
    var enableParentChain = false
    /** 开启快速滚动child带动parent联动效果*/
    var enableChildChain = true

    init {
        addOnScrollListener(object :OnScrollListener(){
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                /** 滚动停止且到了底部,快速滑动事件下发给child view*/
                if(enableParentChain && SCROLL_STATE_IDLE == newState && !canScrollVertically(1) && velocityY != 0){
                    val manager = layoutManager
                    if(manager is LinearLayoutManager){
                        val first = manager.findFirstVisibleItemPosition()
                        val total = manager.itemCount - 1
                        manager.getChildAt(total - first)?.let {
                            searchViewGroup(recyclerView,it)
                        }
                    }
                }
            }

            /** 采用递归算法遍历*/
            fun searchViewGroup(parent :ViewGroup,child : View):Boolean{
                if(flingChild(parent,child)){
                    return true
                }
                if(child is ViewGroup) {
                    for (i in 0 until child.childCount) {
                        val subChild = child.getChildAt(i)
                        if(subChild is ViewGroup && searchViewGroup(parent,subChild)){
                            return true
                        }
                    }
                }
                return false
            }

            /** 确保child的屏幕坐标在parent内,主要适配ViewPager*/
            fun flingChild(parent :ViewGroup,child : View):Boolean{
                val locChild = IntArray(2)
                val locParent = IntArray(2)
                parent.getLocationOnScreen(locParent)
                child.getLocationOnScreen(locChild)
                val childX = locChild[0]
                val parentX = locParent[0]
                if (childX >= parentX && childX< (parentX+parent.measuredWidth) && child is RecyclerView) {
                    child.fling(0, velocityY/2)
                    return true
                }
                return false
            }
        })
    }

    override fun onTopChild(isTop: Boolean) {
        isChildTop = isTop
    }

    override fun onScrollChain() {
        /** child带动parent联动效果,快速滑动事件下发给self view*/
        if(enableChildChain && isChildTop ) {
            fling(0,velocityY/2)
        }
    }

    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
        if(enableConflict){
            dispatchConflictTouchEvent(ev)
        }
        return  super.dispatchTouchEvent(ev)
    }

    private fun dispatchConflictTouchEvent(ev: MotionEvent){
        //Log.e("xx","dispatchConfict action=${ev.action},dy=${ev.y}")
        velocity?:{
            velocity = VelocityTracker.obtain()
        }.invoke()
        velocity?.addMovement(ev)
        when(ev.action){
            MotionEvent.ACTION_DOWN ->{
                startdx = ev.x
                startdy = ev.y
                velocityY = 0
                isMoveY = false
            }
            MotionEvent.ACTION_MOVE ->{
                conflictTouchEvent(ev)
            }
            MotionEvent.ACTION_UP ->{
                isSelfTouch = true
                velocity?.let {
                    it.computeCurrentVelocity(1000, maxFlingVelocity.toFloat())
                    velocityY = -it.yVelocity.toInt()
                    velocity?.recycle()
                }
                velocity = null
            }
        }
    }


    private fun conflictTouchEvent(ev: MotionEvent){
        /** 纵向滑动处理,横向滑动过滤*/
        if(isMoveY || Math.abs(ev.y-startdy) > Math.abs(ev.x-startdx)){
            isMoveY = true
            /** true 在底部*/
            val isBottom = !canScrollVertically(1)
            /** true向上滑动*/
            val directUp = (ev.y - startdy) < 0
            //Log.e("xx","isBootom=$isBottom,directUp=$directUp,ischildTop=$isChildTop,y=${ev.y}")
            if(isBottom){
                if(isChildTop && !directUp){
                    dispatchSelfTouch(ev)
                }else{
                    dispatchChildTouch(ev)
                }
            }else{
                dispatchSelfTouch(ev)
            }
        }
    }

    /** 给selft view分发*/
    private fun dispatchSelfTouch(ev: MotionEvent){
        if(!isSelfTouch){
            isSelfTouch = true
            dispatchTouchEvent(obtainCancelEvent(ev.x, ev.y))
            dispatchTouchEvent(obtainDownEvent(ev.x, ev.y))
        }
    }

    /** 给child view分发*/
    private fun dispatchChildTouch(ev: MotionEvent){
        if (isSelfTouch){
            isSelfTouch = false
            dispatchTouchEvent(obtainCancelEvent(ev.x,ev.y))
            dispatchTouchEvent(obtainDownEvent(ev.x,ev.y))
            requestDisallowInterceptTouchEvent(true)
        }
    }

    private fun obtainDownEvent(dx :Float, dy :Float) :MotionEvent{
        return obtainActionEvent(MotionEvent.ACTION_DOWN,dx,dy)
    }

    private fun obtainCancelEvent(dx :Float, dy :Float) :MotionEvent{
        return obtainActionEvent(MotionEvent.ACTION_CANCEL,dx,dy)
    }

    private fun obtainActionEvent(action :Int,dx :Float, dy :Float) :MotionEvent{
        val now = SystemClock.uptimeMillis()
        return MotionEvent.obtain(now,now,action,dx,dy,0)
    }
}

 

你可能感兴趣的:(Android)