Android 事件分发机制

事件分发机制

概述

  • 用户通过屏幕与手机交互的时候,每一次点击、长按、移动都是一个事件。
  • 事件分发机制:某一个事件从屏幕传递到各个View,由View来使用这个事件(消费事件)或忽略这一事件(不消费事件),这整个流程的控制。
  • 系统把事件封装到MotionEvent对象中,事件分发到过程就是MotionEvent分发到过程。

View继承关系

在这里插入图片描述

事件序列

每个事件都是从 ACTION_DOWN开始到ACTION_UP结束。

事件类型具体动作
MotionEvent.ACTION_DOWN按下
MotionEvent.ACTION_UP抬起
MotionEvent.ACTION_MOVE移动
MotionEvent.ACTION_CANCEL取消事件(事件被上层拦截时触发)

当某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一事件序列中的其他事件都不会再交给它处理,并且事件将重新由它的父View处理。

事件分发流程

Activity#dispatchTouchEvent() --> PhoneWindow#superDispatchToucheEvent() --> DecorView#superDispatchTouchEvent() --> ViewGroup#dispatchTouchEvent() --> View#dispatchTouchEvent() --> View#onTouchEvent()

在这里插入图片描述

事件分发机制伪代码

public boolean dispatchTouchEvent(MotionEvent event) {
    //是否消费事件
    boolean isConsume = false;

    if (isViewGroup) {
        //如果是ViewGroup
        if (onInterceptTouchEvent(event)) {
            //如果onInterceptTouchEvent()返回true,表示当前View拦截事件,交给当前View的onTouchEvent()处理
            isConsume = onTouchEvent(event);
        } else {
            //如果onInterceptTouchEvent()返回false,该事件会传递给子View处理,子View的dispatchTouchEvent()会被调用
            isConsume = child.dispatchTouchEvent(event);
        }
    } else {
        //如果是View,交给View的onTouchEvent()处理
        isConsume = onTouchEvent(event);
    }
    return isConsume;
}

//如果onTouchEvent()不消费返回false,则交给父View的onTouchEvent()
public void handleTouchEvent(MotionEvent event) {
    if (!onTouchEvent(event)) {
        getParent.onTouchEvent(event);
    }
}

事件分发核心方法

dispatchTouchEvent(MotionEvent ev)

分发事件,当事件传递到当前View时,该方法会被调用

  • 默认返回super,表示会继续分发该事件
  • 当返回true时,表示当前View消费事件,不传递给其他View
  • 当返回false时,表示停止分发事件,交由上层控件的onTouchEvent()处理

onInterceptTouchEvent(MotionEvent ev)

拦截事件,ViewGroup专有方法,在dispatchTouchEvent()中调用

  • 默认返回super,这时有两种情况:
    • 第一种:ViewGroup存在子View,并且点击到该子View,这时不会拦截并分发给子View处理,相当于返回false
    • 第二种:ViewGroup没有子View,或有子View但是没有点击子View,这时事件交由ViewGroup的onTouchEvent()处理,相当于返回true
    • LinearLayout/RelativeLayout/FrameLayout等ViewGroup默认不拦截,而ScrollView/ListView等ViewGroup则可能拦截
  • 当返回true时,表示拦截事件,交给当前View的onTouchEvent()处理,事件一旦被拦击,后续的move、up事件都直接交给onTouchEvent(),不会再重新询问是否拦截,即不再调用onInterceptTouchEvent()
  • 当返回false时,表示事件不拦截,分发到子View

onTouchEvent(MotionEvent event)

消费事件,在dispatchTouchEvent()中调用,可以通过不同场景定制触摸反馈算法

  • 默认返回super,这时分两种情况:

    • 如果当前View的clickable或longClickable为true,表示消费该事件,相当于返回true
    • 如果当前Viewclickable和longClickable同时为false,表示不消费该事件,事件将向上级控价传递,相当于返回false
  • 当返回true时,表示当前View消费该事件

  • 当返回false时,表示当前View不处理事件,事件会被传递给上层控件的onTouchEvent()处理

  • requestDisallowInterceptTouchEvent(boolean disallowIntercept):是ViewGroup专有方法,用于做事件拦截的,一般在子View中调用

onTouch/onTouchEvent/onClick执行顺序

onTouch()
↓
onTouchEvent()
↓
onClick()

onInterceptTouchEvent()被调用,表示当前View要消费事件。

如果设置setOnTouchListener(),则onTouch()会被调用,如果onTouch()返回true时,onTouchEvent()不会被调用;如果返回false或没有设置setOnTouchListener()时,会调用onTouchEvent()

onClick()在设置setOnClickListener()会被调用。

源码分析

Activity事件分发流程

