ViewDragHelper,入门级源码分析

时间:2022-05-31 00:29:28

在这篇文章的开头,我想提出几个问题:

    dragHelper.create(forParent, cb);
dragHelper.create(forParent, sensitivity, cb)

1、这个sensitivity的意义是什么?这个参数对于DragViewHelper起着什么样子作用?

    public boolean onInterceptTouchEvent(MotionEvent ev) {
return dragHelper.shouldInterceptTouchEvent(ev);
}

2、我们在使用ViewDragHelper的时候,通常会重写这个方法,具体这样写有什么用?,有人可能会说让ViewDragHelper去帮我们处理是否拦截事件,但是这个方法如果我不去重写的话,或者返回一个False的话,CallBack回掉能够触发吗?或者说ViewDragHelper具体是怎么样帮我们去处理是否拦截事件?

    public int getViewHorizontalDragRange(View child) {

};

3、在callBack中,我们是否有必要去重写这个方法?这个方法作用到底是什么?

这篇文章的话,我就想以这几个问题为核心对ViewDragHelper的一些源码做一些简单的分析与介绍;
接下来我想从ViewDragHelper的一般使用步骤对其进行一个源码分析;

1、创建一个ViewDragHelper对象

    dragHelper.create(forParent, cb);
dragHelper.create(forParent, sensitivity, cb)

我们按住ctrl键直接跳转到ViewDragHelper的源码的位置:

public static ViewDragHelper create(ViewGroup forParent, Callback cb) {
return new ViewDragHelper(forParent.getContext(), forParent, cb);
}

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

helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
公式右边 helper.mTouchSlop*(1/sensitivity)
从上面的代码可以看到helper.mTouchSlop早已经存在并且不会空,聪明的你,肯定已经猜到了这个是一个默认值,而且我们通过不带sentivity这个参数的Create方法使用的就是这个默认值;
而我们通过传入一个sensitivity一个参数的作用就是对这个mTouchSlop参数进行一个纠正;
mTouchSlop这个参数表示是什么意思呢?通俗的说就是触摸的敏感度,当触摸滑动的距离大于这个的话就产生事件;这里我们就先这样解释,到后边我们还会遇到。这个先树立一个flag;
对于Create方法,我们先说到这里;

2、接下来我们该聊聊,这个方法:

    public boolean onInterceptTouchEvent(MotionEvent ev) {
return dragHelper.shouldInterceptTouchEvent(ev);
}

这个方法的通俗的解释就是让ViewDragHelper帮助我们判断是否拦截这个事件,其实这样解释的话,我们还是不知道它做了哪些事情?什么时候拦截?什么时候不拦截?我们都不知道;基于这样的想法,所以继续打开这个方法的源码,仔细研究研究;

public boolean shouldInterceptTouchEvent(MotionEvent ev) {      
{
。。。。。以下省略纵多代码。。。。
}
return mDragState == STATE_DRAGGING;
}

去掉那些多余的代码就剩下了这一行了,我们可以知道其他代码,不关心,我们就需要知道一件事情,mDragState ==STATE_DRAGGING就返回true,这个shouldInterceptTouchEvent就会去拦截事件;
那么我们现在需要关心的的重点就变成了,在这个shouldInterceptTouchEvent方法里面,什么时候会让mDragState=STATE_DRAGGING,基于这样的想法,我们就使用Ctrl+f跟踪下一下什么时候会让mDragState=STATE_DRAGGING
寻寻觅觅,我们发现:

void setDragState(int state) {
if (mDragState != state) {
mDragState = state;
mCallback.onViewDragStateChanged(state);
if (state == STATE_IDLE) {
mCapturedView = null;
}
}
}

整个ViewDragHelper都是通过这个方法来设置状态的;
那么我们继续跟踪下去看看,究竟在哪里使用了setDragState(STATE_DRAGGING);
寻寻觅觅,我们找到了:

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

很幸运的是整个类中,只有这个方法能过就DragState设置为STATE_DRAGGING;
那么我们接下来需要进一步的跟踪,在哪里调用了这个方法:
顺藤摸瓜ing,我们发现:

boolean tryCaptureViewForDrag(View toCapture, int pointerId) {
if (toCapture == mCapturedView && mActivePointerId == pointerId) {
// Already done!,如果之前捕获过的话,mCapturedVie是不为空的,所以这里的原声注释就是Already,done;
return true;
} //看看这里:
if (toCapture != null && mCallback.tryCaptureView(toCapture, pointerId)) {
mActivePointerId = pointerId;
//hi, boy 看这:
captureChildView(toCapture, pointerId);
return true;
}
return false;
}

