一、Window视图层次结构
二、Activity、Window和View之间的关系
三、Android事件分发流程
以下是触摸屏事件在传递分发过程中使用的方法,返回值为true,表示触摸屏事件被消费;否则表示未被消费。
public boolean dispatchTouchEvent(MotionEvent ev);
// 只有ViewGroup及其子类中才有该方法,用于拦截触摸屏事件
public boolean onInterceptTouchEvent(MotionEvent event);
public boolean onTouchEvent(MotionEvent event);
在ViewRootImpl
中,通过mView.dispatchPointerEvent(event)
将触摸事件传递给窗口的根视图DecorView对象处理。mView
虽然是View类型的变量,但它指向DecorView实例对象,这里使用多态。
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
mAttachInfo.mUnbufferedDispatchRequested = false;
mAttachInfo.mHandlingPointerEvent = true;
// 将触摸事件传递给DecorView处理
boolean handled = mView.dispatchPointerEvent(event);
maybeUpdatePointerIcon(event);
maybeUpdateTooltip(event);
mAttachInfo.mHandlingPointerEvent = false;
if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
mUnbufferedInputDispatch = true;
if (mConsumeBatchedInputScheduled) {
scheduleConsumeBatchedInputImmediately();
}
}
return handled ? FINISH_HANDLED : FORWARD;
}
dispatchPointerEvent(MotionEvent event)
方法并没有定义在DecorView
中,而是定义在其父类View
中。当DecorView
对象调用该方法时,又调用自身重写的dispatchTouchEvent()
方法。
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
在DecorView
的dispatchTouchEvent
方法中,又把触摸事件传递到了Activity中。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// Activity实现了Window.Callback接口
// mWindow.getCallback()就是Activity对象
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
我们来看Activity中dispatchTouchEvent
方法的实现,而Activity将事件分发给PhoneWindow处理。
// 它是用来处理触摸屏事件。重写该方法,在所有触摸屏事件被分发给window之前,可以将它们拦截。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// 将事件传递给PhoneWindow处理
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
再看PhoneWindow的superDispatchTouchEvent
方法,它将事件进一步传递到视图层次结构中。视图DecorView是窗口Window的顶层视图。
@Override
public boolean superDispatchTouchEvent(MotionEvent event)
{
return mDecor.superDispatchTouchEvent(event);
}
而DecorView
在superDispatchTouchEvent
方法中,会调用父类ViewGroup
的dispatchTouchEvent
方法进行事件分发,将事件传递给子控件来处理。
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
四、Android事件分发传递默认过程
首先我们来看下系统中,对Android事件分发传递的默认处理过程,在此我们对Touch事件不做任何拦截处理。只在自定义的View中重写事件分发传递的方法,然后添加输出日志。各控件的之间的关系如下图所示:
当我们点击View_EEE控件时,Android系统对触摸Down事件的默认处理过程如下:
// 事件分发传递过程
2020-03-30 12:23:35.688 31060-31060/com.pjm.dom.pet D/LOG_SB: SJDispatchActivity - dispatchTouchEvent - 0
2020-03-30 12:23:35.690 31060-31060/com.pjm.dom.pet D/LOG_SB: SJViewGroup_AAA - dispatchTouchEvent - 0
2020-03-30 12:23:35.690 31060-31060/com.pjm.dom.pet D/LOG_SB: SJViewGroup_AAA - onInterceptTouchEvent - 0
2020-03-30 12:23:35.691 31060-31060/com.pjm.dom.pet D/LOG_SB: SJViewGroup_BBB - dispatchTouchEvent - 0
2020-03-30 12:23:35.692 31060-31060/com.pjm.dom.pet D/LOG_SB: SJViewGroup_BBB - onInterceptTouchEvent - 0
2020-03-30 12:23:35.692 31060-31060/com.pjm.dom.pet D/LOG_SB: SJView_EEE - dispatchTouchEvent - 0
// 事件处理过程
2020-03-30 12:23:35.693 31060-31060/com.pjm.dom.pet D/LOG_SB: SJView_EEE - onTouchEvent - 0
2020-03-30 12:23:35.694 31060-31060/com.pjm.dom.pet D/LOG_SB: SJViewGroup_BBB - onTouchEvent - 0
2020-03-30 12:23:35.695 31060-31060/com.pjm.dom.pet D/LOG_SB: SJViewGroup_AAA - onTouchEvent - 0
2020-03-30 12:23:35.696 31060-31060/com.pjm.dom.pet D/LOG_SB: SJDispatchActivity - onTouchEvent - 0
通过以上输出的日志Log我们可以看出,
- 当点击
View_EEE
控件时,SJDispatchActivity
将Down事件分发给ViewGroup_AAA
控件; -
ViewGroup_AAA
是ViewGroup
的子类,因此ViewGroup_AAA
首先判断自己是否对Down事件进行了拦截处理。若进行了拦截处理,表示Down事件已经自己被消费,则事件分发传递结束; - 若没有,则将Down事件分发给子控件
ViewGroup_BBB
,ViewGroup_BBB
也是ViewGroup
的子类,因此它将与ViewGroup_AAA
对Down事件做相同处理; - 当
ViewGroup_BBB
没有对Down事件进行拦截,则下发给其子控件
View_EEE
,View_EEE
是View
的子类,故它内部没有其他子控件。则将Down事件交给自己的onTouchEvent
方法进行处理; -
View_EEE
对象的onTouchEvent
方法默认调用父类View
的onTouchEvent
方法对Down事件作处理; -
View_EEE
对象的onTouchEvent
方法返回true,表示已消费Down事件。false表示未消费处理,则交给其父控件ViewGroup_BBB
的onTouchEvent
方法处理。 - 若
ViewGroup_BBB
没有对Down事件进行消费处理,则交给其父控件ViewGroup_AAA
的onTouchEvent
方法处理; - 若
ViewGroup_AAA
也未对Down事件处理,则交给SJDispatchActivity
的onTouchEvent
方法处理,若返回false,则未消费Down事件,则将其丢弃;
五、Android事件分发传递机制分析
(一)ViewGroup 事件分发处理机制分析
1. ViewGroup事件分发机制
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// handled用来记录事件是否被消费
boolean handled = false;
// 对事件进行安全过滤
if (onFilterTouchEventForSecurity(ev)) {
// 1. 对事件进行拦截处理
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.
intercepted = true;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
//2. 检测是否为取消事件
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
// If the event is targeting accessibility focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, 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 (!child.canReceivePointerEvents()
|| !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 (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
2. ViewGroup事件拦截机制
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;
}
(五)View的事件分发机制
@UnsupportedAppUsage
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
1. View事件分发处理机制
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
// 该事件首先应该被可访问性的焦点处理
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
// 没有焦点,或没有虚拟的后代有焦点,则不处理该事件
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
// 进行正常的事件分发处理
// View分发事件处理结果:事件是否被消费
// 默认没有被处理
boolean result = false;
// 对输入事件进行一致性校验
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
// 获取正在执行的不包含指针索引信息的操作,如ACTION_DOWN,或ACTION_POINTER_DOWN
final int actionMasked = event.getActionMasked();
// 若该操作是一个Down事件,那么它是一个新的手势事件,则需要停止正在进行的嵌套滚动
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
//为了安全,对触摸事件进行过滤操作,true表示该事件可以被分发,false表示丢弃该事件
if (onFilterTouchEventForSecurity(event)) {
// 1. isEnabled为true,且拖动滚动条事件被处理
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
// 2. isEnabled为true,且设置触摸事件监听mOnTouchListener,onTouch返回true
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// 3. 若1、2两种情况都未处理,则调用onTouchEvent处理触摸事件,且返回true
if (!result && onTouchEvent(event)) {
result = true;
}
}
// 4. 若1、2、3都未处理触摸事件,则通知校验者,触摸事件未被处理,忽略对该事件的跟踪
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
// 当触摸事件结束,或被取消,或者Down事件未被处理,则在嵌套滚动后清理
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
// 返回事件处理结果
return result;
}
2. 触摸屏事件的处理
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
// 是否可点击
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;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
// 将在当前视图中的物理触摸事件委托给其他View处理
if (mTouchDelegate != null) {
// 若代理视图处消费了触摸事件,则返回true
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
// 若是可点,则交给点击监听器OnClickListener处理
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
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)) {
performClickInternal();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_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;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
final int motionClassification = event.getClassification();
final boolean ambiguousGesture =
motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
int touchSlop = mTouchSlop;
if (ambiguousGesture && hasPendingLongPressCallback()) {
final float ambiguousMultiplier =
ViewConfiguration.getAmbiguousGestureMultiplier();
if (!pointInView(x, y, touchSlop)) {
// The default action here is to cancel long press. But instead, we
// just extend the timeout here, in case the classification
// stays ambiguous.
removeLongPressCallback();
long delay = (long) (ViewConfiguration.getLongPressTimeout()
* ambiguousMultiplier);
// Subtract the time already spent
delay -= event.getEventTime() - event.getDownTime();
checkForLongClick(
delay,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
touchSlop *= ambiguousMultiplier;
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, touchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
final boolean deepPress =
motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
if (deepPress && hasPendingLongPressCallback()) {
// process the long click action immediately
removeLongPressCallback();
checkForLongClick(
0 /* send immediately */,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
}
break;
}
return true;
}
return false;
}
private boolean performClickInternal() {
// Must notify autofill manager before performing the click actions to avoid scenarios where
// the app has a click listener that changes the state of views the autofill service might
// be interested on.
notifyAutofillManagerOnClick();
return performClick();
}
public boolean performClick() {
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
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;
}
(一)DecorView
DecorView是ViewGroup的子类,它将调用父类ViewGroup的dispatchTouchEvent
方法进行事件分发处理。
1. DecorView对事件的分发机制
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
2. DecorView对事件的拦截机制
@Override
public boolean onTouchEvent(MotionEvent event) {
return onInterceptTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
if (mHasCaption && isShowingCaption()) {
// Don't dispatch ACTION_DOWN to the captionr if the window is resizable and the event
// was (starting) outside the window. Window resizing events should be handled by
// WindowManager.
// TODO: Investigate how to handle the outside touch in window manager
// without generating these events.
// Currently we receive these because we need to enlarge the window's
// touch region so that the monitor channel receives the events
// in the outside touch area.
if (action == MotionEvent.ACTION_DOWN) {
final int x = (int) event.getX();
final int y = (int) event.getY();
if (isOutOfInnerBounds(x, y)) {
return true;
}
}
}
if (mFeatureId >= 0) {
if (action == MotionEvent.ACTION_DOWN) {
int x = (int) event.getX();
int y = (int) event.getY();
if (isOutOfBounds(x, y)) {
mWindow.closePanel(mFeatureId);
return true;
}
}
}
if (!SWEEP_OPEN_MENU) {
return false;
}
if (mFeatureId >= 0) {
if (action == MotionEvent.ACTION_DOWN) {
Log.i(mLogTag, "Watchiing!");
mWatchingForMenu = true;
mDownY = (int) event.getY();
return false;
}
if (!mWatchingForMenu) {
return false;
}
int y = (int) event.getY();
if (action == MotionEvent.ACTION_MOVE) {
if (y > (mDownY + 30)) {
Log.i(mLogTag, "Closing!");
mWindow.closePanel(mFeatureId);
mWatchingForMenu = false;
return true;
}
} else if (action == MotionEvent.ACTION_UP) {
mWatchingForMenu = false;
}
return false;
}
//Log.i(mLogTag, "Intercept: action=" + action + " y=" + event.getY()
// + " (in " + getHeight() + ")");
if (action == MotionEvent.ACTION_DOWN) {
int y = (int) event.getY();
if (y >= (getHeight() - 5) && !mWindow.hasChildren()) {
Log.i(mLogTag, "Watching!");
mWatchingForMenu = true;
}
return false;
}
if (!mWatchingForMenu) {
return false;
}
int y = (int) event.getY();
if (action == MotionEvent.ACTION_MOVE) {
if (y < (getHeight() - 30)) {
Log.i(mLogTag, "Opening!");
mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, new KeyEvent(
KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU));
mWatchingForMenu = false;
return true;
}
} else if (action == MotionEvent.ACTION_UP) {
mWatchingForMenu = false;
}
return false;
}
(一)Activity
Activity首先将Touch事件分发给PhoneWindow处理,若PhoneWindow中的视图对象没有消费Touch事件,则调用onTouchEvent处理。
// 它是用来处理触摸屏事件。重写该方法,在所有触摸屏事件被分发给window之前,可以将它们拦截。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// 将事件传递给PhoneWindow处理
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
六、Android事件分发机制总结
七、参考资源
PhoneWindow
DecorView
ActivityManagerService
WindowManagerService
ViewRootImpl
SystemServer
原来Android触控机制竟是这样的
从源码角度看 Android 触摸事件分发