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 表示不拦截,下发个子viewrequestDisallowInterceptTouchEvent()可以处理父布局不拦截,这是ViewGroup的方法