幸运的是我们发现调用这个captureChildView方法的,在整个类中也只有tryCaptureViewForDrag这个类
这个方法是不是有点眼熟呢?和我们在callBack中回掉的tryCaptureView方法是不是很像,我们在上面源码中就知道这个方法里面其实调用了tryCaptureView这个方法;
这里让我们重新整理一下思路:
shouldInterceptTouchEvent—->setDragState(STATE_DRAGGING)——>captureChildView—–>tryCaptureViewForDrag
我们沿着路一直追到了这里=_=,这才刚刚开始;
那么我们就继续往下追,看看哪里调用了tryCaptureViewForDrag这个方法:
这里我们一共追到了5处调用tryCaptureViewForDrag这个方法,=_= 是不是太多了?
没有关系,这里的话,我们只关心在shouldInterceptTouchEvent里面调用的几处就可以了;(共三处)
第一处:case MotionEvent.ACTION_DOWN
这里涉及到了mDragState == STATE_SETTLING这个状态,那么肯定发生了状态的变化,也就是说肯定不是第一次进入到这里,所以这里暂时我们不用关心;

            if (toCapture == mCapturedView && mDragState == STATE_SETTLING) {
tryCaptureViewForDrag(toCapture, pointerId);
}

第二处:case MotionEventCompat.ACTION_POINTER_DOWN: 这里涉及到了多点触发,我们也暂时不用关心,我就不罗列了
第三处(重要): case MotionEvent.ACTION_MOVE:这里发生了很重要的事情,看下面:

        //这个方法我们可以继续跟踪下去看看发生了什么事情,不过这里并不建议,这里的目的就是找到手指接触到的最上面的孩子
final View toCapture = findTopChildUnder((int) x, (int) y);
//toCapture!=null表示找到了,checkTouchSlop这里我们需要进入看看
if (toCapture != null && checkTouchSlop(toCapture, dx, dy) &&
tryCaptureViewForDrag(toCapture, pointerId)) {
break;
}

这里我们要看看checkTouchSlop这个方法到底实现了什么?什么时候会返回true,什么时候返回false

    //child:就是上面传递进来的孩子,dx表示水平方向的位移差值;(手指在屏幕水平滑动的距离),dy同理
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
//这两个都是false的话也就是说,这个checkTouchSlop方法必定返回一个false
if (checkHorizontal && checkVertical) {
//这里的mTouchSlop有没有眼熟,就是我们上面第一个问题的flag设置的值
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;
}

通过上面的观察,我们知道下面的几件事情:
1、如果正常的情况下,如果我们不重写getViewHorizontalDragRange这个方法,这个方法返回默认值为0,那么checkHorizontal为false,在水平方向上,shouldInterceptTouchEvent是不会去拦截的;也就是说,对于下面这个方法,我们写了和没有写是一个道理,返回的都是false;

    public boolean onInterceptTouchEvent(MotionEvent ev) {
return dragHelper.shouldInterceptTouchEvent(ev);
}

2、决定checkTouchSlop这个返回true或者false还有另一个关键性的因素,就是mTouchSlop,

    //如果我们使用这个方法的话,因为使用的是默认值,是不会影响mTouchSlop
dragHelper.create(forParent, cb);
//但是如果我们使用的这个方法创建对象的话,对于拖拽的敏感度,我们是可以进行一个调整的;就可通过调整这个变量的值,来决定checkTouchSlop是否返回一个false
dragHelper.create(forParent, sensitivity, cb)

其实说到这里话,我想对于ViewDragHelper的一个入门级的源码分析已经结束了,对上面的分析我想说的有几点值得注意的是,

  • 上面我们进行的分析都是针对ViewDragHelper的一个被动拦截机制,什么意思呢?在最开始我们说过,在shouldInterceptTouchEvent中有三处调用了tryCaptureViewForDrag这个方法,我们只分析了其中的一种,二其他两处都需要是:mDragState的状态是 STATE_SETTLING这样,我们自己去手动去跟踪发现,能够将mDragState状态修改成STATE_SETTLING的方法,都不是ViewDragHelper自己去调用的,而是暴露出给外界,所这里我们都没有分析;
  • getViewHorizontalDragRange这个方法不仅仅影响这个ViewDragHelper的拦截机制,如果有用过DragHelper中的smoothSlideViewTo方法,来实现过度,其实我们观看源码,就知道getViewHorizontalDragRange还能影响这个方法的过度时间;可以将这个方法的返回值改成-1,试一下;
  • 让ViewDragHelper调用这个shouldInterceptTouchEvent方法意义:其实这里就是view的事件的分发和拦截有关系,这里返回false,对于子View如果不消耗事件的话,写不写没有意义,但是如果这里子类可能回去消耗事件的话,如果这个返回false,那么ViewDragHelper中的processTouchEvent方法将不会再执行;

Android交流群: 232748032