Android 事件分发源码分析

113 阅读6分钟

Android 事件分发一直是我头疼的地方 什么分发,拦截 处理,响应 头大.最近用到了再次尝试总结一下关于Android中ViewGroup,View 中事件分发的那些事儿.

注意:

事件分发遵循,儿干爹不干原则 递归向下原则

流程:

Activity.dispatchTouchEvent()
    ↓
Window.superDispatchTouchEvent()
    ↓
DecorView.dispatchTouchEvent()  [ViewGroup]
    ↓
ViewGroup.dispatchTouchEvent()
    ↓
    |-- ViewGroup.onInterceptTouchEvent()
    |   ↓
    |   true: 事件拦截,交给自身 onTouchEvent()
    |   false: 继续传递给子 View
    ↓
子 View.dispatchTouchEvent()
    ↓
    |-- 子 View 是 ViewGroup: 重复上述流程
    |-- 子 View 是 View: 调用 View.onTouchEvent()
    ↓
View.onTouchEvent()
    ↓
    |-- true: 事件被消费,终止传递
    |-- false: 不处理

结果:

  • 拦截

    ViewGroup->(ViewGroup)dispatchTouchEvent->(ViewGroup)onInterceptTouchEvent(true)->(ViewGroup)onTouchEvent

  • 不拦截

    ViewGroup->(ViewGroup)dispatchTouchEvent->(ViewGroup)onInterceptTouchEvent(false)->onTouchEvent

1:核心方法说明

1.1 ViewGroup核心方法:

  • dispatchTouchEvent 分发
  • onInterceptTouchEvent 拦截 true 表示拦截 false 表示不拦截
  • addTouchTarget() 添加触摸事件
  • dispatchTransformedTouchEvent() 事件分发的核心方法
  • requestDisallowInterceptTouchEvent() 子view 调用此方法可以通知父view不拦截
1.1.1 dispatchTouchEvent() 分发触摸事件
@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
     
        boolean handled = false;
		// 安全性校验 过滤无用事件
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;
			
	    // 核心1:ACTION_DOWN,清理之前触摸状态
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
	    // 关键变量  intercepted 是否拦截
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
	            // 核心2: 是否允许拦截  关联 requestDisallowInterceptTouchEvent()方法 子view控制父view是否拦截
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
				// 核心3: 关键代码  根据 onInterceptTouchEvent() 方法是否拦截事件  自定义的Viewgroup 需要实现的方法(假如拦截需要实现 且返回true)
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }

          
            // 检查是否是 cancel 状态
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

	      // 核心 4: 判断 不是取消 不拦截 就将事件分发子View
            if (!canceled && !intercepted) {
          
				for (int i = childrenCount - 1; i >= 0; i--) {
							
					// 核心 5调用子View的dispatchTouchEvent分发事件
					if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
						
						// 核心 6: 记录该子View为新的触摸目标 mFirstTouchTarget 这时就不未null 了
						newTouchTarget = addTouchTarget(child, idBitsToAssign);
						alreadyDispatchedToNewTouchTarget = true;
						break;
					}
				}		
                  
            }

          	// 核心 7: 是否子view处理了触摸
			// 关键属性 mFirstTouchTarget 子View 是否处理了触摸  null 的时候是未处理  
            if (mFirstTouchTarget == null) {
			// 子view 未处理就分发   注意null
                handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
            } else {
			   // 子view 处理了 后续操作
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                ...
                }
            }

        }
		
		....
		
        return handled;
    }
1.1.2 onInterceptTouchEvent() 是否拦截触摸事件

此事件一般需要自定义ViewGroup 重写

  • true 表示拦截
  • 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;
}

1.1.3 addTouchTarget() 添加触摸目标

当事件分发给子View 并且子View 响应了(onTouchEvent 返回true) 此时父容器就不需要处理了(儿干爹不干)

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
    final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    // 当事件分发不拦截下发给View且View onTouchEvent()返回true 的时候表示子View 已经处理 父的容器就不能拦截了
    mFirstTouchTarget = target;
    return target;
}
1.1.4 dispatchTransformedTouchEvent() 真正的分发逻辑

事件分发的实际执行方法 当 child=null 的时候会调用View 的分发方法

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

      
        if (child == null) {
	   // 调用的是父类View 的分发方法 ()
            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());
            }
			// 子View 分发
            handled = child.dispatchTouchEvent(transformedEvent);
        }

       
        return handled;
    }
1.1.4 requestDisallowInterceptTouchEvent() 子View通知父类不拦截

ViewGroup dispatchTouchEvent() 方法中

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;'

搭配此方法判断是否拦截



 public boolean dispatchTouchEvent(MotionEvent ev) {
      
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
         
            // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
				// 处理是否拦截 mGroupFlags 搭配 requestDisallowInterceptTouchEvent() 方法
                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 {
       
                intercepted = true;
            }
		}
		...

}

public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
        // We're already in this state, assume our ancestors are too
        return;
    }

    if (disallowIntercept) {
        mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
    } else {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }

    // Pass it up to our parent
    if (mParent != null) {
        mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
    }
}




1.2 View核心方法:

  • dispatchTouchEvent 分发
  • onTouchEvent 处理 true 才会有后续事件 false 无后续事件
  • requestDisallowInterceptTouchEvent() 子view控制父view 是否拦截
1.2.1 dispatchTouchEvent() 分发