Activity & Window & DecorView关系

  • 一个Activity包含一个Window,Window由PhoneWindow类来实现的
  • 每个PhoneWindow对应一个DecorView,将DecorView做为整个应用窗口的根视图
  • DecorView是FrameLayout的子类,而这个DecorView又将屏幕划分为两个区域一个是TitleView一个是ContentView,而我们平常做应用所写的布局正是展示在ContentView中的。

在这里插入图片描述

Activitty#dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
    //一般事件序列从DOWN事件开始,所以一直为true
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    
    //事件传递到PhoneWindow#superDispatchTouchEvent()处理
    /*
    如果PhoneWindow#superDispatchTouchEvent()返回true,表示分发该事件;
    如果返回false,则交给Activity#onTouchEvent()处理
    */
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}
PhoneWindow#superDispatchTouchEvent()
public boolean superDispatchTouchEvent(MotionEvent event) {
    //PhoneWindow将事件传递给DecorView,mDecor是DecorView的实例对象
    return mDecor.superDispatchTouchEvent(event);
}
DecorView#superDispatchTouchEvent()
public boolean superDispatchTouchEvent(MotionEvent event) {
    //最终交给ViewGroup#dispatchTouchEvent()处理
    return super.dispatchTouchEvent(event);
}
Activity#onTouchEvent()
//当一个事件未被Activity下的任何View处理时,将调用Activity#onTouchEvent()处理
public boolean onTouchEvent(MotionEvent event) {
    // Activity为Dialog时
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }
    return false;
}

View事件分发流程

在这里插入图片描述

View#dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent event) {  
    /*
    当mOnTouchListener!=null,会先执行onTouch(),如果返回true,则不执行onTouchEvent;
    如果返回false,则执行onTouchEvent()
    */
    boolean result = false;
    //安全检查
    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        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#onTouchEvent()
public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();

    /*
    CLICKABLE/LONG_CLICKABLE/CONTEXT_CLICKABLE:可单击、可长按、上下文点击,至少满足一个,clickable为true
    clickable用于判断是否可以点击
    如果View是被禁用状态DISABLED,clickable为true时会消耗事件
    */
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                               || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
        || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
        return clickable;
    }  

    /*
	mTouchDelegate不为null,则消费该事件
    */
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

    /*
    核心代码:处理事件逻辑代码
    */
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                //处理UP事件
                
                //如果不可点击时,状态全部重置
                if (!clickable) {
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;
                }

                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    boolean focusTaken = false;
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                        focusTaken = requestFocus();
                    }

                    if (prepressed) {
                        setPressed(true, x, y);
                    }

                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        removeLongPressCallback();

                        if (!focusTaken) {
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                //触发点击事件
                                performClickInternal();
                            }
                        }
                    }

                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }

                    if (prepressed) {
                        postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        mUnsetPressedState.run();
                    }

                    removeTapCallback();
                }
                mIgnoreNextUpEvent = false;
                break;
            case MotionEvent.ACTION_DOWN:
                //处理DOWN事件

                //判断是否为手指触摸
                if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                    mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                }
                mHasPerformedLongPress = false;

                //如果不可点击时,检查是否为长按事件
                if (!clickable) {
                    checkForLongClick(
                        ViewConfiguration.getLongPressTimeout(),
                        x,
                        y,
                        TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                    break;
                }

                /*
                判断是否在滑动控件里
                如果在滑动控件里,则延迟响应down事件
                如果不在滑动控件里,则标记按下状态
                */
                //是否为滑动控件
                boolean isInScrollingContainer = isInScrollingContainer();
                if (isInScrollingContainer) {
                    mPrivateFlags |= PFLAG_PREPRESSED;
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPendingCheckForTap.x = event.getX();
                    mPendingCheckForTap.y = event.getY();
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                } else {
                    setPressed(true, x, y);
                    checkForLongClick(
                        ViewConfiguration.getLongPressTimeout(),
                        x,
                        y,
                        TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                //处理CANCEL事件
                
                //重置状态
                if (clickable) {
                    setPressed(false);
                }
                removeTapCallback();
                removeLongPressCallback();
                mInContextButtonPress = false;
                mHasPerformedLongPress = false;
                mIgnoreNextUpEvent = false;
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                break;
            case MotionEvent.ACTION_MOVE:
                //处理MOVE事件

                /*
                如果手指移出界时,状态全部重置
                */
                if (!pointInView(x, y, touchSlop)) {
                    removeTapCallback();
                    removeLongPressCallback();
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        setPressed(false);
                    }
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                }

                //如果用力按下时,会触发长按
                final boolean deepPress =
                    motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
                if (deepPress && hasPendingLongPressCallback()) {
                    removeLongPressCallback();
                    checkForLongClick(
                        0 /* send immediately */,
                        x,
                        y,
                        TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
                }
                break;
        }
        return true;
    }
    return false;
}
//处理点击事件,在up事件时才会触发
private boolean performClickInternal() {
    notifyAutofillManagerOnClick();
    return performClick();
}
//执行点击事件
public boolean performClick() {
    notifyAutofillManagerOnClick();

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

    notifyEnterOrExitForAutoFillIfNeeded(true);

    return result;
}     

