触摸事件类型
触摸事件对应的类是 MotionEvent 类,事件的主要类型有以下四种:
- ACTION_DOWN 手指刚接触屏幕
- ACTION_MOVE 手指在屏幕上滑动
- ACTION_UP 手指从屏幕上抬起
- ACTION_CANCEL 非人为因素取消ß
View 事件分发的本质就是将 MotionEvent 类分发给具体某个View 处理的过程,一次完整的事件响应包含 一个 ACTION_DOWN 事件,中间任意个 ACTION_MOVE 事件,以及最后的 ACTION_UP 事件或者 ACTION_CANCEL 事件。
其中 ACTION_CANCEL 事件的触发会在以下两种情况下发生:
- 如果在父View中拦截ACTION_UP或ACTION_MOVE,在第一次父视图拦截消息的瞬间,父视图指定子视图不接受后续消息了,同时子视图会收到ACTION_CANCEL事件。
- 如果触摸某个控件,但是又不是在这个控件的区域上抬起(移动到别的地方了),就会出现action_cancel。
事件分发的对象
具备事件分发能力的对象有以下三种:
- Activity 控制当前页面的生命周期,以及事件分发的第一层View
- ViewGroup 一组View的集合,包含多个 View 或者 ViewGroup
- View 所有 UI 组件的基类
事件分发的大致流程:当手指触碰到屏幕时,系统底层会发出一个信号,最终到达 Activity,由Activity向下把事件分给下层的ViewGroup,再由下层的ViewGroup向下分发,直到找到可以处理当前事件的视图,并把后续事件也交给其处理。
事件分发的方法
事件分发的过程由三个主要的方法完成:
- dispatchTouchEvent:分发。返回 true 表示事件被当前 View 消费掉;返回 false 表示交给父类去处理,返回 super.dispatchTouchEvent 表示继续分发下去。
- onInterceptTouchEvent:判断事件是否拦截(只存在于ViewGroup中)
- onTouchEvent:事件的处理
事件分发流程
Activity 中的 分发过程
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// 交给 DecorView 去分发事件
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// 如果 DecorView 没有处理这个事件,将事件交给自己处理
return onTouchEvent(ev);
}
//
public boolean onTouchEvent(MotionEvent event) {
// 针对 类似 DialogActivity,点击了外部区域,关闭当前的 Activity
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
ViewGroup中的事件分发
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
//
if (onFilterTouchEventForSecurity(ev)) {
...
// 判断是否需要拦截这个事件
final boolean intercepted;
// DOWN 事件,或者已经有相应视图处理过之前的事件
// mFirstTouchTarget 主要是用来记录 从 顶层容器 到 处理这个 事件的 一个View 的链路
// 第二次的事件分发 就直接走这个 链路进行分发
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 {
// 没有找到处理过事件的视图,并且也不是 down 事件
intercepted = true;
}
...
if (!canceled && !intercepted) {
...
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
...
// 判断哪些子View可以接收这个的事件分发
}
}
// 创建 事件分发的链表
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;
}
}
}
return handled;
}
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// 找到能处理这个 Event 的 子View
final ArrayList<View> 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);
// 判断这个子View 是否能处理这个 Event
if (!canViewReceivePointerEvents(child)
|| !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);
// 往子View做分发
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
// 搭建事件分发处理链表
newTouchTarget = addTouchTarget(child, idBitsToAssign);
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;
}
}
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
...
if (child == null) {
// 如果已经没有子View 了,调用父类的 分发方法,这里一般就直接调用了 View 的分发方法
handled = super.dispatchTouchEvent(transformedEvent);
} else {
...
// 传给子View 去进行分发
handled = child.dispatchTouchEvent(transformedEvent);
}
return handled;
}
View 中的事件分发
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
// 当前 View 是否可见 或者是否被遮挡
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
ListenerInfo li = mListenerInfo;
// 优先响应 onTouchListener 事件
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// onTouchListener 事件 返回 false,响应自身的 onTouchEvent 事件
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
public boolean onTouchEvent(MotionEvent event) {
// 判断当前View 是否可点击 可长按,如果 可点击可长按的话,最终一定 return true
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
// 有设置代理走 代理方法
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
...
return true;
}
return false;
}
ACTION_DOWN
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
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 {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
// 通过 Handler 发送一个 500ms 的延迟消息,在 CheckForLongPress run 方法中将
// mHasPerformedLongPress 标志位设置为 ture 代表长按方法已经执行
private void checkForLongClick(int delayOffset, float x, float y) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.setAnchor(x, y);
mPendingCheckForLongPress.rememberWindowAttachCount();
mPendingCheckForLongPress.rememberPressedState();
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
}
ACTION_CANCEL
if (clickable) {
setPressed(false);
}
// 移除 DOWN 事件时,通过Handler发出的消息,不在触发 LongClick
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
ACTION_MOVE
if (clickable) {
drawableHotspotChanged(x, y);
}
// 滑动到外部去了
if (!pointInView(x, y, mTouchSlop)) {
// 移除 DOWN 事件时,通过Handler发出的消息,不在触发 LongClick
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
ACTION_UP
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// 如果长按事件没有响应,这个时候来执行点击事件
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// 移除 DOWN 事件时,通过Handler发出的消息,不在触发 LongClick
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
removeTapCallback();
}