Android事件分发机制

373 阅读4分钟

事件分发机制

事件分发机制的两个阶段:

  • 分发:事件从父视图往子视图分发,被拦截后不再传递,进入回溯阶段
  • 回溯:事件从子视图往父视图回溯,被消费后不再回溯

关键方法:

  • ViewGroup.dispatchTouchEvent 往子视图分发事件
  • ViewGroup.onInterceptTouchEvent 返回 true 表示拦截分发事件,不再传递,进入当前视图 onTouchEvent
  • View.dispatchTouchEvent 默认事件分发,调用 onTouchEvent
  • View.onTouchEvent 通常重载此方法处理事件,返回 true 表示消费事件,不再传递,返回 false 往上回溯
  • ViewParent.requestDisallowInterceptTouchEvent(true) 可以确保事件分发到子视图前不被拦截

假设视图层次为 A.B.C.D,事件分发回溯默认过程为:

A.dispatchTouchEvent
  B.dispatchTouchEvent
    C.dispatchTouchEvent
      D.dispatchTouchEvent
      D.onTouchEvent 
    C.onTouchEvent
  B.onTouchEvent
A.onTouchEvent

假设 B 拦截了事件:

A.dispatchTouchEvent
  B.dispatchTouchEvent -> B.onInterceptTouchEvent 
  B.onTouchEvent
A.onTouchEvent

假设 C.onTouchEvent 消费了事件:

A.dispatchTouchEvent
  B.dispatchTouchEvent
    C.dispatchTouchEvent
      D.dispatchTouchEvent
      D.onTouchEvent 
    C.onTouchEvent  

事件分发机制伪代码:

class Activity { 
    fun dispatchTouchEvent(ev) {
        if (parent.dispatchTouchEvent(ev)) {
            return true
        }
        return onTouchEvent(ev)
    }
    fun onTouchEvent(ev)Boolean {...}
} 

class ViewGroup : View {  
    fun dispatchTouchEvent(ev) { 
        var handled = false
        if (!onInterceptTouchEvent(ev)) {
            handled = child.dispatchTouchEvent(ev)
        } 
        return handled || super.dispatchTouchEvent(ev) 
    } 
    fun onInterceptTouchEvent(ev)Boolean {...} 
    fun onTouchEvent(ev)Boolean {...}
} 

class View {  
    fun dispatchTouchEvent(ev) { 
        var result = false
        if (handleScrollBarDragging(ev)) {
            result = true
        }
        if (!result && mOnTouchListener.onTouch(ev)) {
            result = true
        } 
        if (!result && onTouchEvent(ev)) {
            result = true
        }
        return result
    } 
    fun onTouchEvent(ev)Boolean {...}
}

ViewGroup.dispatchTouchEvent 源码分析

  1. 开始ACTION_DOWN 事件开始一个新的事件序列,清除之前触摸状态
  2. 拦截
    2.1. 非 ACTION_DOWN 事件如果当前没有子视图消费事件,表示事件序列已被拦截
    2.2. 事件未被拦截且子视图未申请禁止拦截时,再通过 onInterceptTouchEvent 尝试拦截事件
  3. 分发:如果事件未被拦截也未被取消,就遍历子视图分发事件,并寻找当前事件的触摸目标
    3.1. 在触摸目标链表中找到了可以消费当前事件的视图触摸目标 -> 将其标记为当前触摸目标,延迟到步骤4分发事件给它
    3.2. 一个不在触摸目标链表中的视图消费了事件 -> 将其标记为当前触摸目标,并设置为触摸目标链表表头
    3.3. 未找到消费当前事件的视图,但触摸目标链表不为空 -> 将触摸目标链表末端标记为当前触摸目标
  4. 分发:触摸目标链表不为空,则遍历触摸目标链尝试传递事件或取消触摸目标(事件被拦截)
  5. 回溯:触摸目标链表为空(当前没有子视图消耗事件序列),则将事件转发给基类 dispatchTouchEvent 处理

注:触摸目标(ViewGourp.TouchTarget) 描述一个被触摸的子视图和它捕获的指针ids


public boolean dispatchTouchEvent(MotionEvent ev) {
    // 省略代码 ...
    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
     
        if (actionMasked == MotionEvent.ACTION_DOWN) { 
            // 1. `ACTION_DOWN` 事件开始一个新的事件序列,清除之前触摸状态 ...
        }
        // 省略代码 ... 
        final boolean intercepted;
        // 2. 拦截
        if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                // 2.2. 事件未被拦截且子视图未申请禁止拦截时,再通过 onInterceptTouchEvent 尝试拦截事件
                intercepted = onInterceptTouchEvent(ev);
                // 省略代码 ...
            } else {
                intercepted = false;
            }
        } else { 
            // 2.1. 非 `ACTION_DOWN` 事件如果当前没有子视图消费事件,表示事件序列已被拦截
            intercepted = true;
        }
        // 省略代码 ... 
        if (!canceled && !intercepted) {
            // 省略代码 ... 
                    // 3. 分发:如果事件未被拦截也未被取消,就遍历子视图分发事件,并寻找当前事件的触摸目标
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        // 省略代码 ... 
                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) { 
                            // 3.1. 在触摸目标链表中找到了可以消费当前事件的视图触摸目标 -> 将其标记为当前触摸目标,延迟到步骤4分发事件给它
                            // 省略代码 ... 
                            break;
                        }
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // 省略代码 ...
                            // 3.2. 一个不在触摸目标链表中的视图消费了事件 -> 将其标记为当前触摸目标,并设置为触摸目标链表表头
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
                        // 省略代码 ...
                    }
                    
                if (newTouchTarget == null && mFirstTouchTarget != null) {
                    // 3.3. 未找到消费当前事件的视图,但触摸目标链表不为空 -> 将触摸目标链表末端标记为当前触摸目标 
                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            // 省略代码 ... 
        }

        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) {
            // 5. 回溯:触摸目标链表为空(当前没有子视图消耗事件序列),则将事件转发给基类 dispatchTouchEvent 处理
            handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
        } else {
            // 省略代码 ... 
            // 4. 分发:触摸目标链表不为空,则遍历触摸目标链尝试传递事件或取消触摸目标(事件被拦截)
            TouchTarget target = mFirstTouchTarget;
            while (target != null) { 
                final TouchTarget next = target.next; 
                // 省略代码 ... 
                    if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
                        handled = true;
                    } 
                // 省略代码 ... 
                target = next;
            }
        }
        // 省略代码 ... 
    }
    // 省略代码 ...
    return handled;
}

View.dispatchTouchEvent 和 View.onTouchEvent 源码分析

  • 滚动条消费鼠标事件
  • OnTouchListener 消费触摸事件
  • onTouchEvent 消费触摸事件
    • TouchDelegate 消费触摸事件
public boolean dispatchTouchEvent(MotionEvent event) {
    // 省略代码 ...
    boolean result = false;
    
    // 省略代码 ... 
    if (onFilterTouchEventForSecurity(event)) {
        // 滚动条消费鼠标事件
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        } 
        // OnTouchListener 消费触摸事件
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        } 
        // View默认的事件处理逻辑,事件可能在其中被设置的 TouchDelegate 消费
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    // 省略代码 ... 
    return result;
} 

public boolean onTouchEvent(MotionEvent event) {
    // 省略代码 ... 
    if (mTouchDelegate != null) {
        // TouchDelegate 消费触摸事件
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }
    // 省略代码 ... 
    return false;
}