ViewDragHelper的一些总结

View设置了clickable=true不能触发移动

只要设置了clickable=true或者本身就是可被点击的,如button,那么view久无法被移动。原因很简单,就是事件被子view给消费掉了,viewGroup无法被会滴。

源码分析

public boolean shouldInterceptTouchEvent(MotionEvent ev) {
    ...
    case MotionEvent.ACTION_MOVE: {
        ...
        final boolean pastSlop = toCapture != null && checkTouchSlop(toCapture, dx, dy);
        ...
        if (pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) {
            break;
        }
        ...
    }
    ...
}

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;
    }

boolean tryCaptureViewForDrag(View toCapture, int pointerId) {
        if (toCapture == mCapturedView && mActivePointerId == pointerId) {
            // Already done!
            return true;
        }
        if (toCapture != null && mCallback.tryCaptureView(toCapture, pointerId)) {
            mActivePointerId = pointerId;
            captureChildView(toCapture, pointerId);
            return true;
        }
        return false;
    }    

public void captureChildView(View childView, int activePointerId) {
        if (childView.getParent() != mParentView) {
            throw new IllegalArgumentException("captureChildView: parameter must be a descendant " +
                    "of the ViewDragHelper's tracked parent view (" + mParentView + ")");
        }

        mCapturedView = childView;
        mActivePointerId = activePointerId;
        mCallback.onViewCaptured(childView, activePointerId);
        setDragState(STATE_DRAGGING);
    }

shouldInterceptTouchEvent返回true的条件是mDragState == STATE_DRAGGING,然而mDragState是在tryCaptureViewForDrag方法中被设置为STATE_DRAGGING的。

如果horizontalDragRange == 0 && verticalDragRange == 0这个条件一直为true的话,tryCaptureViewForDrag方法就得不到调用了。

而horizontalDragRange和verticalDragRange分别是Callback的getViewHorizontalDragRange和getViewVerticalDragRange方法返回的值,这两个方法默认情况下都返回0。

  • getViewHorizontalDragRange:返回子View水平方向可以被拖拽的范围
  • getViewVerticalDragRange:返回子View垂直方向可以被拖拽的范围

一般的写法,在Callback中加入:

@Override
public int getViewVerticalDragRange(View child) {
   return getMeasuredHeight() - child.getMeasuredHeight();
}

@Override
public int getViewHorizontalDragRange(View child) {
   return getMeasuredWidth() - child.getMeasuredWidth();
}

思考:为啥非clickable的view不需要加??

这涉及到了android的事件分发机制,由于子view不消费事件,那么事件都会给到上层viewGroup。而上层group只会触发一次shouldInterceptTouchEvent,即down事件,后面就不会了。只触发onTouch了。

它走的逻辑就不一样,它走的是processTouchEvent里的逻辑。

ViewDragHelper.Callback

boolean tryCaptureView(View child, int pointerId)

需要进行事件捕获的view,返回true即表示需要捕获。

void onViewCaptured(View capturedChild, int activePointerId)

一旦tryCaptureView成功,这个方法即被触发。

void onViewDragStateChanged(int state)

拖动状态改变时会调用此方法,状态state有STATE_IDLE、STATE_DRAGGING、STATE_SETTLING三种取值。

  • tryCaptureViewForDrag()成功捕获到子View时
    • shouldInterceptTouchEvent()的ACTION_DOWN部分捕获到
    • shouldInterceptTouchEvent()的ACTION_MOVE部分捕获到
    • processTouchEvent()的ACTION_MOVE部分捕获到
  • 调用settleCapturedViewAt()、smoothSlideViewTo()、flingCapturedView()时
  • 拖动View松手时(processTouchEvent()的ACTION_UP、ACTION_CANCEL)
  • 自动滚动停止时(continueSettling()里检测到滚动结束时)
  • 外部调用abort()时

int getViewHorizontalDragRange(View child)

int getViewVerticalDragRange(View child)

返回给定的child在相应的方向上可以被拖动的最远距离,默认返回0。?????????

void onViewReleased(View releasedChild, float xvel, float yvel)

拖动View松手时(processTouchEvent()的ACTION_UP)或被父View拦截事件时(processTouchEvent()的ACTION_CANCEL)会调用此方法。

可以在这里做手指离开后的处理逻辑。

void onViewPositionChanged(View changedView, int left, int top, int dx, int dy)

正在被拖动的View或者自动滚动的View的位置改变时会调用此方法。

int clampViewPositionHorizontal(View child, int left, int dx)

