Android View事件分发机制总结

525 阅读3分钟
一、MotionEvent
  • DOWN -> MOVE (多次) -> UP 是一个完整的动作序列

  • 补充:

    ACTION_CANCEL 已经废弃不用,可当做 ACTION_UP 处理,源码如下

    /**
     * Constant for {@link #getActionMasked}: The current gesture has been aborted.
     * You will not receive any more points in it.  You should treat this as
     * an up event, but not perform any action that you normally would.
     */
    public static final int ACTION_CANCEL = 3;
二、分发过程重要源码解析

Activity 分发逻辑核心代码

class Activity {

    //如果所有子 view 都不消费事件,则通过 Activity#onTouchEvent 消费
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (getWindow().superDispatchTouchEvent(ev)) { // PhoneWindow#superDispatchTouchEvent -> DecorView#superDispatchTouchEvent -> FrameLayout#dispatchTouchEvent -> ViewGroup#dispatchTouchEvent
            return true;
        } else {
            return onTouchEvent(ev);
        }
    }
}

ViewGroup 分发逻辑核心代码

class ViewGroup extends View implements ViewParent {
    
    public boolean dispatchTouchEvent(MotionEvent ev) {
            boolean handled = false;
 
            // 1. ACTION_DOWN -> 事件序列的开始,重置到初始状态.
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                cancelAndClearTouchTargets(ev); //重置 mFirstTouchTarget = null
                resetTouchState(); //重置 touch state,最主要的一点就是重置 FLAG_DISALLOW_INTERCEPT 状态,也就决定了 child 无法阻止 parent 拦截 DOWN 事件
            }
            
            // 2. Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN //点下或mFirstTouchTarget != null检查是否需要拦截
                    || 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 {// 当没有触摸targets,且不是down事件时,开始持续拦截触摸。
                intercepted = true;
            }
            
            // 3. 如果不取消,不拦截,则分发事件
            if (!cancel && !intercepted) {
                for (int j = childrenCount; j>=0 ; j--) { //由上层到下层遍历
                View child = mChildren[index];
                
                if (!canReceiveEvent(child))
                    break; //如果 child 不能接收事件,跳出循环,不能接收事件的条件为,事件坐标不在 child 范围,或 child 正在执行动画
                
                if (dispatchTransformedTouchEvent(child) { //如果 child 消费了事件
                    addTouchTarget(); //赋值 mFirstTouchTarget
                    break; //跳出循环
                }
                
                if (mFirstTouchTarget == null) { //事件传递到最后一个child,并且child是ViewGroup,或者ViewGroup拦截了事件,mFirstTouchTarget 一直都不会被赋值,所以后续的所有事件都会被ViewGroup处理
                    handled = dispatchTransformedTouchEvent(null);
                }
            }
            
            // 4.返回结果,如果是 false,需要传到 activity 处理
            return handled;
    }
    
    private boolean dispatchTransformedTouchEvent(View child) {
        if (child == null) { // child is null, call View#dispatchTouchEvent
            return super.dispatchTouchEvent();
        } else {
            return child.dispatchTouchEvent();
        }
    }
}

View 的分发核心逻辑

class View {
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean result;
        
        if (mOnTouchListener != null && mOnTouchListener.onTouch()) {//OnTouchListener优先级较高
            result = true;
        }
        
        if (!result && onTouchEvent(ev)) {
            result = true;
        }
        
        return result;
    }
    
    public boolean onTouchEvent(MotionEvent ev) {
        if (clickable || longClickable) {
            switch(ev.getAction) {
                // ... 处理各种事件的逻辑,比如一次点击后会触发 performClick -> clickListener.onClick(),完成了最熟悉的点击监听回调
            }
            
            return true;
        }
    }
}
小结
  1. 事件传递到附属在Activity的Window上,然后Window将事件交给DecorView(FrameLayout)处理。

  2. DecorView也是ViewGroup,事件传递到ViewGroup中。

  3. 首先进入ViewGroup的dispatchTouchEvent()中,然后进入onInterceptTouchEvent(),如果返回true,表示需要拦截此次事件,则会执行ViewGroup的onTouchEvent()。如果返回false,表示不需要拦截事件,则会从上层到下层遍历子View并进入dispatchTransformedTouchEvent(child)。

  4. dispatchTransformedTouchEvent()会判断传入childView是否为null,如果为null,则之行super.dispatchTouchEvent(),即执行View的事件分发。如果不为null,则会执行childView的事件分发。

  5. 事件最终总会传到一个 View 或 ViewGroup,需要相对应的 onTouchEvent()处理后的返回值,如果返回true,表示事件已经处理,则ViewGroup会break对子View的遍历并会给FirstTouchTarget赋值。如果返回false,表示事件没有处理,就继续遍历其他子View进行处理,如果所有子View处理都返回了false,就会执行dispatchTransformedTouchEvent(null),也就是执行super.dispatchTouchEvent(),也就是View的事件分发。

  6. 事件传递到View的dispatchTouchEvent(),首先会进入mOnTouchListenner.onTouch(),如果进入onTouch()且返回true,则dispatchTouchEvent()返回true,表示事件已经消费。如果没进入或返回false,则继续执行onTouchEvent(),在此方法中会处理相应的事件,如onClick。

  7. 处理事件后,只要View满足clickable or longClickble 那么onTouchEvent()都会返回true(View是disable的也无所谓)。如果返回false表示事件没能处理,则会交给父级View处理,如果一直都返回false,则会回到Activity的onTouchEvent()处理事件。

补充:onInterceptTouchEvent()执行前提,ACTION_DOWN || mFirstTouchTarget != null。也就是说DOWN事件一定会进入拦截方法中(child#requestDisAllowIntercept也没用),而且一旦拦截后,之后的一系列事件都会交给此控件处理,因为mFirstTouchTarget不会被赋值。