View的分发会处理 滑动和OnTouchListener 事件 然后在做分发

 public boolean dispatchTouchEvent(MotionEvent event) {

		...
		
		 boolean result = false;
        if (onFilterTouchEventForSecurity(event)) {
	         // 滚动条拖动优先消费 如果这个 View 是启用状态(ENABLED),并且发生了拖动滚动条的行为,则直接消费事件
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            
	          // onTouchListener 优先处理 如果开发者通过 setOnTouchListener() 设置了监听器,则优先调用它
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
			// 分发onTouchEvent 假如自定义ViewGroup 重写了onTouchEvent 执行是重写的  未重写执行View 的
			//onTouchEvent 返回true 才表示消费了 返回false 表示不处理
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        ...
		
        return result;
    }
1.2.2 onTouchEvent() 触摸事件处理
public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int action = event.getAction();
    final int viewFlags = mViewFlags;

    // 是否支持点击或长按
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE);

    if (clickable) {
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mHasPerformedLongPress = false;

                boolean isInScrollingContainer = isInScrollingContainer();

                if (isInScrollingContainer) {
                    mPrivateFlags |= PFLAG_PREPRESSED;
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPendingCheckForTap.x = x;
                    mPendingCheckForTap.y = y;
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                } else {
                    setPressed(true, x, y);
                    checkForLongClick(
                        ViewConfiguration.getLongPressTimeout(),
                        x, y,
                        TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                }
                break;

            case MotionEvent.ACTION_MOVE:
                int touchSlop = mTouchSlop;
                if (!pointInView(x, y, touchSlop)) {
                    removeTapCallback();
                    removeLongPressCallback();
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        setPressed(false);
                    }
                }
                break;

            case MotionEvent.ACTION_UP:
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || (mPrivateFlags & PFLAG_PREPRESSED) != 0) {
                    boolean focusTaken = false;
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                        focusTaken = requestFocus();
                    }
                    if ((mPrivateFlags & PFLAG_PREPRESSED) != 0) {
                        setPressed(true, x, y);
                    }
                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        removeLongPressCallback();
                        if (!focusTaken) {
                            mPerformClick = new PerformClick();
                            if (!post(mPerformClick)) {
                                performClickInternal();
                            }
                        }
                    }
                    // 延迟取消按下状态
                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }
                    if ((mPrivateFlags & PFLAG_PREPRESSED) != 0) {
                        postDelayed(mUnsetPressedState,
                                ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        mUnsetPressedState.run();
                    }
                    removeTapCallback();
                }
                mIgnoreNextUpEvent = false;
                break;

            case MotionEvent.ACTION_CANCEL:
                setPressed(false);
                removeTapCallback();
                removeLongPressCallback();
                mHasPerformedLongPress = false;
                mIgnoreNextUpEvent = false;
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                break;
        }
        return true; // 事件被消费
    }
    return false;
}

重要方法说明

View源码分析

// 分发事件
 public boolean dispatchTouchEvent(MotionEvent event) {

		...
		
		 boolean result = false;
        if (onFilterTouchEventForSecurity(event)) {
	         // 滚动条拖动优先消费 如果这个 View 是启用状态(ENABLED),并且发生了拖动滚动条的行为,则直接消费事件
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            
	          // onTouchListener 优先处理 如果开发者通过 setOnTouchListener() 设置了监听器,则优先调用它
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
			// 分发onTouchEvent 假如自定义ViewGroup 重写了onTouchEvent 执行是重写的  未重写执行View 的
			//onTouchEvent 返回true 才表示消费了 返回false 表示不处理
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        ...
		
        return result;
    }
    
 // 触摸事件   
 public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int action = event.getAction();
    final int viewFlags = mViewFlags;

    // 是否支持点击或长按
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE);

    if (clickable) {
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mHasPerformedLongPress = false;

                boolean isInScrollingContainer = isInScrollingContainer();

                if (isInScrollingContainer) {
                    mPrivateFlags |= PFLAG_PREPRESSED;
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPendingCheckForTap.x = x;
                    mPendingCheckForTap.y = y;
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                } else {
                    setPressed(true, x, y);
                    checkForLongClick(
                        ViewConfiguration.getLongPressTimeout(),
                        x, y,
                        TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                }
                break;

            case MotionEvent.ACTION_MOVE:
                int touchSlop = mTouchSlop;
                if (!pointInView(x, y, touchSlop)) {
                    removeTapCallback();
                    removeLongPressCallback();
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        setPressed(false);
                    }
                }
                break;

            case MotionEvent.ACTION_UP:
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || (mPrivateFlags & PFLAG_PREPRESSED) != 0) {
                    boolean focusTaken = false;
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                        focusTaken = requestFocus();
                    }
                    if ((mPrivateFlags & PFLAG_PREPRESSED) != 0) {
                        setPressed(true, x, y);
                    }
                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        removeLongPressCallback();
                        if (!focusTaken) {
                            mPerformClick = new PerformClick();
                            if (!post(mPerformClick)) {
                                performClickInternal();
                            }
                        }
                    }
                    // 延迟取消按下状态
                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }
                    if ((mPrivateFlags & PFLAG_PREPRESSED) != 0) {
                        postDelayed(mUnsetPressedState,
                                ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        mUnsetPressedState.run();
                    }
                    removeTapCallback();
                }
                mIgnoreNextUpEvent = false;
                break;

            case MotionEvent.ACTION_CANCEL:
                setPressed(false);
                removeTapCallback();
                removeLongPressCallback();
                mHasPerformedLongPress = false;
                mIgnoreNextUpEvent = false;
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                break;
        }
        return true; // 事件被消费
    }
    return false;
}
    

总结

触摸事件分发儿干爹不干,递归向下原则

  • onTouchEvent() true 表示处理有后续 返回false表示不处理 后续不处理
  • onInterceptTouchEvent()返回true表示拦截分发给自己处理 false 表示不拦截,下发个子view
  • requestDisallowInterceptTouchEvent() 可以处理父布局不拦截,这是ViewGroup的方法