使用ViewDragHelper实现平滑拖动动画

2,315 阅读3分钟

在之前的文章中,分别介绍了使用layout(left, top, right, bottom)layoutParamsoffsetLeftAndRight/offsetTopAndBottomscrollBy/scrollToScroller方式实现移动效果,本篇文章,将讲解一个更高阶的实现移动效果的方式---ViewDragHelper

1、ViewDragHelper使用方法介绍

ViewDragHelper使用十分简单,基本上就是固定的使用套路。因为ViewDragHelper是一个拖动、移动的辅助类,一般情况下,我们会使用在自定义view or viewgroup之中。

  • 使用ViewDragHelper.create(this, object : ViewDragHelper.Callback() {}方法创建一个ViewDragHelper对象
val itemDragHelper = ViewDragHelper.create(this, object : ViewDragHelper.Callback() {
...//do somethings
}
  • View or ViewGroup中的事件拦截使用viewDragHelper的方法进行*事件接管*
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
     return mItemViewDragHelper?.shouldInterceptTouchEvent(ev) == true
 }

 override fun onTouchEvent(event: MotionEvent): Boolean {
     if (event.action == MotionEvent.ACTION_DOWN) {
         this.performClick()
     }
     mItemViewDragHelper?.processTouchEvent(event)
     return true
 }
  • 重写computeScroll方法,在其中处理刷新
override fun computeScroll() {
    if (mItemViewDragHelper?.continueSettling(true) == true) {
        ViewCompat.postInvalidateOnAnimation(this)
    }
}
  • 实现ViewDragHelper.Callback的方法,进行逻辑处理操作。

下面介绍下ViewDragHelper.Callback常用的方法

//用来判断需要拦截那个view对象用来拖动
override fun tryCaptureView(child: View, pointerId: Int): Boolean 

//用来控制拖动的边界,默认情况下,left和top就是系统默认计算的边界值
override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int 
override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int 

//手指释放时候的反馈
override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) 

//触摸边界时候回调
override fun onEdgeDragStarted(edgeFlags: Int, pointerId: Int)

2、实现一个简单的拖动移动效果

在这里插入图片描述 上面三个分别是普通拖动(无回弹效果)、推动松手后回弹、在布局边缘拖动效果。

<org.fireking.basic.animator.basic.widget.SimpleViewDragHelperUseView2
   android:layout_width="match_parent"
   android:layout_height="0dp"
   android:layout_weight="2"
   android:gravity="center"
   android:orientation="vertical">

   <TextView
       android:id="@+id/tvTouchV1"
       android:layout_width="180dp"
       android:layout_height="60dp"
       android:background="#80ff00ff"
       android:gravity="center"
       android:text="Touch"
       android:textColor="@android:color/white"
       android:textSize="12dp" />

   <TextView
       android:id="@+id/tvTouchV2"
       android:layout_width="180dp"
       android:layout_height="60dp"
       android:background="#80FF8877"
       android:gravity="center"
       android:text="Touch"
       android:textColor="@android:color/white"
       android:textSize="12dp" />

   <TextView
       android:id="@+id/tvTouchV3"
       android:layout_width="180dp"
       android:layout_height="60dp"
       android:background="#80334455"
       android:gravity="center"
       android:text="我是在布局边界滑动触发"
       android:textColor="@android:color/white"
       android:textSize="12dp" />

</org.fireking.basic.animator.basic.widget.SimpleViewDragHelperUseView2>
class SimpleViewDragHelperUseView2 @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayoutCompat(context, attrs, defStyleAttr) {

    private var mTouch1: TextView? = null
    private var mTouch2: TextView? = null
    private var mTouch3: TextView? = null
    private var mItemViewDragHelper: ViewDragHelper? = null

    private var mTouch1OriginLeft: Int = 0
    private var mTouch1OriginTop: Int = 0
    private var mTouch2OriginLeft: Int = 0
    private var mTouch2OriginTop: Int = 0
    private var mTouch3OriginLeft: Int = 0
    private var mTouch3OriginTop: Int = 0

    init {

        isFocusableInTouchMode = true

        isMotionEventSplittingEnabled = true

        mItemViewDragHelper =
            ViewDragHelper.create(this, object : ViewDragHelper.Callback() {

                override fun getViewHorizontalDragRange(child: View): Int {
                    return mItemViewDragHelper?.touchSlop ?: 0
                }

                override fun getViewVerticalDragRange(child: View): Int {
                    return mItemViewDragHelper?.touchSlop ?: 0
                }

                /**
                 * 用来判断要支持拖动的view
                 */
                override fun tryCaptureView(child: View, pointerId: Int): Boolean {
                    return child == mTouch1 || child == mTouch2
                }

                /**
                 * 表示支持在水平方向上移动
                 */
                override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int {
                    return left
                }

                /**
                 * 表示支持在垂直方向上移动
                 */
                override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int {
                    return top
                }

                /**
                 * 手指释放时候的反馈
                 */
                override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) {
                    releasedChild.let {
                       if (it == mTouch2) {
                            mItemViewDragHelper?.smoothSlideViewTo(
                                it,
                                mTouch2OriginLeft,
                                mTouch2OriginTop
                            )
                        } else if (it == mTouch3) {
                            mItemViewDragHelper?.smoothSlideViewTo(
                                it,
                                mTouch3OriginLeft,
                                mTouch3OriginTop
                            )
                        }
                        ViewCompat.postInvalidateOnAnimation(this@SimpleViewDragHelperUseView2)
                    }
                }

                /**
                 * 触摸边界时候回调
                 */
                override fun onEdgeDragStarted(edgeFlags: Int, pointerId: Int) {
                   //边界拖动触发后,设置拖动事件捕获touch3
                    mTouch3?.let {
                        mItemViewDragHelper?.captureChildView(it, pointerId)
                    }
                }

            })
            //设置边界拖动可用
        mItemViewDragHelper?.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT)
    }

    override fun onFinishInflate() {
        super.onFinishInflate()
        mTouch1 = findViewById(R.id.tvTouchV1)
        mTouch2 = findViewById(R.id.tvTouchV2)
        mTouch3 = findViewById(R.id.tvTouchV3)
    }

    override fun computeScroll() {
        if (mItemViewDragHelper?.continueSettling(true) == true) {
            ViewCompat.postInvalidateOnAnimation(this)
        }
    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        super.onLayout(changed, l, t, r, b)
        mTouch1OriginLeft = mTouch1?.left ?: 0
        mTouch1OriginTop = mTouch1?.top ?: 0
        mTouch2OriginLeft = mTouch2?.left ?: 0
        mTouch2OriginTop = mTouch2?.top ?: 0
        mTouch3OriginLeft = mTouch3?.left ?: 0
        mTouch3OriginTop = mTouch3?.top ?: 0
    }

    private var startX = 0F
    private var startY = 0F

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        var result: Boolean = mItemViewDragHelper?.shouldInterceptTouchEvent(ev) == true
        val curX = ev.x
        val curY = ev.y
        when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                startX = curX
                startY = curY
            }
            MotionEvent.ACTION_MOVE -> {
                if (abs(curX - startX) > mItemViewDragHelper?.touchSlop ?: 0 || abs(curY - startY) > mItemViewDragHelper?.touchSlop ?: 0) {
                    result = true
                }
            }
        }
        return result
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (event.action == MotionEvent.ACTION_DOWN) {
            this.performClick()
        }
        mItemViewDragHelper?.processTouchEvent(event)
        return true
    }

}