android触摸消息的分发

本文详细解析了Android中触摸事件的分发机制,包括Activity、ViewGroup和View之间的消息传递流程,以及ACTION_DOWN消息与其他消息的分发差异。

控件的状态及onClick等都由若干基本的触摸事件以及其他因素决定,控制了触摸事件的处理路径也变相改变了控件的状态。

分发的流程是Activity到ViewGroup,最后一直到叶子节点。

View的dispatchTouchEvent比较简单,主要决定执行自己的onTouchEvent还是安装的OnTouchListener等问题。题外话,onClick是在onTouchEvent中触发的。


dispatchTouchEvent方法在Activity、View、ViewGroup中都有实现,负责消息分发:

public boolean dispatchTouchEvent(MotionEvent ev) {  
    final int action = ev.getAction();  
    final float xf = ev.getX();  
    final float yf = ev.getY();  
    final float scrolledXFloat = xf + mScrollX;  
    final float scrolledYFloat = yf + mScrollY;  
    final Rect frame = mTempRect;  
    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
    if (action == MotionEvent.ACTION_DOWN) {  
        if (mMotionTarget != null) {  
            mMotionTarget = null;  
        }  
        if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
            ev.setAction(MotionEvent.ACTION_DOWN);  
            final int scrolledXInt = (int) scrolledXFloat;  
            final int scrolledYInt = (int) scrolledYFloat;  
            final View[] children = mChildren;  
            final int count = mChildrenCount;  
            for (int i = count - 1; i >= 0; i--) {  
                final View child = children[i];  
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                        || child.getAnimation() != null) {  
                    child.getHitRect(frame);  
                    if (frame.contains(scrolledXInt, scrolledYInt)) {  
                        final float xc = scrolledXFloat - child.mLeft;  
                        final float yc = scrolledYFloat - child.mTop;  
                        ev.setLocation(xc, yc);  
                        child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
                        if (child.dispatchTouchEvent(ev))  {  
                            mMotionTarget = child;  
                            return true;  
                        }  
                    }  
                }  
            }  
        }  
    }  
    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
            (action == MotionEvent.ACTION_CANCEL);  
    if (isUpOrCancel) {  
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
    }  
    final View target = mMotionTarget;  
    if (target == null) {  
        ev.setLocation(xf, yf);  
        if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
            ev.setAction(MotionEvent.ACTION_CANCEL);  
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        }  
        return super.dispatchTouchEvent(ev);  
    }  
    if (!disallowIntercept && onInterceptTouchEvent(ev)) {  
        final float xc = scrolledXFloat - (float) target.mLeft;  
        final float yc = scrolledYFloat - (float) target.mTop;  
        mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        ev.setAction(MotionEvent.ACTION_CANCEL);  
        ev.setLocation(xc, yc);  
        if (!target.dispatchTouchEvent(ev)) {  
        }  
        mMotionTarget = null;  
        return true;  
    }  
    if (isUpOrCancel) {  
        mMotionTarget = null;  
    }  
    final float xc = scrolledXFloat - (float) target.mLeft;  
    final float yc = scrolledYFloat - (float) target.mTop;  
    ev.setLocation(xc, yc);  
    if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
        ev.setAction(MotionEvent.ACTION_CANCEL);  
        target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        mMotionTarget = null;  
    }  
    return target.dispatchTouchEvent(ev);  
}

1. 总体原则

DOWN是动作序列的起始,没有它就没有然后了

默认状态下,一个消息只能有一个消息处理,中间路径只做中转

onInterceptTouchEvent是ViewGroup的方法,用来拦截消息,返回true代表拦截,就不进行后续的消息分发了,false反之


2. ACTION_DOWN消息的分发

mMonitorTarget代表当前ViewGroup处理消息的子控件,初始时其为空,当有子控件处理消息后,mMonitorTarget的值变为子控件。

先经过onInterceptTouchEvent方法,看当前ViewGroup有没有被拦截,如果没有就遍历子节点,看分发给哪个控件,

并调用他们的dispatch方法,如果子节点是ViewGroup,将会按此方法继续分发,

如果是View,根据OnTouchEvent及OnTouchListener处理消息,返回true代表已处理,false反之,

如果点击位置没有子控件或者子控件都不处理消息,代码就执行到38行。

44行,如果没有子控件处理消息,直接交给自己View的dispatchTouchEvent处理。

到这里,ACTION_DOWN的所有分支都完了,52行附近无法达到。


3. 其他消息

9~37行忽略。

38行,如果是up或者cancel消息,先禁止拦截,再在后续会清空mMonitorTarget,代表一个触摸流程的结束。

44行,如果没有子控件要处理事件,分发给自己,交给自己的View的dispatchTouchEvent处理。

52行,如果消息是要子控件处理的,先经过onInterceptTouchEvent看是否需要拦截,如果拦截就直接结束触摸流程并给子控件发流程结束消息

63行,此时不需要拦截,如果是触摸流程结束消息,清空处理消息控件mMonitorTarget,其他与52行类似,只是改成把当前消息分发给子控件。

注意:触摸流程结束消息如cancel和up是必须传递给子控件的,否则流程根本停不下来;down的拦截是不给child发消息,其他消息的拦截是直接发结束消息。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值