Android 自定义可以拖动并自动贴边的viewgroup

158 阅读2分钟

Android 自定义可以拖动并自动贴边的viewgroup

class DraggableViewGroup @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
    FrameLayout(context, attrs, defStyle) {
    private var mViewDragHelper: ViewDragHelper? = null
    private var mCurrentTop = 0
    private var mCurrentLeft = 0

    init {
        mViewDragHelper = ViewDragHelper.create(this, 1.0f, object : ViewDragHelper.Callback() {
            /**
             * 是否允许view的拖动功能,返回true是允许拖动,返回false是不允许拖动
             */
            override fun tryCaptureView(child: View, pointerId: Int): Boolean {
                return true
            }

            /**
             * 控制垂直方向的拖动位移,如果不重写此方法默认是不允许垂直运动的,按照下面重写方法后可以允许垂直方向的拖动
             */
            override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int {
                // 最小 y 坐标值不能小于 topBound
                val topBound = paddingTop
                // 最大 y 坐标值不能大于 bottomBound
                val bottomBound = height - child.height - paddingBottom
                mCurrentTop = top.coerceAtLeast(topBound).coerceAtMost(bottomBound)
                return mCurrentTop
            }

            /**
             * 控制横向方向的拖动位移,如果不重写此方法默认是不允许横向运动的,按照下面重写方法后可以允许横向方向的拖动
             */
            override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int {
                // 最小x坐标值不能小于leftBound
                val leftBound = paddingLeft
                // 最大x坐标值不能大于rightBound
                val rightBound = width - child.width - paddingRight
                mCurrentLeft = left.coerceAtLeast(leftBound).coerceAtMost(rightBound)
                return mCurrentLeft
            }

            override fun getViewHorizontalDragRange(child: View): Int {
                return measuredWidth - child.measuredWidth
            }

            override fun getViewVerticalDragRange(child: View): Int {
                return measuredHeight - child.measuredHeight
            }

            override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) {
                super.onViewReleased(releasedChild, xvel, yvel)
                val childWidth = releasedChild.width
                val parentWidth = width
                val leftBound = paddingLeft // 左边缘
                val rightBound = width - releasedChild.width - paddingRight // 右边缘
                // 方块的中点超过 ViewGroup 的中点时,滑动到左边缘,否则滑动到右边缘
                if (childWidth / 2 + mCurrentLeft < parentWidth / 2) {
                    mViewDragHelper?.settleCapturedViewAt(leftBound, mCurrentTop)
                } else {
                    mViewDragHelper?.settleCapturedViewAt(rightBound, mCurrentTop)
                }
                invalidate()
            }
        })
    }

    override fun computeScroll() {
        super.computeScroll()
        if (mViewDragHelper != null && mViewDragHelper?.continueSettling(true) == true) {
            invalidate()
        }
    }

    /**
     * 注意,你需要重写onInterceptTouchEvent方法并且将触摸拦截交予ViewDragHelper的shouldInterceptTouchEvent,使其可以重新分配触控事件
     */
    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        if (isTouchView(ev.x, ev.y, getChildAt(0))) {
            val action = MotionEventCompat.getActionMasked(ev)
            if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
                mViewDragHelper?.cancel()
                return false
            }
            return mViewDragHelper?.shouldInterceptTouchEvent(ev) ?: super.onInterceptTouchEvent(ev)
        } else {
            return super.onInterceptTouchEvent(ev)
        }
    }

    /**
     * 注意,你需要重写onTouchEvent,并且将mViewDragHelper的processTouchEvent实现,使其可以实现拖动子view的效果
     */
    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (isTouchView(event.x, event.y, getChildAt(0))) {
            mViewDragHelper?.processTouchEvent(event)
            return true
        } else {
            return super.onTouchEvent(event)
        }
    }

    /**
     * 是否触摸到 View
     */
    private fun isTouchView(downX: Float, downY: Float, view: View): Boolean {
        return isTouchView(downX, downY, view.x, view.x + view.width, view.y, view.y + view.height)
    }

    /**
     * 是否触摸到 View
     */
    private fun isTouchView(downX: Float, downY: Float, left: Float, right: Float, top: Float, bottom: Float): Boolean {
        return downX > left && downX < right && downY > top && downY < bottom
    }
}