View事件分发机制

来源:互联网 发布:手机上头像源码怎么用 编辑:程序博客网 时间:2024/05/16 02:03

点击事件的传递规则

点击事件产生后,传递过程:Activity->Window->顶层View->分发到具体的View。前两个传递比较简单,不用说。顶层View一般为ViewGroup,ViewGroup会首先根据onInterceptTouchEvent判断是否拦截,如果拦截,那么就会调用自己的onTouchEvent方法进行处理,如果不拦截,就会分发给子View,子View再调用自己的dispatchTouchEvent做进一步分发。如果所有的View都不消费掉这个事件,那么会调用Activity的onTouchEvent方法来处理。
对于一个View而言,OnTouchListener的优先级高于自己的onTouchEvent方法,在onTouchEvent方法中,TouchDelegate的优先级最高,其次是OnLongClickListener(如果是长按的话),最后才是OnClickListener。

源码解析

当触摸事件发生时,Activity的dispatchTouchEvent首先被调用。

// Activity$dispatchTouchEventpublic boolean dispatchTouchEvent(MotionEvent ev) {    if (ev.getAction() == MotionEvent.ACTION_DOWN) {        onUserInteraction();    }    // 首先分发给Window去处理,如果该事件没有被消费掉,那么    // 会调用Activity的ouTouchEvent方法    if (getWindow().superDispatchTouchEvent(ev)) {        return true;    }    return onTouchEvent(ev);}
// PhoneWindow$superDispatchTouchEvent@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {    return mDecor.superDispatchTouchEvent(event);}
// DecorView$superDispatchTouchEventpublic boolean superDispatchTouchEvent(MotionEvent event) {    return super.dispatchTouchEvent(event);}
// ViewGroup$dispatchTouchEvent@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {    ...    // 出于安全考虑,如果设置了窗口被遮挡后丢弃事件的属性,那么窗口被    // 遮挡时,事件直接被丢弃,而不会分发出去。也就是说,当出现了Toast、    // 对话框等的时候,触摸事件就不会被响应了。该属性一般不会被设置。    if (onFilterTouchEventForSecurity(ev)) {        ...        // Handle an initial down.        if (actionMasked == MotionEvent.ACTION_DOWN) {            // Throw away all previous state when starting a new touch gesture.            // The framework may have dropped the up or cancel event for the previous gesture            // due to an app switch, ANR, or some other state change.            cancelAndClearTouchTargets(ev);            // 这个方法中会重置FLAG_DISALLOW_INTERCEPT标识,该标识表示当前            // ViewGroup不能拦截触摸事件,也就是说,无论如何该标识都不能控制            // ACTION_DOWN事件的拦截            resetTouchState();        }        // 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.            // 也就是说如果ViewGorup拦截了某触摸手势的ACTION_DOWN事件后,            // 该触摸手势的后续事件都交给这个ViewGroup进行处理,不再需要用            // onInterceptTouchEvent判断是否拦截            intercepted = true;        }        ...    }}...
// 还是ViewGroup$dispatchTouchEvent@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {    ...    // ViewGroup不拦截时的事件分发    for (int i = childrenCount - 1; i >= 0; i--) {        final int childIndex = customOrder                ? getChildDrawingOrder(childrenCount, i) : i;        final View child = (preorderedList == null)                ? children[childIndex] : preorderedList.get(childIndex);        // If there is a view that has accessibility focus we want it        // to get the event first and if not handled we will perform a        // normal dispatch. We may do a double iteration but this is        // safer given the timeframe.        if (childWithAccessibilityFocus != null) {            if (childWithAccessibilityFocus != child) {                continue;            }            childWithAccessibilityFocus = null;            i = childrenCount - 1;        }        if (!canViewReceivePointerEvents(child)                || !isTransformedTouchPointInView(x, y, child, null)) {            ev.setTargetAccessibilityFocus(false);            continue;        }        newTouchTarget = getTouchTarget(child);        if (newTouchTarget != null) {            // Child is already receiving touch within its bounds.            // Give it the new pointer in addition to the ones it is handling.            newTouchTarget.pointerIdBits |= idBitsToAssign;            break;        }        resetCancelNextUpFlag(child);        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {            // Child wants to receive touch within its bounds.            mLastTouchDownTime = ev.getDownTime();            if (preorderedList != null) {                // childIndex points into presorted list, find original index                for (int j = 0; j < childrenCount; j++) {                    if (children[childIndex] == mChildren[j]) {                        mLastTouchDownIndex = j;                        break;                    }                }            } else {                mLastTouchDownIndex = childIndex;            }            mLastTouchDownX = ev.getX();            mLastTouchDownY = ev.getY();            newTouchTarget = addTouchTarget(child, idBitsToAssign);            alreadyDispatchedToNewTouchTarget = true;            break;        }        // The accessibility focus didn't handle the event, so clear        // the flag and do a normal dispatch to all children.        ev.setTargetAccessibilityFocus(false);    }    ...    if (mFirstTouchTarget == null) {        // ViewGroup进行拦截或者在子View中没有处理事件时,ViewGroup        // 调用父类View的dispatchTouchEvent进行处理。        // No touch targets so treat this as an ordinary view.        handled = dispatchTransformedTouchEvent(ev, canceled, null,                TouchTarget.ALL_POINTER_IDS);    }    ...}

