Android 事件分发机制
转自:https://lrh1993.gitbooks.io/android_interview_guide/android/basis/Event-Dispatch.html
事件分发涉及到的类 及 方法
事件分发都分发了哪些事件
事件分发流程图
事件分发源码解析
面试题
事件分发涉及到的类 及 方法
类 | dispatchTouchEvent | onInterceptTouchEvent | onTouchEvent |
---|---|---|---|
Activity | ✅ | ❌ | ✅ |
ViewGroup | ✅ | ✅ | ✅ |
View | ✅ | ❌ | ✅ |
dispatchTouchEvent:事件分发
onInterceptTouchEvent:事件拦截
onTouchEvent :处理点击事件
事件分发都分发了哪些事件
- MotionEvent.ACTION_DOWN:按下View(所有事件的开始)
- MotionEvent.ACTION_MOVE:滑动View
- MotionEvent.ACTION_CANCEL:非人为原因结束本次事件
-
MotionEvent.ACTION_UP:抬起View(与DOWN对应)
image.png
事件分发流程图
事件分发流程图.png此图只介绍了down 事件的传递流程,后续的move 和 up 事件还会随着down 传递流程的改变发生相应的变化:
dispatchTouchEvent:
- activity 中,当 Down 返回true,其他情况返回super,则其他事件会传递到activity 的onTouchEvent
- viewGroup中 返回false,则其他事件不会传到此viewgroup,交友父控件的onTouchEvent处理,后续事件不会分发到该view
onTnerceptTouchEvent
- 当返回true 时,会由此viewgroup的onTouchevent处理,后续也不会分发到子view,且不会再处理该控件的onTnerceptTouchEvent(即该方法只要有一次返回true,即不再执行)
- 当Down 返回super/false,其余的返回true,则down 会分发到子view,后续的一个事件会分发到子view中执行,但是此时子view中执行的是cancel事件,随后的事件不会分发到子view,而是由当前view处理
onTouchEvent
- 返回false ,之后的事件不会传到此view进行处理
事件分发源码解析
我们先了解下事件分发三个方法的伪代码:
// 点击事件产生后,会直接调用dispatchTouchEvent()方法
public boolean dispatchTouchEvent(MotionEvent ev) {
//代表是否消耗事件
boolean consume = false;
if (onInterceptTouchEvent(ev)) {
//如果onInterceptTouchEvent()返回true则代表当前View拦截了点击事件
//则该点击事件则会交给当前View进行处理
//即调用onTouchEvent ()方法去处理点击事件
consume = onTouchEvent (ev) ;
} else {
//如果onInterceptTouchEvent()返回false则代表当前View不拦截点击事件
//则该点击事件则会继续传递给它的子元素
//子元素的dispatchTouchEvent()就会被调用,重复上述过程
//直到点击事件被最终处理为止
consume = child.dispatchTouchEvent (ev) ;
}
return consume;
}
接下来我们看下具体的事件传播源码
首先事件由activity 的dispatchTouchEvent 开始
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// 实现屏保功能:
// a. 该方法为空方法
// b. 当此activity在栈顶时,触屏点击按home,back,menu键等都会触发此方法
onUserInteraction();
}
// window 是一个抽象类,只有一个继承类PhoneWindow
// mWindow 初始化 mWindow = new PhoneWindow(this, window, activityConfigCallback);
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
接下来我们看下PhoneWindow 的superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event) {
return
// mDecor 是DecorView一个对象,它的父类是FrameLayout
mDecor.superDispatchTouchEvent(event);
}
DecorView 的 superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event) {
// DecorView 的父类是FrameLayout
return super.dispatchTouchEvent(event);
}
FrameLayout 中没有实现dispatchTouchEvent ,直接使用的ViewGroup 的dispatchTouchEvent。
下面我们来看下dispatchTouchEvent 的流程图:
image.png
所以接下来我们看下viewgroup的分发方法,在这里我们主要介绍其中两个主要的片段:
/**
* 这一段的内容主要是为判断是否拦截。如果当前事件的MotionEvent.ACTION_DOWN,则进入判断,
* 调用ViewGroup onInterceptTouchEvent()方法的值,判断是否拦截。如果mFirstTouchTarget != null,
* 即已经发生过MotionEvent.ACTION_DOWN,并且该事件已经有ViewGroup的子View进行处理了,
* 那么也进入判断,调用ViewGroup onInterceptTouchEvent()方法的值,判断是否拦截。
* 如果不是以上两种情况,即已经是MOVE或UP事件了,并且之前的事件没有对象进行处理,则设置成true,
* 开始拦截接下来的所有事件。这也就解释了如果子View的onTouchEvent()方法返回false,
* 那么接下来的一些列事件都不会交给他处理。如果VieGroup的onInterceptTouchEvent()第一次执行为true,
* 则mFirstTouchTarget = null,则也会使得接下来不会调用onInterceptTouchEvent(),直接将拦截设置为true。
*/
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//disallowIntercept:是否禁用事件拦截的功能(默认是false),即不禁用
//可以在子View通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改,不让该View拦截事件
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 {
// 当没有触摸targets,且不是down事件时,开始持续拦截触摸。
intercepted = true;
}
...
// 该片段作用:遍历了当前ViewGroup下的所有子View 赋值mFirstTouchTarget
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// 如果当前视图无法获取用户焦点,则跳过本次循环
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
// 如果view不可见,或者触摸的坐标点不在view的范围内,则跳过本次循环
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
//孩子在其范围内已经收到了触摸,并退出整个循环。
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//如果触摸位置在child的区域内,则把事件分发给子View或ViewGroup
// 际就是调用子元素的dispatchTouchEvent()方法
/**
* if (child == null) {
* handled = super.dispatchTouchEvent(event);
* } else {
* handled = child.dispatchTouchEvent(event);
* }
*/
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;
}
//获取TouchDown的x,y坐标
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//添加TouchTarget,则mFirstTouchTarget != null。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//表示以及分发给NewTouchTarget
alreadyDispatchedToNewTouchTarget = true;
break;
}
接下来我们看下view 的 dispatchTouchEvent片段
// 有mOnTouchListener 控件是可点击的,若onTouch返回true,则直接返回,不会执行onTouchEvent
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
view 中的onTouch 方法
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//如果该控件是可以点击的就会进入到下两行的switch判断中去;
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
//如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
// 在经过种种判断之后,会执行到关注点1的performClick()方法。
//请往下看关注点1
if ((mPrivateFlags & 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 (!mHasPerformedLongPress) {
// 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)) {
//关注点1
//请往下看performClick()的源码分析
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
mPrivateFlags |= PRESSED;
refreshDrawableState();
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress = false;
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
//如果该控件是可以点击的,就一定会返回true
return true;
}
//如果该控件是不可以点击的,就一定会返回false
return false;
}
performClick :
public boolean performClick() {
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;
}
可以看到其中执行了OnClickListener.onClick,结合上边的代码可以知道onClick 是在ACTION_UP 时执行的,则处于时间传递的最后一个环节
面试题
- 事件传递涉及的方法和在不同组件中的表现
- onTouchListener onTouchEvent onClickListener 执行关系
- 如何拦截事件