美文网首页Android开发@IT·互联网android开源
PullToDetailsLayout 仿京东天猫上拉加载详情

PullToDetailsLayout 仿京东天猫上拉加载详情

作者: 晓峰残月 | 来源:发表于2018-09-08 12:01 被阅读50次

    有图有真相

    20180908153637620554347.gif

    使用步骤

    1. 引入 PullToDetailsLayout
    implementation 'com.jwenfeng.library:PullToDetails:1.0.0'
    
    1. 使用 PullToDetailsLayout
    <com.jwenfeng.library.PullToDetailsLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        
        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:overScrollMode="never">
        </ScrollView>
        
        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:overScrollMode="never">
        </ScrollView>
        
    </com.jwenfeng.library.PullToDetailsLayout>
    

    只能放入两个 View 支持 ScrollView、ListView、RecyclerView 等

    1. 滚动监听
    main_pull_to_details.onPageChangeListener = object:PullToDetailsLayout.OnPageChangeListener{
        override fun onChange(status: Int) {
            Log.d("MainActivity","status:$status")
        }
    }
    

    实现原理

    1. 自定义一个Layout 继承 ViewGroup
    class PullToDetailsLayout:ViewGroup {
         // 最小滑动距离
        private var mTouchSlop = 0
        private var velocityTracker: VelocityTracker
        private var scroller: Scroller
        
        constructor(context: Context) : this(context, null)
    
        constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0)
    
        constructor(context: Context, attributeSet: AttributeSet?, styleRes: Int) : super(context, attributeSet, styleRes) {
            mTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
            velocityTracker = VelocityTracker.obtain()
            scroller = Scroller(context)
        }
    }
    
    1. 测量 Layout 的大小,设置子 View 的大小和自身一样大
    class PullToDetailsLayout:ViewGroup {
         
        ...
        
        override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
            // 获取父布局的大小
            val rootWidth = MeasureSpec.getSize(widthMeasureSpec)
            val rootHeight = MeasureSpec.getSize(heightMeasureSpec)
            // 设置子布局的大小模式
            val childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(rootWidth, MeasureSpec.EXACTLY)
            val childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(rootHeight, MeasureSpec.EXACTLY)
    
            for (i in 0 until childCount) {
                val childView = getChildAt(i)
                if (childView.visibility == View.GONE) {
                    continue
                }
                // 设置子布局和父布局的大小一致
                measureChild(childView, childWidthMeasureSpec, childHeightMeasureSpec)
            }
            setMeasuredDimension(rootWidth, rootHeight)
        }
        
    }
    
    1. 让子 View 重新布局,从上到下分布
    class PullToDetailsLayout:ViewGroup {
         
        ...
        
        override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
            var top = t
            for (i in 0 until childCount) {
                val childView = getChildAt(i)
    
                if (childView.visibility == View.GONE) {
                    continue
                }
                // 设置子布局的位置,按照顺序从上到下
                childView.layout(l, top, r, top + childView.measuredHeight)
                top += childView.measuredHeight
            }
        }
        
    }
    
    1. 触摸事件分发

    onInterceptTouchEvent 用来判断是否拦截某个事件,如果当前的 View 拦截了某个事件,那么在同一个事件序列中,此方法不会再次被调用。返回结果表示是否拦截当前事件。

    在这里我们需要判断第一页的 View 是否可以向上继续滑动,如果第一页的 View 不能继续滑动,那么自身 View 就需要拦截当前事件处理滑动;

    同理我们也需要判断第二页的 View 是否可以向下继续滑动,如不能滑动那么自身的 View 就需要拦截当前事件来处理滑动。

    class PullToDetailsLayout:ViewGroup {
        
       ...
       // 0:第一页 1:第二页
       private var status = 0
       private var lastY = 0
       
       override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
           var shouldIntercept = false
           val y = ev.y.toInt()
           val action = ev.action
           when (action) {
               MotionEvent.ACTION_DOWN -> {
                   lastY = y
               }
               MotionEvent.ACTION_MOVE -> {
                   val dy = lastY - y
    
                   if (dy > 0 && status == 0) {
                       if (!mFrontView.canScrollVertically(1)) {
                           if (dy >= mTouchSlop) {
                               shouldIntercept = true
                           }
                       }
                   } else if (dy < 0 && status == 1) {
                       if (!mBehindView.canScrollVertically(-1)) {
                           if (Math.abs(dy) >= mTouchSlop) {
                               shouldIntercept = true
                           }
                       }
                   }
               }
           }
           return shouldIntercept
       }
       
    }
    
    1. 处理滑动

    父 View 获取到滑动通过 scrollBy() 方法进行滑动,这里 dy /= 3 是增加滑动的弹性

    当手指离屏幕的时候根据滑动速度来决定是回弹到第一页还是第二页,通过 velocityTracker.yVelocity 来获取Y轴的滑动速度。

    class PullToDetailsLayout:ViewGroup {
         
        ...
        override fun onTouchEvent(ev: MotionEvent): Boolean {
            val y = ev.y.toInt()
            velocityTracker.addMovement(ev)
    
            when (ev.action) {
                MotionEvent.ACTION_MOVE -> {
                    var dy = lastY - y
                    dy /= 3
                    if (dy + scrollY < 0) {
                        dy = -(scrollY + dy + Math.abs(dy))
                    }
                    scrollBy(0, dy)
                }
                MotionEvent.ACTION_UP,
                MotionEvent.ACTION_CANCEL -> {
                    velocityTracker.computeCurrentVelocity(1000)
                    val yVelocity = velocityTracker.yVelocity
                    if (status == 0) {
                        if (yVelocity < 0 && yVelocity < -speed) {
                            smoothScroll(mFrontView.bottom);
                            status = 1;
                        } else {
                            smoothScroll(0);
                        }
                    } else {
                        if (yVelocity > 0 && yVelocity > speed) {
                            smoothScroll(0);
                            status = 0;
                        } else {
                            smoothScroll(mFrontView.bottom);
                        }
                    }
    
                }
    
            }
            lastY = y
    
            return super.onTouchEvent(ev)
        }
    
    
        //通过Scroller实现弹性滑动
        private fun smoothScroll(tartY: Int) {
            val dy = tartY - scrollY
            scroller.startScroll(scrollX, scrollY, 0, dy)
            invalidate()
        }
    
        override fun computeScroll() {
            if (scroller.computeScrollOffset()) {
                scrollTo(scroller.currX, scroller.currY)
                postInvalidate()
            }
        }
        
    }
    

    完整代码详见GitHub:PullToDetailsLayout 仿京东天猫上拉加载详情


    微信公众号

    微信公众号

    佛系开发

    相关文章

      网友评论

        本文标题:PullToDetailsLayout 仿京东天猫上拉加载详情

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