明显,在正常的事件(通常是ACTION_DOWN)分发时,ViewGroup会依次查询每个子View,如果子View能够接收到触摸事件,就会把事件交给该子View去处理。判断是否能接收到触摸事件有两点:view没有在播放动画,也没有准备播放动画;触摸事件的坐标处于view的范围内。子View收到事件后,会调用自己的dispatchTouchEvent来进一步分发。如果子View处理了该事件,那么mFirstTouchTarget就会在addTouchTarget方法中被赋值,并且终止事件的分发。在之前的代码中,可以看到,如果mFirstTouchTarget为null,也就是没有任何子View处理该事件(通常是ACTION_DOWN),那么接下来的所有事件都会被当前的ViewGroup拦截。

// ViewGroup$dispatchTransformedTouchEventprivate boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,        View child, int desiredPointerIdBits) {    ...    // Perform any necessary transformations and dispatch.    if (child == null) {        handled = super.dispatchTouchEvent(transformedEvent);    } else {        final float offsetX = mScrollX - child.mLeft;        final float offsetY = mScrollY - child.mTop;        transformedEvent.offsetLocation(offsetX, offsetY);        if (! child.hasIdentityMatrix()) {            transformedEvent.transform(child.getInverseMatrix());        }        handled = child.dispatchTouchEvent(transformedEvent);    }    ...}
// View$dispatchTouchEventpublic boolean dispatchTouchEvent(MotionEvent event) {    ...    boolean result = false;    ...    if (onFilterTouchEventForSecurity(event)) {        //noinspection SimplifiableIfStatement        ListenerInfo li = mListenerInfo;        if (li != null && li.mOnTouchListener != null                && (mViewFlags & ENABLED_MASK) == ENABLED                && li.mOnTouchListener.onTouch(this, event)) {            result = true;        }        if (!result && onTouchEvent(event)) {            result = true;        }    }    ...    return result;}

View在处理触摸事件时,会先判断是否设置了OnTouchListener,如果设置了而且onTouch方法处理了该事件,就不再调用onTouchEvent进行处理;否则才会调用onTouchEvent。明显,onTouch优先级高于onTouchEvent。

