拖动神器-ViewDragHelper

/ 2评 / 0

前言

以前我们想实现一个可拖动的悬浮窗,一般来说需要在ViewGroup的onTouch事件里面处理各种DOWN、MOVE、UP事件,写的很恶心,而且让代码也很难维护,好在现在有一个神器ViewDragHelper来帮我们处理这些情况,感谢Google~~~

ViewDragHelper的基础使用

ViewDragHelper的基本使用如下。首先需要覆写ViewGroup的onInterceptTouchEvent以及onTouchEvent,然后将其交给ViewDragHelper进行处理,然后在ViewDragHelper.Callback中进行各种回调处理。一般来说,需要覆写tryCaptureView,用来告诉ViewDragHelper哪一个View可以被拖动。

public class ViewDragHelperDemo extends LinearLayout {

    private ViewDragHelper mViewDragHelper;

    private ViewDragHelper.Callback mDragCallback;

    private void init() {
        initDragCallBack();
        mViewDragHelper = ViewDragHelper.create(this, 1.0f, mDragCallback);
        // 设置为可以捕获屏幕左边的滑动
        mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
    }

    private void initDragCallBack() {
        mDragCallback = new ViewDragHelper.Callback() {

            /**
             * 返回true,表示传入的View可以被拖动
             */
            @Override
            public boolean tryCaptureView(View child, int pointerId) {
                // 只允许第一个被拖动
                return child.equals(getChildAt(0));
            }
			/**
             * 传入View即将到达的位置(left),返回值为真正到达的位置
             */
            @Override
            public int clampViewPositionHorizontal(View child, int left, int dx) {
                return left;
            }

            /**
             * 传入View即将到达的位置(top),返回值为真正到达的位置
             */
            @Override
            public int clampViewPositionVertical(View child, int top, int dy) {
                return top;
            }
        };
    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
		//让ViewDragHelper决定是否拦截事件
        return mViewDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
		//表示处理Touch事件
        mViewDragHelper.processTouchEvent(event);
        return true;
    }
}

拖动效果可以查看Demo.apk

限制拖动范围

上面的代码能让ViewGroup中第一个子View任意拖动,如果我们向让其左右不能出边界,上下可以出边界,就需要改写下clampViewPositionHorizontal、clampViewPositionVertical方法,这两个方法主要是用来修正可被拖动的View即将到达的位置,通过修改这两个函数的返回值,即可限制拖动范围。

/**
 * 传入View即将到达的位置(left),返回值为真正到达的位置
 */
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
	LinearLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
	int leftBorder = getPaddingLeft() + lp.leftMargin;
	int rightBorder = getMeasuredWidth() - getPaddingRight() - child.getMeasuredWidth() - lp.rightMargin;
	return Math.min(Math.max(leftBorder, left), rightBorder);
}

/**
 * 传入View即将到达的位置(top),返回值为真正到达的位置
 */
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
	return top;
}

加入自动回到起点

首先我们需要知道什么时候用户停止了拖动,这一事件可以在ViewDragHelper.Callback的onViewReleased方法中知道,然后我们ViewDragHelper.settleCapturedViewAt设置View要回到的位置,那么View就会以当前拖动的速度,自动回到指定地点。

并且惯性滑动ViewDragHelper使用的ScrollerCompat实现,所以我们也得覆写computeScroll方法才行,大致逻辑如下(省略部分代码)

public class ViewDragHelperDemo extends LinearLayout {

    private ViewDragHelper mViewDragHelper;

    private ViewDragHelper.Callback mDragCallback;

    private void init() {
        initDragCallBack();
        mViewDragHelper = ViewDragHelper.create(this, 1.0f, mDragCallback);
        // 设置为可以捕获屏幕左边的滑动
        mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
    }

    private void initDragCallBack() {
        mDragCallback = new ViewDragHelper.Callback() {
			
			/**
             * 当手指离开以后的回调
             * 
             * @param releasedChild 子View
             * @param xvel X轴的速度
             * @param yvel Y轴的速度
             */
            @Override
            public void onViewReleased(View releasedChild, float xvel, float yvel) {
                if (releasedChild.equals(getChildAt(0))) {
                    Log.i(TAG, "xvel = " + xvel + ",yvel=" + yvel);
                    // 手指松开以后自动回到原始位置
                    mViewDragHelper.settleCapturedViewAt(100, 100);
                    invalidate();
                }
            }
        };
    }
	
	@Override
    public void computeScroll() {
        if (mViewDragHelper != null && mViewDragHelper.continueSettling(true)) {
            invalidate();
        }
    }
}

解决点击冲突

如果你将可移动的View添加一个点击事件,如果不进行特殊的处理,你会发现点击事件是无效的,所以我们需要如下覆写部分方法

mDragCallback = new ViewDragHelper.Callback() {
			
			/**
			 * 返回横向能拖动的长度,默认返回0,如果被拖动的View设置了点击事件,返回0会不响应点击事件
			 */
            @Override
            public int getViewHorizontalDragRange(View child) {
                return getMeasuredWidth() - child.getMeasuredWidth();
            }

            /**
             * 返回纵向能拖动的长度,默认返回0,如果被拖动的View设置了点击事件,返回0会不响应点击事件
             */
            @Override
            public int getViewVerticalDragRange(View child) {
                return getMeasuredHeight() - child.getMeasuredHeight();
            }
        };

添加屏幕边缘滑动监听

如果我们的View在屏幕中间,我们想让用户滑动屏幕的某一个边缘也行将其滑动,那么就需要添加屏幕边缘滑动监听。

mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);

上面的代码就是当用户在屏幕左边滑动的时候,ViewDragHelper会自动回调ViewDragHelper.Callback的onEdgeDragStarted方法,然后我们就可以进行移动处理了。

private void init() {
	initDragCallBack();
	mViewDragHelper = ViewDragHelper.create(this, 1.0f, mDragCallback);
	// 设置为可以捕获屏幕左边的滑动
	mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
}

private void initDragCallBack() {
	mDragCallback = new ViewDragHelper.Callback() {

		@Override
		public void onEdgeDragStarted(int edgeFlags, int pointerId) {
			Log.i(TAG, "onEdgeDragStarted;" + edgeFlags);
			// 当从屏幕左边开始滑动的时候,开始滑动第一个子控件
			mViewDragHelper.captureChildView(getChildAt(0), pointerId);
		}
	};
}

移动联合动画

如果需要在移动某一个View的时候,另一个view也随之进行某种移动,那么仅仅需要在View移动的时候进行监听,然后同步移动另一个即可,至于如何监听View的移动,ViewDragHelper.CallBack中也有回调。

mDragCallback = new ViewDragHelper.Callback() {

	/**
	 * 当某一个View在动的时候的回调,不管是用户手动滑动,还是使用settleCapturedViewAt或者smoothSlideViewTo,都会回调这里
	 */
	@Override
	public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
		Log.i(TAG, "left=" + left + ",top=" + top + ",dx=" + dx + ",dy=" + dy);
	}
};

其他

其实查看ViewDragHelper的源码可以发现,ViewDragHelper移动View是使用的View自带的offsetLeftAndRight以及offsetTopAndBottom,惯性移动使用的ScrollerCompat,主要是将手指的复杂处理给封装起来,便于开发者调用。

顺带贴出能让View进行位移的一些方法。

View.setLeft/Top/Right/Bottom
View.layout(l,t,r,b);
View.setTranslationX/Y

参考链接:希尔瓦娜斯女神

Demo地址:Github

  1. 夕凉说道:

    挖槽, 沙发了.

发表评论

您的电子邮箱地址不会被公开。