它是为了自定义ViewGroup而添加的一个工具类。它内部有一系列方法,这些方法方便我们拖拽和重新定位子View的位置。在位于V4包中。
ViewDragHelper的本质是分析onInterceptTouchEvent和onTouchEvent的MotionEvent参数,然后根据分析的结果去改变一个容器中被拖动子View的位置( 通过offsetTopAndBottom(int offset)和offsetLeftAndRight(int offset)方法 ),他能在触摸的时候判断当前拖动的是哪个子View。
public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) { final ViewDragHelper helper = create(forParent, cb); helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity)); return helper; }从中可以看出:最终调用了两个参数的create(),只是将mTouchSlop的值进行了改变。而两个参数的create()很简单,只是调用了下面的构造方法:
private ViewDragHelper(Context context, ViewGroup forParent, Callback cb) { if (forParent == null) { throw new IllegalArgumentException("Parent view may not be null"); } if (cb == null) { throw new IllegalArgumentException("Callback may not be null"); } mParentView = forParent; mCallback = cb; final ViewConfiguration vc = ViewConfiguration.get(context); final float density = context.getResources().getDisplayMetrics().density;//屏幕密度 mEdgeSize = (int) (EDGE_SIZE * density + 0.5f); mTouchSlop = vc.getScaledTouchSlop();//代码一 mMaxVelocity = vc.getScaledMaximumFlingVelocity(); mMinVelocity = vc.getScaledMinimumFlingVelocity(); mScroller = ScrollerCompat.create(context, sInterpolator); }从代码一处可以看到mTouchSlop只是一个限制数字(手指按到屏幕上时会无意识的运动,该值就是为了屏蔽这种运动),当手指移动的距离大于该值时才可认为用户已经滑动,否则认为手指是静止的。因此,当该值越大时用户需要滑动的距离就越大,也就是对滑动越不敏感。这也是三个参数的create()中第二个参数的含义:当第二个参数越大,mTouchSlop越小,从而越敏感;反之同理。
//ViewDragHelper.Callback中的方法 public void onViewReleased(View releasedChild, float xvel, float yvel) { super.onViewReleased(releasedChild, xvel, yvel); helper.settleCapturedViewAt(releasedChild.getLeft(), getWidth() - releasedChild.getWidth()); invalidate();//为了启动移动动画 } @Override public void computeScroll() { super.computeScroll(); if(helper.continueSettling(true)){//如果为true,表明移动未结束 //需要继续进行移动 ViewCompat.postInvalidateOnAnimation(this); } }
public boolean onInterceptTouchEvent(MotionEvent ev) { final int action = MotionEventCompat.getActionMasked(ev); if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { mDragHelper.cancel(); return false; } return mDragHelper.shouldInterceptTouchEvent(ev); }processTouchEvent(MotionEvent):与GestureDetector类似ViewDragHelper也需要关联相应的MotionEvent,该方法就是关联MotionEvent的。它通常是在onTouchEvent()中调用。如:
public boolean onTouchEvent(MotionEvent event) { mDragHelper.processTouchEvent(event); return true; }
public View findTopChildUnder(int x, int y) { final int childCount = mParentView.getChildCount(); for (int i = childCount - 1; i >= 0; i--) { final View child = mParentView.getChildAt(mCallback.getOrderedChildIndex(i)); if (x >= child.getLeft() && x < child.getRight() && y >= child.getTop() && y < child.getBottom()) { return child; } } return null; }因此仅有一个view包含(x,y)时,该方法基本上无用;不止一个view包含时(x,y)时,该方法可以用来调整findTopChildUnder()的返回值。但是最好还是别重写。
dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy);dragTo源码如下:
private void dragTo(int left, int top, int dx, int dy) { int clampedX = left; int clampedY = top; final int oldLeft = mCapturedView.getLeft(); final int oldTop = mCapturedView.getTop(); if (dx != 0) { clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx); mCapturedView.offsetLeftAndRight(clampedX - oldLeft); }从上面可以看出,clampViewPositionHorizontal的返回值表示本次移动后view将要达到的位置。
private boolean checkTouchSlop(View child, float dx, float dy) { if (child == null) { return false; } final boolean checkHorizontal = mCallback.getViewHorizontalDragRange(child) > 0; final boolean checkVertical = mCallback.getViewVerticalDragRange(child) > 0; if (checkHorizontal && checkVertical) { return dx * dx + dy * dy > mTouchSlop * mTouchSlop; } else if (checkHorizontal) { return Math.abs(dx) > mTouchSlop; } else if (checkVertical) { return Math.abs(dy) > mTouchSlop; } return false; }当返回值不大于0时,该checkTouchSlop()返回false,也就是说当前的滑动距离不够大,ViewDragHelper便不会认为是滑动,所以没有不会有滑动效果。