// View$onTouchEventpublic boolean onTouchEvent(MotionEvent event) {    ...    if ((viewFlags & ENABLED_MASK) == DISABLED) {        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {            setPressed(false);        }        // A disabled view that is clickable still consumes the touch        // events, it just doesn't respond to them.        return (((viewFlags & CLICKABLE) == CLICKABLE                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);    }    if (mTouchDelegate != null) {        if (mTouchDelegate.onTouchEvent(event)) {            return true;        }    }    if (((viewFlags & CLICKABLE) == CLICKABLE ||            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||            (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {        switch (action) {            case MotionEvent.ACTION_UP:                // 如果在ListView等可以滚动的容器中ACTION_DOWN时,                // 会有一个延迟之后,才会置位PFLAF_PRESSED,此前都是                // PFLAG_PREPRESSED被置位。                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {                    // take focus if we don't have it already and we should in                    // touch mode.                    boolean focusTaken = false;                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {                        focusTaken = requestFocus();                    }                    if (prepressed) {                        // The button is being released before we actually                        // showed it as pressed.  Make it show the pressed                        // state now (before scheduling the click) to ensure                        // the user sees it.                        setPressed(true, x, y);                    }                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {                        // This is a tap, so remove the longpress check                        removeLongPressCallback();                        // Only perform take click actions if we were in the pressed state                        if (!focusTaken) {                            // Use a Runnable and post this rather than calling                            // performClick directly. This lets other visual state                            // of the view update before click actions start.                            if (mPerformClick == null) {                                mPerformClick = new PerformClick();                            }                            if (!post(mPerformClick)) {                                performClick();                            }                        }                    }                }                break;                ...        }        return true;    }    return false;}

明显,只要是CLICKABLE或者LONG_CLICKABLE或者CONTEXT_CLICKABLE的,不管View是否是enabled状态,都会消费点击事件。如果View是enabled的,那么会看是否设置了代理,如果有代理,那么先调用代理的onTouchEvent方法看是否被消费掉,如果没有消费,那么才会真正由View来做处理。在ACTION_UP时如果点击事件没有被OnLongClickListener.onLongClick方法消费掉,那么就会调用OnClickListener的onClick方法。

// View$performLongClickpublic boolean performLongClick() {    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);    boolean handled = false;    ListenerInfo li = mListenerInfo;    if (li != null && li.mOnLongClickListener != null) {        handled = li.mOnLongClickListener.onLongClick(View.this);    }    if (!handled) {        handled = showContextMenu();    }    if (handled) {        performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);    }    return handled;}
// View$performClickpublic boolean performClick() {    final boolean result;    final ListenerInfo li = mListenerInfo;    if (li != null && li.mOnClickListener != null) {        playSoundEffect(SoundEffectConstants.CLICK);        li.mOnClickListener.onClick(this);        result = true;    } else {        result = false;    }    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);    return result;}
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 妈妈误打死一只黄鼠狼怎么办 油笔画在白墙上怎么办 壁纸上的水彩笔怎么办 隐形拉链头脱了怎么办 拉链的一边掉了怎么办 帝豪gs加了乙醇汽油怎么办 命理五行缺木怎么办 微信改名含有特殊符号怎么办 户口名字打错了怎么办 寻仙会心几率差怎么办 注册商标下来了没收到怎么办 金融公司倒闭欠的钱怎么办 买车贷款被骗了怎么办 定投终止后钱怎么办 受到小贷公司催款威胁怎么办 合同保证金单据丢了怎么办 公司注销期间发现欠税怎么办 公司注销后银行账户怎么办 注销公司营业执照和公章丢失怎么办 工商核名过期了怎么办 核名后的许可没办下来怎么办 重庆公司核名有同名的怎么办 新电视不全屏怎么办左右有黑边 所学类别找不到音乐表演怎么办 公司口头通知不续签合同怎么办 雪纺衬衣皱了怎么办 狗打架受伤怎么办泰迪 大狗打架破了怎么办 舌头上长溃疡怎么办吃什么药 悠悠球不回弹怎么办啊 围棋遇到对方不停围堵怎么办? s围棋业余四段想提升怎么办 wps禁止创建分享链接怎么办 驾驶人开车违章不认可怎么办 京东白条退货分期服务费怎么办 新车年检标丢了怎么办 异地违章罚单丢了怎么办 异地现场违章罚单丢了怎么办 新车没有牌照过停车杆怎么办 驾照换证时间过了怎么办 杭州告知单丢了怎么办