这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战
事件分发的概述
事件分发,其实就是对MotionEvent事件的分发过程。此过程由三个很重要的方法来共同完成。dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent
dispatchTouchEvent(分发)
返回值为true表示事件被当前视图消费掉;返回为super.dispatchTouchEvent表示继续分发该事件
onInterceptTouchEvent(拦截)
返回值为true,表示拦截这个事件并交由自身的onTouchEvent方法进行消费;
返回false表示不拦截,需要继续传递给子视图。
如果return super.onInterceptTouchEvent(ev), 事件拦截分两种情况
- 如果该View(ViewGroup)存在子View且点击到了该子View, 则不拦截, 继续分发 给子View 处理, 此时相当于return false。
- 如果该View(ViewGroup)没有子View或者有子View但是没有点击中子View(此时ViewGroup 相当于普通View), 则交由该View的onTouchEvent响应,此时相当于return true。
onTouchEvent(消费)
返回值为true表示当前视图可以处理对应的事件;
返回值为false表示当前视图不处理这个事件,它会被传递给父视图的onTouchEvent方法进行处理。
如果return super.onTouchEvent(ev),事件处理分为两种情况:
- 如果该View是clickable或者longclickable的,则会返回true, 表示消费 了该事件, 与返回true一样;
- 如果该View不是clickable或者longclickable的,则会返回false, 表示不 消费该事件,将会向上传递,与返回false一样.
事件分发的顺序
Activity -> ViewGroup -> View
| 控件 | 分发 | 拦截 | 消费 |
|---|---|---|---|
| Activity | 有 | 有 | |
| ViewGroup | 有 | 有 | 有 |
| View | 有 | 有 |
View的事件分发
进入dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent event) {
。。。
boolean result = false;
。。。。
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
。。。
return result;
}
if(onFilterTouchEventForSecurity(event)),这个主要是判断当前事件到来的时候,窗口有没有被遮挡,如果被遮挡则会直接返回false,从而中断事件的处理。
如果窗口没被遮挡,那么会正常处理事件。
进入ListenerInfo 类中
static class ListenerInfo {
public OnClickListener mOnClickListener;
protected OnLongClickListener mOnLongClickListener;
private OnKeyListener mOnKeyListener;
private OnTouchListener mOnTouchListener;
...
}
这是View里面的一个内部类,定义了一系列的Listener,其中有我们经常用到的onClickListener,这里是获取当前View所设置的Listener。
进入onTouchEvent()方法
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;
//判断了当前View是否可用,如果不可用则进入if体,根据注释我们知道,
//即使是不可 以状态下的View,
//如果它自身是可点击或者可长按的话,一样会消耗事件,只是不作出任何反应罢了。
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
return clickable;
}
//这里判断是否设置了mTouchDelegate,这个表示View的代理,即如果设置了代理,
//那么当前View的点击事件会交给代理的View来处理,调用代理View的onTouchEvent方法,
//如果代理View消耗了事件,那么相当于当前View消耗了事件。
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//首先是判断当前View是否可以点击或者长按,其中一个为true的话,就会进入if内。
//进入if后,是对事件进行判断,可以看到最后会返回true,即事件最后会被消耗。
//也就是说,如果一个View是clickable或者long_clickable的话onTouchEvent
//方法会返回true,把事件消耗掉。
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) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
removeLongPressCallback();
if (!focusTaken) {
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;
。。。
}
return true;
}
return false;
}
我们看看up响应的部分。
首先会判断当前View是否是pressed状态,即按下状态,如果是按下状态就会触发performClick()方法
public boolean performClick() {
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;
}
检测当前View是否设置了onClickListener,如果设置了那么回调它的onClick方法,所以我们平时对一个Button设置点击事件之后,都会在其onTouchEvent方法的ACTION_UP逻辑里面得到回调
这里可以得出结论: onTouchListener>onTouchEvent>onClickListener
小结
- 事件传递给View的时候,会调用dispatchTouchEvent()方法,但是View没有onIntercept方法,所以会接着调用onTouchEvent()方法。
- 如果一个View是可点击的(clickable或long_clickable),那么它默认会消耗事件。对于一个Button来说,默认是可点击的,对于一个textView来说,默认是不可点击的,而对于一个自定义View来说,默认也是不可点击的,可以在xml布局中设置View的点击性质。
- 如果对一个View设置了onClickListener监听,那么确保它的可点击的,而且接收到了ACTION_DOWN和ACTION_UP事件。
ViewGroup的事件分发
进入ViewGroup中找dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
//处理down事件
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
//重置状态
resetTouchState();
}
//检查当前View是否拦截事件
final boolean intercepted;
// ViewGroup在如下两种情况下会判断是否拦截当前事件:事件类型为down或者mFirstTouchTarget != null。
// 当ViewGroup不拦截事件并将事件交由子元素处理时,mFirstTouchTarget会被赋值也就是mFirstTouchTarget != null。
// 这样当move事件和up事件到来时,并且事件已经被分发下去,那么onInterceptTouchEvent这个方法将不会再被调用。
//所以当前ViewGroup拦截事件之后就不会再次调用onInterceptTouchEvent方法;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
// disallowIntercept = 是否禁用事件拦截的功能(默认是false),可通过调用requestDisallowInterceptTouchEvent()修改
if (!disallowIntercept) {
//调用onInterceptTouchEvent方法判断是否拦截当前事件,ViewGroup默认返回false;
//如果拦截事件将intercepted置为true;
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
。。。
// 如果事件未被取消且未被拦截,如果拦截事件会将intercepted置为true;
if (!canceled && !intercepted) {
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
//down事件
if (actionMasked == MotionEvent.ACTION_DOWN||
(split && actionMasked ==MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
。。。。
// 当ViewGroup不拦截事件的时候,事件会向下分发交由它的子View进行处理
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x =isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
final float y =isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
//从上至下寻找一个可以接收该事件的子view
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//遍历所有ViewGroup的所有子元素,然后判断子元素是否收到点击事件
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if (!child.canReceivePointerEvents()|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
// 如果某个子元素满足条件,那么事件就会传递给它处理,
// dispatchTransformedTouchEvent这个方法实际上就是调用子元素的dispatchTouchEvent方法,
// 如果子元素仍然是一个ViewGroup,则递归调用重复此过程。
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
// 如果子元素的dispatchTouchEvent返回true,表示子元素已经处理完事件,
// 那么mFirstTouchTarget就会被赋值同时跳出for循环。
// mFirstTouchTarget的赋值在addTouchTarget内部完成
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// 如果遍历完所有的子元素事件没有被合适处理,有两种情况:
// 1. ViewGroup没有子元素
// 2. 子元素处理了点击事件,但是dispatchTouchEvent返回false
// 这时ViewGroup会自己处理点击事件。
if (mFirstTouchTarget == null) {
// 这里的第三个参数child为null,此时会调用handled = super.dispatchTouchEvent(event),最终会调用ViewGroup自身的onTouchEvent来处理事件;
handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
//将处理该事件的子view复制给target,由子元素来处理该事件
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;
//该方法最终会调用子元素的dispatchTouchEvent,传给给子元素来处理事件
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;
}
}
if (canceled|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//UP事件到来,重置状态,例如将处理该事件的子view mFirstTouchTarget置为null;
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;
}
ViewGroup进行事件分发的时候会调用onInterceptTouchEvent来询问是否拦截事件
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;
}
onInterceptTouchEvent()返回false(即不拦截事件),返回true(即拦截事件)
viewgroup不拦截事件
事件继续向下传递,调用View的dispatchTouchEvent(),将执行View的事件分发(见上面)
Activity的事件分发
点击事件发生线传到activity的dispatchTouchEvent()进行事件分发
public boolean dispatchTouchEvent(MotionEvent ev) {
//事件都是down开始的
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
进入onUserInteraction() 方法
public void onUserInteraction() {
}
该方法是一个空方法。实现屏保功能
当activity在栈顶时,触屏点击按钮home,back,menu键等都会触发这个方法
getWindow().superDispatchTouchEvent(ev)
public Window getWindow() {
return mWindow;
}
获取Window类的对象
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow是PhoneWindow对象
进入PhoneWindow的superDispatchTouchEvent 方法
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
mDecor是一个DecorView对象
mDecor = (DecorView) preservedWindow.getDecorView();
进入DecorView的superDispatchTouchEvent方法
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
调用父类的dispatchTouchEvent方法
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
}
进入FrameLayout类中发现没有dispatchTouchEvent方法
public class FrameLayout extends ViewGroup{
}
进入ViewGroup的事件分发(参考上面)。