一.拖拽滑動(dòng)
其中拖住滑動(dòng)是通過dragTo方法來實(shí)現(xiàn)的
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);
ViewCompat.offsetLeftAndRight(mCapturedView, clampedX - oldLeft);
}
if (dy != 0) {
clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy);
ViewCompat.offsetTopAndBottom(mCapturedView, clampedY - oldTop);
}
if (dx != 0 || dy != 0) {
final int clampedDx = clampedX - oldLeft;
final int clampedDy = clampedY - oldTop;
mCallback.onViewPositionChanged(mCapturedView, clampedX, clampedY,
clampedDx, clampedDy);
}
}
可以看到最終是通過ViewCompat.offsetLeftAndRight()和ViewCompat.offsetTopAndBottom()來實(shí)現(xiàn)左右、上下拖動(dòng)的。
二.自動(dòng)滑動(dòng)
ViewDragHelper中有兩種方式可以實(shí)現(xiàn)子View自動(dòng)滑動(dòng)到某個(gè)位置,分別是
public boolean smoothSlideViewTo(@NonNull View child, int finalLeft, int finalTop) {
**********
}
public boolean settleCapturedViewAt(int finalLeft, int finalTop) {
**********
}
通過代碼可以看到它們都會(huì)調(diào)用forceSettleCapturedViewAt()方法,代碼如下
private boolean forceSettleCapturedViewAt(int finalLeft, int finalTop, int xvel, int yvel) {
final int startLeft = mCapturedView.getLeft();
final int startTop = mCapturedView.getTop();
final int dx = finalLeft - startLeft;
final int dy = finalTop - startTop;
if (dx == 0 && dy == 0) {
// Nothing to do. Send callbacks, be done.
mScroller.abortAnimation();
setDragState(STATE_IDLE);
return false;
}
final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel);
mScroller.startScroll(startLeft, startTop, dx, dy, duration);
setDragState(STATE_SETTLING);
return true;
}
可以看到最終滑動(dòng)是通過OverScroller來實(shí)現(xiàn)的,我們知道Scroller是需配合在View的computeScroll()方法中不斷調(diào)用invalidate(),從而不斷重新繪制視圖位置達(dá)到視圖自動(dòng)滑動(dòng)的效果的,所以我們使用ViewDragHelper時(shí)需要重寫自定義View的computeScroll()方法,如
@Override
public void computeScroll() {
super.computeScroll();
if (mDragHelper != null && mDragHelper.continueSettling(true)) {
invalidate();
}
}
ViewDragHelper中還有一個(gè)方法可以實(shí)現(xiàn)在拖拽滑動(dòng)松手后進(jìn)行慣性滑動(dòng),最終停留位置由松手時(shí)的位置和速度來決定,它和上面的settleCapturedViewAt()都必須在mReleaseInProgress為true的時(shí)候才能生效,也就是必須在Callback的onViewReleased()方法中調(diào)用。它也是通過調(diào)用OverScroller,所以使用時(shí)同樣需重寫自定義View的computeScroll()方法。
public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop) {
if (!mReleaseInProgress) {
throw new IllegalStateException("Cannot flingCapturedView outside of a call to "
+ "Callback#onViewReleased");
}
mScroller.fling(mCapturedView.getLeft(), mCapturedView.getTop(),
(int) mVelocityTracker.getXVelocity(mActivePointerId),
(int) mVelocityTracker.getYVelocity(mActivePointerId),
minLeft, maxLeft, minTop, maxTop);
setDragState(STATE_SETTLING);
}
三.邊緣滑動(dòng)
/**
* Enable edge tracking for the selected edges of the parent view.
* The callback's {@link Callback#onEdgeTouched(int, int)} and
* {@link Callback#onEdgeDragStarted(int, int)} methods will only be invoked
* for edges for which edge tracking has been enabled.
*
* @param edgeFlags Combination of edge flags describing the edges to watch
* @see #EDGE_LEFT
* @see #EDGE_TOP
* @see #EDGE_RIGHT
* @see #EDGE_BOTTOM
*/
public void setEdgeTrackingEnabled(int edgeFlags) {
mTrackingEdges = edgeFlags;
}
ViewDragHelper通過setEdgeTrackingEnabled()方法設(shè)置要監(jiān)測(cè)哪個(gè)方向屏幕邊緣的滑動(dòng),而是否是邊緣滑動(dòng)是通過對(duì)觸摸事件中Action_Down時(shí)的位置進(jìn)行判令的
public boolean shouldInterceptTouchEvent(@NonNull MotionEvent ev) {
**********
switch (action) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
final int pointerId = ev.getPointerId(0);
saveInitialMotion(x, y, pointerId);
**********
final int edgesTouched = mInitialEdgesTouched[pointerId];
if ((edgesTouched & mTrackingEdges) != 0) {
mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
}
break;
}
case MotionEvent.ACTION_POINTER_DOWN: {
final int pointerId = ev.getPointerId(actionIndex);
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
saveInitialMotion(x, y, pointerId);
// A ViewDragHelper can only manipulate one view at a time.
if (mDragState == STATE_IDLE) {
final int edgesTouched = mInitialEdgesTouched[pointerId];
if ((edgesTouched & mTrackingEdges) != 0) {
mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
}
} else if (mDragState == STATE_SETTLING) {
**********
}
break;
}
case MotionEvent.ACTION_MOVE: {
if (mInitialMotionX == null || mInitialMotionY == null) break;
// First to cross a touch slop over a draggable view wins. Also report edge drags.
final int pointerCount = ev.getPointerCount();
for (int i = 0; i < pointerCount; i++) {
final int pointerId = ev.getPointerId(i);
// If pointer is invalid then skip the ACTION_MOVE.
if (!isValidPointerForActionMove(pointerId)) continue;
final float x = ev.getX(i);
final float y = ev.getY(i);
final float dx = x - mInitialMotionX[pointerId];
final float dy = y - mInitialMotionY[pointerId];
**********
reportNewEdgeDrags(dx, dy, pointerId);
**********
}
saveLastMotion(ev);
break;
}
**********
}
return mDragState == STATE_DRAGGING;
}
public void processTouchEvent(@NonNull MotionEvent ev) {
**********
switch (action) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
final int pointerId = ev.getPointerId(0);
final View toCapture = findTopChildUnder((int) x, (int) y);
saveInitialMotion(x, y, pointerId);
**********
final int edgesTouched = mInitialEdgesTouched[pointerId];
if ((edgesTouched & mTrackingEdges) != 0) {
mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
}
break;
}
case MotionEvent.ACTION_POINTER_DOWN: {
final int pointerId = ev.getPointerId(actionIndex);
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
saveInitialMotion(x, y, pointerId);
// A ViewDragHelper can only manipulate one view at a time.
if (mDragState == STATE_IDLE) {
**********
final int edgesTouched = mInitialEdgesTouched[pointerId];
if ((edgesTouched & mTrackingEdges) != 0) {
mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
}
} else if (isCapturedViewUnder((int) x, (int) y)) {
**********
}
break;
}
case MotionEvent.ACTION_MOVE: {
if (mDragState == STATE_DRAGGING) {
**********
} else {
// Check to see if any pointer is now over a draggable view.
final int pointerCount = ev.getPointerCount();
for (int i = 0; i < pointerCount; i++) {
final int pointerId = ev.getPointerId(i);
// If pointer is invalid then skip the ACTION_MOVE.
if (!isValidPointerForActionMove(pointerId)) continue;
final float x = ev.getX(i);
final float y = ev.getY(i);
final float dx = x - mInitialMotionX[pointerId];
final float dy = y - mInitialMotionY[pointerId];
reportNewEdgeDrags(dx, dy, pointerId);
**********
}
saveLastMotion(ev);
}
break;
}
**********
}
}
其中saveInitialMotion()方法如下
private void saveInitialMotion(float x, float y, int pointerId) {
ensureMotionHistorySizeForId(pointerId);
mInitialMotionX[pointerId] = mLastMotionX[pointerId] = x;
mInitialMotionY[pointerId] = mLastMotionY[pointerId] = y;
mInitialEdgesTouched[pointerId] = getEdgesTouched((int) x, (int) y);
mPointersDown |= 1 << pointerId;
}
private int getEdgesTouched(int x, int y) {
int result = 0;
if (x < mParentView.getLeft() + mEdgeSize) result |= EDGE_LEFT;
if (y < mParentView.getTop() + mEdgeSize) result |= EDGE_TOP;
if (x > mParentView.getRight() - mEdgeSize) result |= EDGE_RIGHT;
if (y > mParentView.getBottom() - mEdgeSize) result |= EDGE_BOTTOM;
return result;
}
可以看出ACTION_DOWN時(shí)會(huì)記錄觸摸點(diǎn)的x,y坐標(biāo)位置,手指id,以及當(dāng)前手指是否是觸摸在屏幕某一個(gè)邊緣,具體是通過當(dāng)前觸摸點(diǎn)x或y是否在邊緣一個(gè)很小的距離范圍來來判定。如果是邊緣觸摸并且方向和setEdgeTrackingEnabled()方法設(shè)置要監(jiān)測(cè)的方向一致,則會(huì)回調(diào)Callback的onEdgeTouched()方法。
通過上面的ACTION_MOVE中代碼看到手指滑動(dòng)過程中,會(huì)調(diào)用reportNewEdgeDrags()方法來對(duì)邊緣滑動(dòng)做進(jìn)一步的處理
private void reportNewEdgeDrags(float dx, float dy, int pointerId) {
int dragsStarted = 0;
if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_LEFT)) {
dragsStarted |= EDGE_LEFT;
}
if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_TOP)) {
dragsStarted |= EDGE_TOP;
}
if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_RIGHT)) {
dragsStarted |= EDGE_RIGHT;
}
if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_BOTTOM)) {
dragsStarted |= EDGE_BOTTOM;
}
if (dragsStarted != 0) {
mEdgeDragsInProgress[pointerId] |= dragsStarted;
mCallback.onEdgeDragStarted(dragsStarted, pointerId);
}
}
private boolean checkNewEdgeDrag(float delta, float odelta, int pointerId, int edge) {
final float absDelta = Math.abs(delta);
final float absODelta = Math.abs(odelta);
if ((mInitialEdgesTouched[pointerId] & edge) != edge || (mTrackingEdges & edge) == 0
|| (mEdgeDragsLocked[pointerId] & edge) == edge
|| (mEdgeDragsInProgress[pointerId] & edge) == edge
|| (absDelta <= mTouchSlop && absODelta <= mTouchSlop)) {
return false;
}
if (absDelta < absODelta * 0.5f && mCallback.onEdgeLock(edge)) {
mEdgeDragsLocked[pointerId] |= edge;
return false;
}
return (mEdgeDragsInProgress[pointerId] & edge) == 0 && absDelta > mTouchSlop;
}
可以看到會(huì)通過mInitialEdgesTouched中記錄的該手指最開始Action_down的時(shí)候是否是邊緣觸摸,要監(jiān)測(cè)的是否是該方向,該邊緣方向是否是鎖定狀態(tài),以及是否是Action_down后首次該邊緣方向的ACTION_MOVE觸發(fā)多個(gè)條件來進(jìn)行判定,如果判定成功則會(huì)調(diào)用Callback的onEdgeDragStarted()方法。
其中某個(gè)邊緣方向是否是鎖定狀態(tài)通過Callback的onEdgeLock()方法來獲取,我們可以在這個(gè)回調(diào)中動(dòng)態(tài)改變返回值來動(dòng)態(tài)設(shè)置是否可以觸發(fā)onEdgeDragStarted()方法,而真正要實(shí)現(xiàn)邊緣觸摸的時(shí)候可以拖拽出某個(gè)view(比如某一個(gè)下拉菜單)還需要在onEdgeDragStarted()中做其他處理,如
ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
**********
@Override
public void onEdgeTouched(int edgeFlags, int pointerId) {
super.onEdgeTouched(edgeFlags, pointerId);
}
@Override
public boolean onEdgeLock(int edgeFlags) {
return super.onEdgeLock(edgeFlags);
}
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
mDragHelper.captureChildView(mBottomMenu, pointerId);
}
**********
};
參考鏈接:
ViewDragHelper 的基本使用(一)
ViewDragHelper原理與使用
ViewDragHelper源碼解析
Android ViewDragHelper源碼解析