【Android 笔记】ViewDragHelper

Author Avatar
vecrates 12月 14, 2017

ViewDragHelper

 用于实现视图的拖揣、滑动等功能的类。

Demo

 实现一个有侧滑菜单功能 ViewGroup, ViewGroup 当中包含 MenuView 和 MainView 两个子View。这里实现往右拖揣 MainView 出现 MenuView, 往左拖揣 MenuView 和 MainView 隐藏 MenuView。

步骤
  • 初始化
    ViewDragHelper.create(ViewGroup forParent, float sensitivity, Callback callback)
    参数1 ViewGroup
    参数2 拖动 View 的灵敏度,0~1范围,1最灵敏
    参数3 需要实现的 callback
  • 将拦截事件及触摸事件传递给 ViewDragHelper
  • 重写 computeScroll 保证滑动效果
  • 实现 Callback 中的部分方法
    这里实现
    tryCaptureView(),决定哪些 View 可以拖揣
    clampViewPositionHorizontal(),返回 View 的 left 坐标以实现水平滑动效果
    onViewReleased(),释放 View 时调用
    onViewPositionChanged(),View 位置变化时使用。在我的测试中只要在tryCaptureView 中允许某 View 可以被拖揣,则拖揣该 View 时就会调用此方法,不管 View 是否有位置变化。
效果

5892091-e46e254e9ca929ed

代码
/**
 * @author Vecrates.
 * @describe
 */
public class ViewDragHelperTest extends FrameLayout{

    private ViewDragHelper mViewDragHelper;
    private View mMenuView;
    private View mMainView;

    public ViewDragHelperTest(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();

    }

    public ViewDragHelperTest(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mViewDragHelper = ViewDragHelper.create(this, 1f, new ViewDragCallBack());
        mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mMenuView = getChildAt(0);
        mMainView = getChildAt(1);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        return mViewDragHelper.shouldInterceptTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mViewDragHelper.processTouchEvent(event);
        return true;
    }

    private class ViewDragCallBack extends ViewDragHelper.Callback{

        /**
         * 确定哪一个view可以被拖揣
         * @param child
         * @param pointerId
         * @return
         */
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return true;
        }

        /**
         * 控制水平方向的移动
         * @param child
         * @param left     控件的left
         * @param dx 水平距离
         * @return
         */
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            if(child == mMainView) {
                //主页面不可以往左侧拉,能往右侧拉50%
                if(left < getPaddingLeft()) {
                    left = getPaddingLeft();
                }else if(left > getWidth() * 0.5f) {
                    left = (int) (getWidth() * 0.5f);
                }
            } else if(child == mMenuView) {
                //菜单可以往左侧拉不可以往右侧拉
                if(left > getPaddingLeft()) {
                    left = getPaddingLeft();
                }
            }
            return left;
        }

        /**
         * 释放view时
         * @param releasedChild
         * @param xvey
         * @param yvel
         */
        @Override
        public void onViewReleased(View releasedChild, float xvey, float yvel) {
            if(mMainView.getLeft() < getWidth() * 0.5f) {
                mViewDragHelper.smoothSlideViewTo(mMainView, getPaddingLeft(), getPaddingTop());
                ViewCompat.postInvalidateOnAnimation(ViewDragHelperTest.this);
            } else {
                mViewDragHelper.smoothSlideViewTo(mMainView, (int) (getWidth() * 0.5f), getPaddingTop());
                ViewCompat.postInvalidateOnAnimation(ViewDragHelperTest.this);
            }
        }

        /**
         * view位置变化时调用
         * 亲测只要允许拖揣该view,在拖揣时就会调用此方法
         * @param changedView
         * @param left
         * @param top
         * @param dx
         * @param dy
         */
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            //这里实现拖动菜单向左可以把主页面拉回来
            if(changedView == mMenuView && left < getPaddingLeft()) {
                mViewDragHelper.smoothSlideViewTo(mMainView, getPaddingLeft(), getPaddingTop());
                mMenuView.layout(getPaddingLeft(), getPaddingTop(), mMenuView.getWidth(), mMenuView.getHeight());
                ViewCompat.postInvalidateOnAnimation(ViewDragHelperTest.this);
            }
        }

    }

    /**
     * view onDraw时调用此方法
     */
    @Override
    public void computeScroll() {
        if (mViewDragHelper.continueSettling(true)) {
            //滚动未停止时刷新界面,保证滑动效果
            ViewCompat.postInvalidateOnAnimation(ViewDragHelperTest.this);
        }
    }

}

补充:
tryCaptureView(View,int) ,传递当前触摸的子View实例,如果当前的子View需要进行拖拽移动返回true
clampViewPositionHorizontal,决定拖拽的View在水平方向上面能移动到的位置
clampViewPositionVertical,决定拖拽的View在垂直方向上面能移动到的位置
getViewHorizontalDragRange ,返回一个大于0的数,然后才会在水平方向移动(如果 ViewGroup 中有 Button,并且onClickable=true)
getViewVerticalDragRange,返回一个大于0的数,然后才会在垂直方向移动(如果 ViewGroup 中有 Button,并且onClickable=true)
onViewDragStateChanged,拖拽状态发生变化回调
onViewPositionChanged,当拖拽的View的位置发生变化的时候回调(特指capturedview)
onViewCaptured,捕获captureview的时候回调
onViewReleased,当拖拽的View手指释放的时候回调
onEdgeTouched,当触摸屏幕边界的时候回调
onEdgeLock,是否锁住边界
onEdgeDrageStarted,在边缘滑动的时候可以设置滑动另一个子View跟着滑动