int clampViewPositionVertical(View child, int top, int dy)

child在某方向上被拖动时会调用对应方法,返回值是child移动过后的坐标位置,clampViewPositionHorizontal()返回child移动过后的left值,clampViewPositionVertical()返回child移动过后的top值。

int getOrderedChildIndex(int index)

在寻找当前触摸点下的子View时会调用此方法,寻找到的View会提供给tryCaptureViewForDrag()来尝试捕获。如果需要改变子View的遍历查询顺序可改写此方法,例如让下层的View优先于上层的View被选中。

void onEdgeTouched(int edgeFlags, int pointerId)

ACTION_DOWN或ACTION_POINTER_DOWN事件发生时如果触摸到监听的边缘会调用此方法。edgeFlags的取值为EDGE_LEFT、EDGE_TOP、EDGE_RIGHT、EDGE_BOTTOM的组合。

boolean onEdgeLock(int edgeFlags)

返回true表示锁定edgeFlags对应的边缘,锁定后的那些边缘就不会在onEdgeDragStarted()被通知了,默认返回false不锁定给定的边缘,edgeFlags的取值为EDGE_LEFT、EDGE_TOP、EDGE_RIGHT、EDGE_BOTTOM其中之一。

void onEdgeDragStarted(int edgeFlags, int pointerId)

ACTION_MOVE事件发生时,检测到开始在某些边缘有拖动的手势,也没有锁定边缘,会调用此方法。edgeFlags取值为EDGE_LEFT、EDGE_TOP、EDGE_RIGHT、EDGE_BOTTOM的组合。可在此手动调用captureChildView()触发从边缘拖动子View的效果。

三个开启自动滚动的方法

settleCapturedViewAt(int finalLeft, int finalTop)

以松手前的滑动速度为初速动,让捕获到的View自动滚动到指定位置。只能在Callback的onViewReleased()中调用。【如实现,自动回到触发的位置的功能】

flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop)

以松手前的滑动速度为初速动,让捕获到的View在指定范围内fling。只能在Callback的onViewReleased()中调用。

smoothSlideViewTo(View child, int finalLeft, int finalTop)

指定某个View自动滚动到指定的位置,初速度为0,可在任何地方调用。

实例

第一个View,就是演示简单的移动 第二个View,演示除了移动后,松手自动返回到原本的位置。(注意你拖动的越快,返回的越快) 第三个View,边界移动时对View进行捕获。

package com.yuqirong.swipelistview.activity;

import android.content.Context;
import android.graphics.Point;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;

/**
 * Created by panda on 2017/9/13.
 */

public class VDHLinearLayout extends LinearLayout {
    private ViewDragHelper mDragger;

    private View mDragView;
    private View mAutoBackView;
    private View mEdgeTrackerView;

    private Point mAutoBackOriginPos = new Point();

    public VDHLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        mDragger = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
            @Override
            public boolean tryCaptureView(View child, int pointerId) {
                //mEdgeTrackerView禁止直接移动
                return child == mDragView || child == mAutoBackView;
            }

            @Override
            public int clampViewPositionHorizontal(View child, int left, int dx) {
                return left;
            }

            @Override
            public int clampViewPositionVertical(View child, int top, int dy) {
                return top;
            }

            @Override
            public int getViewHorizontalDragRange(View child) {
                return getMeasuredWidth() - child.getMeasuredWidth();
            }

            @Override
            public int getViewVerticalDragRange(View child) {
                return getMeasuredHeight() - child.getMeasuredHeight();
            }

            //手指释放的时候回调
            @Override
            public void onViewReleased(View releasedChild, float xvel, float yvel) {
                //mAutoBackView手指释放时可以自动回去
                if (releasedChild == mAutoBackView) {
                    mDragger.flingCapturedView(0, 0, mAutoBackOriginPos.x, mAutoBackOriginPos.y);
                    invalidate();
                }
            }

            //在边界拖动时回调
            @Override
            public void onEdgeDragStarted(int edgeFlags, int pointerId) {
                mDragger.captureChildView(mEdgeTrackerView, pointerId);
            }
        });
        mDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
    }


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

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

    @Override
    public void computeScroll() {
        if (mDragger.continueSettling(true)) {
            invalidate();
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        mAutoBackOriginPos.x = mAutoBackView.getLeft();
        mAutoBackOriginPos.y = mAutoBackView.getTop();
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mDragView = getChildAt(0);
        mAutoBackView = getChildAt(1);
        mEdgeTrackerView = getChildAt(2);
    }
}

results matching ""

    No results matching ""