Android view 事件分发流程(四)--- 事件拦截

Android view 事件分发流程(四)— 事件拦截

1、事件拦截介绍

事件拦截就是拦截事件分发到子View中去。事件拦截的方式有两种:内部拦截和外部拦截。

2、内部拦截

内部拦截是DOWN事件已经到分到了子View后,接收到MOVE事件,由于某种判断确定要把子View的事件返回到给父容器处理(注意:事件处理一旦回到了父容器手上后,后面的事件处理就没有子View的分了,不需得重新开始DOWN事件后再次分发事件,子View才有可能再次拿到事件的处理)。

1)代码分析

内部拦截时调用getParent().requestDisallowIntercepteTouchEvent(boolean disallowIntercept)分发实现的。disallowIntercept 为 true 时是请求父容器允许事件分发到子View;disallowIntercept 为 false 时请求父容器拦截事件分发到子View的。

看下requestDisallowIntercepteTouchEvent(boolean disallowIntercept)代码,主要改变的是mGroupFlags的值,disallowIntercept == true 时 mGroupFlags = FLAG_DISALLOW_INTERCEPT,disallowIntercept == false 时 mGroupFlags = 0

@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
        // We're already in this state, assume our ancestors are too
        return;
    }

    if (disallowIntercept) {
        mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
    } else {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }

    // Pass it up to our parent
    if (mParent != null) {
        mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
    }
}

由上面代码得出结果是,mGroupFlags会影响到拦截,然后再ViewGroup.dispatchTouchEvent方法中的拦截模块找到了它,final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; mGroupFlags == FLAG_DISALLOW_INTERCEPT 时 disallowIntercept = true → intercepted = false; mGroupFlags == 0 时 disallowIntercept = false → intercepted = onInterceptTouchEvent(ev);由此看出调用getParent().requestDisallowIntercepteTouchEvent(false)时,还得父View的onInterceptTouchEvent(ev)方法配合

// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); // restore action in case it was changed
    } else {
        intercepted = false;
    }
} else {
    // There are no touch targets and this action is not an initial down
    // so this view group continues to intercept touches.
    intercepted = true;
}

由上代码可以看出调用调用getParent().requestDisallowIntercepteTouchEvent(false)会影响到intercepted的值,在看看intercepted除了在获取目标子View的时候还有在哪里用到,在事件处理代码模块时使用到了 final boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted; dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)
,intercepted的值决定cancelChild的值,cancelChild的值会影响dispatchTransformedTouchEvent方法的处理

// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
} else {
    // Dispatch to touch targets, excluding the new touch target if we already
    // dispatched to it.  Cancel touch targets if necessary.
    TouchTarget predecessor = null;
    TouchTarget target = mFirstTouchTarget;
    while (target != null) {
        final TouchTarget next = target.next;
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
            handled = true;
        } else {
            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
            if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                handled = true;
            }
            if (cancelChild) {
                if (predecessor == null) {
                    mFirstTouchTarget = next;
                } else {
                    predecessor.next = next;
                }
                target.recycle();
                target = next;
                continue;
            }
        }
        predecessor = target;
        target = next;
    }
}

在这里我们看看cancelChild在dispatchTransformedTouchEvent方法的使用,cancel为true时会强制执行MotionEvent.ACTION_CANCEL事件,执行完MotionEvent.ACTION_CANCEL后,mFirstTouchTarget一定为null,下一次事件只要不是DOWN事件,一定会执行if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}也就是super.dispatchTouchEvent(event); 不会再分发事件给子View了

// Canceling motions is a special case.  We don't need to perform any transformations
// or filtering.  The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
    event.setAction(MotionEvent.ACTION_CANCEL);
    if (child == null) {
        handled = super.dispatchTouchEvent(event);
    } else {
        handled = child.dispatchTouchEvent(event);
    }
    event.setAction(oldAction);
    return handled;
}

2)代码实现

在子View中重写dispatchTouchEvent方法,在DOWN事件时必须调用getParent().requestDisallowInterceptTouchEvent(true);方法,mGroupFlags = FLAG_DISALLOW_INTERCEPT → disallowIntercept = true → intercepted = false; 不拦截 不让调用onInterceptTouchEvent方法,直接赋值false,在MOVE事件特定的条件下,调用getParent().requestDisallowInterceptTouchEvent(false);方法,必须调用onInterceptTouchEvent方法获取拦截状态,也就是说onInterceptTouchEvent方法在MOVE事件下必须返回true。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if(ev.getAction() == MotionEvent.ACTION_DOWN){   
        getParent().requestDisallowInterceptTouchEvent(true);
        point = new Point((int) ev.getX(),(int) ev.getY());
    }else if(ev.getAction() == MotionEvent.ACTION_MOVE){
        if(point != null){
            Point newPoint = new Point((int) ev.getX(),(int) ev.getY());
            if(getDirection(point,newPoint)){
                getParent().requestDisallowInterceptTouchEvent(false);
                System.out.println("把事件给回到上层");
            }
        }
    }
    return super.dispatchTouchEvent(ev);
}

父View中重写onInterceptTouchEvent方法,DOWN事件时返回false,允许分发事件到子View,MOVE事件时,必须返回true

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    if(ev.getAction() == MotionEvent.ACTION_DOWN){
        return false;
    }else{
        return true;
    }
}

3)总结

move事件 → 子View在dispatchTouchEvent方法中调用getParent().requestDisallowIntercepteTouchEvent(false) → mGroupFlags = 0 → disallowIntercept = false → intercepted = onInterceptTouchEvent(ev); 此时onInterceptTouchEvent必须返回true → intercepted = true → cancelChild = true → dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits) → mFirstTouchTarget = null
;走完这一流程,下一次除DOWN事件外来时,由于mFirstTouchTarget = null,最后调用的是super.dispatchTouchEvent(event);

3、外部拦截

外部拦截事件是父View自己通过onInterceptTouchEvent方法拦截事件分发到子View的。最终想要的效果是改变cancelChild值,最终强制给子View执行CANCEL事件,mFirstTouchTarget = null。后面的流程和内部拦截是一样的,主要是获取intercepted值是不一样的,这里的intercepted值必须从onInterceptTouchEvent(ev)方法获取,所以事件的拦截判断是从onInterceptTouchEvent(ev)实现。mGroupFlags没有其他的干预时的值是0,所以一直都可以intercepted = onInterceptTouchEvent(ev);

1)代码实现

DOWN事件默认返回false,MOVE事件根据判断返回结果,如果返回true就会给子View执行CANCEL事件,mFirstTouchTarget = null,实现拦截子View,后面的事件都是不方法到子View

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    if(ev.getAction() == MotionEvent.ACTION_DOWN){
        downPoint = new Point((int) ev.getX(),(int)ev.getY());
        direction = false;
    }else if(ev.getAction() == MotionEvent.ACTION_MOVE){
        if(downPoint != null){
            Point newPoint = new Point((int) ev.getX(),(int)ev.getY());
            direction = getDirection(downPoint,newPoint);
            downPoint = null;
        }
    }
    return direction;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

szzyjsxyzwy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值