ViewGroup事件分发流程

在这里插入图片描述

ViewGroup#dispatchTouchEvent()
  • 判断是否拦截,如果down事件发生时或mFirstTouchTarget!=null,会执行onInterceptTouchEvent(),如果ViewGroup消费该事件,mFirstTouchTarget会设置为null

  • mFirstTouchTarget是提供给move和up事件使用的

  • 当View一旦开始拦截,那么后续事件就全部交给它处理,不再执行onInterceptTouchEvent()

public boolean dispatchTouchEvent(MotionEvent ev) {

    boolean handled = false;

    //过滤安全事件
    if (onFilterTouchEventForSecurity(ev)) {
        /*
        如果接收down事件,说明是一次新的事件序列
        清空mFirstTouchTarget引用、重置状态
        */
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        /*
        判断是否拦截
        当发生down事件,且mFirstTouchTarget!=null,down事件被child消费
        */
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
            //disallowIntercept由requestDisallowInterceptTouchEvent()设置
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            //默认情况会进入该方法
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action);
            } else {
                intercepted = false;
            }
        } else {
            //消费了down事件,后续事件也被拦截
            intercepted = true;
        }

        //检查是否为cancel事件
        final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;
    }
}
  • 当ViewGroup不拦截时,会执最终行child.dispatchTouchEvent(event)将事件分发给子View
//不取消和不拦截时调用
if (!canceled && !intercepted) {
    //遍历所有子View
    for (int i = childrenCount - 1; i >= 0; i--) {
        final int childIndex = getAndVerifyPreorderedIndex(
            childrenCount, i, customOrder);
        final View child = getAndVerifyPreorderedView(
            preorderedList, children, childIndex);
        
        //子View不能接收事件,或不在范围内   不在执行
        if (!child.canReceivePointerEvents()
            || !isTransformedTouchPointInView(x, y, child, null)) {
            continue;
        }

        newTouchTarget = getTouchTarget(child);
        if (newTouchTarget != null) {
            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;
        }
        ev.setTargetAccessibilityFocus(false);
    }
    if (preorderedList != null) preorderedList.clear();
}
//如果ViewGroup没有子View,则调用super.dispatchTouchEvent()
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                                              View child, int desiredPointerIdBits) {
    final boolean handled;

    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;
    }
}
if (mFirstTouchTarget == null) {
    //分发给自己的onTouchEvent()
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
                                            TouchTarget.ALL_POINTER_IDS);
} else {
    while (target != null) {
        final TouchTarget next = target.next;
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {

        } else {           
            if (cancelChild) {
                if (predecessor == null) {
                    mFirstTouchTarget = next;
                } else {
                    predecessor.next = next;
                }
                continue;
            }
        }
    }
}
ViewGroup#onInterceptTouchEvent()
//onInterceptTouchEvent()默认返回false,不进行拦截
public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
        && ev.getAction() == MotionEvent.ACTION_DOWN
        && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
        && isOnScrollbarThumb(ev.getX(), ev.getY())) {
        return true;
    }
    return false;
}

总结

事件由上而下的传递规则

事件产生后先由Activiyt处理,传递给Window再传递给顶层的ViewGroup,一般事件传递只考虑ViewGroup的onInterceptTouchEvent(),因为我们不会重写dispatchTouchEvent()

对于根ViewGroup,事件首先传递给它的dispatchTouchEvent(),如果该ViewGroup的onInterceptTouchEvent()返回true,则表示它要拦截这个事件,事件会由它的onTouchEvent()处理,如果onInterceptTouchEvent()返回false,则表示不拦截事件,事件交给它的子View的dispatchTouchEvent()处理,如此反复,最终传递到最底层的View,一般情况最终会调View的onTouchEvent()

事件由下而上的传递规则

事件传到最底层的View,如果它的onTouchEvent()返回true,则表示消费事件,如果返回false则表示该View不处理,事件则会传递给父View的onTouchEvent()处理,如果父View的onTouchEvent()仍然返回false,则继续往上传递,如此反复。

在这里插入图片描述

常见问题

onTouch & onTouchEvent & onClick关系

  • 如果mOnTouchListener不为null,会执行mOnTouchListener.onTouch(),返回值为true时,表示消费该事件
  • 如果mOnTouchListener为null,或者 mOnTouchListener.onTouch()返回值为false,会调用View#onTouchEvent(),返回值为true时,表示消费该事件
  • View#onTouchEvent()处理up事件时,会调用onClick()
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值