Java是面向对象的编程语言, 所以它把用户和屏幕的一次交互封装成了一个对象,这个就是MotionEvent
而我们对屏幕的交互一般来说,不外乎这么三个动作:
- 按下
- 移动
- 抬起
这三个动作分别对应了MotionEvent中的三个int值
- ACTION_DOWN 对应了我们手指刚按下的动作
- ACTION_MOVE 对应了我们手指在屏幕上移动的动作
- ACTION_UP 对应了我们手指从屏幕上抬起的动作
而一次交互,会产生至少一个ACTION_DOWN事件,若干个连续的ACTION_MOVE事件,和最多一个ACTION_UP事件.一次交互产生的一系列事件是属于同一个事件序列
我们讲的事件分发机制,就是指的经过一系列调用后,由谁来响应这一系列事件. 分发的开始是发送一个事件ACTION_DOWN.
如果事件被传递给了一个view,那么一定会调用到这个方法,它的返回值会由view中的onInterceptTouchEvent这个方法和下一级View的dispatchTouchEvent方法来决定,
具体来说就是一个if else条件判断,
先判断当前view的onInterceptTouchEvent方法的返回值,
如果为true,那么就表示拦截了这个事件:意思是这个事件其他的view就不用管了, 我来处理就好. 然后就会将MOtionEvent传递给当前view处理机制
否则就调用下一级view的dispatchTouchEvent方法,并将下一级View的该方法返回值返回.
View有这个方法。
这个方法的返回值表示,传递给当前view的方法是不是要拦截下来,由自己来处理,返回true的话,表示消费了这个事件:意思是这个事件其他的view就不用管了, 我来处理就好.
注意:View没有这个方法,只有ViewGroup有这个方法,且默认返回false,即不拦截事件。
很好理解,只有ViewGroup才有子View,需要判断是否拦截发给子View的事件来交给自身处理。而普通的View不需要没有子View,不需要拦截事件,当事件被分发给一个普通View时,它直接处理即可
这个方法会用来处理传递给当前view的事件,
返回true就表示消耗了这个事件,那么后续的一系列事件就会直接交给它来处理.
返回false,那么同一个事件序列中后续事件就不会受到,然后会把事件交给它的上一级去处理。
View的onTouchEvent默认都会返回true,表示消费了事件。除非他是不可点击的,即clickable和longclickable都为false。
而当MotionEvent被传递给当前view的处理机制后, 那么大致又会进行这样一个调用流程
如果设置了的话,就会调用回调listener中的onTouch方法,然后返回一个布尔值.
否则就直接调用onTouchEvent()
如果是false,那么就会调用onTouchEvent方法.
在onTouchEvent方法中,如果一个View设置了onTouchListener。当点击这个View时,onTouchListener.onClick方法将会被调用
否则就不会onTouchEvent()方法
dispatchTouchEvevnt
开始的 public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
getWindow()
获得的是一个Window对象,Window是一个抽象类,是对窗口这个概念的抽象。它的具体实现是PhoneWindow。我们查看PhoneWindow中的对应方法。
@Override
public boolean superDispatchGenericMotionEvent(MotionEvent event) {
return mDecor.superDispatchGenericMotionEvent(event);
}
其中,mDecor就是DecorView,是View的最顶层View。
private DecorView mDecor;
//通过调用getDecorView就可以获得当前PhoneWindow对应的DecorView
@Override
public final View getDecorView() {
if (mDecor == null) {
installDecor();
}
return mDecor;
}
private void installDecor() {
if (mDecor == null) {
//创建DecorView
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
}
...
}
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
可以看到,我们最后直接new了一个DecorView对象。
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
这个方法中,我们看到了熟悉的dispatchTouchEvent。
DecorView 是一个 ViewGroup,继承自 FrameLayout。所以调用了 FrameLayout 的 dispatchTouchEvent 方法。而DecorView是顶层的ViewGroup,它调用dispatchTouchEvent分发事件,而ViewGroup默认不拦截事件,那么事件就会一层层被传递到最底层的普通View。 接下来我们看看具体的 dispatchTouchEvent
@Override
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事件时,对事件进行初始话。将mFirstTouchTarget置为null,并将FLAG_DISALLOW_INTERCEPT进行重置
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//判断是否需要调用onInterceptTouchEvent
final boolean intercepted;
//(1)(1)(1) 标记,后面讲解会用到
//如果事件是DOWN事件,那么进入if。或者事件不是DOWN事件,mFirstTouchTarget != null,说明子View消费了事件,也进入if
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
/*
1. 判断FLAG_DISALLOW_INTERCEPT这个flag,这个flag在子view中可以调用requestDisallowInterceptTouchEvent来设置。
2.而这个flag在事件为Down事件时,会被进行重置。
3. 这个flag可以使的父View无法拦截除了DOWN事件以外的其他事件
*/
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
/*判断了flag,
1. 事件为DOWN事件,那么一定能进入这个if判断。
2. 如果事件不是DOWN事件,那么只有当子View没有调用requestDisallowInterceptTouchEvent,才会进入if。
*/
if (!disallowIntercept) {
//调用onInterceptTouchEvent来判断是否拦截事件,ViewGroup默认返回flase
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was chnged
} else {
/*if中条件不满足,intercepted为false。表示
1. 事件一定不是DOWN事件并且mFirstTouchTarget != null。
2. 子View调用了requestDisallowInterceptTouchEvent,使得父View不能调用onInterceptTouchEvent来拦截事件。
*/
intercepted = false;
}
} else {
//当事件不为DOWN事件并且mFirstTouchTarget为null时。表示子View没有消费事件。那么后续的一系列事件都不会交给子View了
intercepted = true;
}
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// 事件是否取消检查
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;
//(2)(2)(2) 标记,后面讲解会用到
//事件没有取消,并且intercepted值为flase才进入if
if (!canceled && !intercepted) {
...
//子View的数目
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
//判断子View中哪些是能接到点击事件的,即只有包含了事件发生位置的view才能接收到事件
final ArrayList preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//遍历子View
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;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//将事件分发给子View,dispatchTransformedTouchEvent。具体解析看2.2小节
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();
//当dispatchTransformedTouchEvent返回值为true时,完成对mFirstTouchTarget的赋值。TouchTarget实际上是一个链表数据结构
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
...
}
}
}
/*
1.(1)标记处最后intercepted为flase,那么会进入(2)号标记的if中将事件分发给子View,如果子View最终消费了事件,返回了true,那么mFirstTouchTarget.child会被指向子View,mFirstTouchTarget就不会为null。那么当前这个if判断就进不去
2. 而若 mFirstTouchTarget为null, 说明在DOWN事件中,要么父View调用onInterceptTouchEvent拦截了事件,要么子View没有没有消费DOWN事件。
3. 当子View没有消费事件,则后续一系列的事件都不会交给子View来处理了。
4. 若父View拦截了DOWN事件,并消费处理事件,那么后续连续事件的mFirstTouchTarget.child就会指向这个View。最后还是会调用mFirstTouchTarget.child.onTouchEvent来让父View处理。
*/
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
//调用具体解析看2.2小节
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;
}
}
...
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
// 这个方法用来进行过滤,根据参数中child是否为null,来判断是将事件交给子View处理,还是交给父View处理。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
//判断是否有特殊的取消指令,有的话将事件都转为CANCEL事件
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
// 计算要传递的指针数量
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
//特殊情况下指针数量为0,产生了一个没有指针的Action。那么就丢弃这个事件
if (newPointerIdBits == 0) {
return false;
}
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
//当child属性为null,那么调用当前View的父View中的dispatchTouchEvent。最后会调到ViewGroup的父View中的dispatchTouchEvent。 而ViewGroup的父View就是View这个类。实际上最后会调用onTouchEvent方法。
if (child == null) {
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());
}
/*child不为空的话,就会调用child的dispatchTouchEvent方法。而当一个底层View消费了事件之后。这个child就会指向它。 所以会直接调用底层View的dispatchTouchEvent。而底层View的dispatchTouchEvent会直接调用到onTouchEvent。
最后将事件分发完毕。
具体解析看2.3小节
*/
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
可以看到dispatchTransformedTouchEvent会根据第三个参数child来进行判断。
当child不为null,则调用child的dispatchTouchEvent。
当child为null,则调用viewGroup的父类的dispatchTouchEvent,而viewGroup的父类是View。即调用View.dispatchTouchEvent。
public boolean dispatchTouchEvent(MotionEvent event) {
...
//事件安全性判断
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//判断如果设置了OnTouchListener,那么会调用OnTouchListener.onTouch()并判断返回值。如果返回值为true,那么将result变量设置为true。否则result值为false
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//如果OnTouchListener.onTouch()的返回值为false,才会调用onTouchEvent。onTouchEvent返回值为true,则将result置为true
if (!result && onTouchEvent(event)) {
result = true;
}
}
...
return result;
}
可以看出,当一个view处理事件时,会先判断这个view的onTouchListener这个回调是否设置,如果设置了这个回调,那么就会调用OnTouchListener.onTouch()。onTouch的返回值为true的情况下,就不会调用onTouchEvent。
只有onTouch返回值为false的情况下,才会调用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();
//判断一个view是否是Enable的。即便一个view处于disable的状态下,也是会消费事件的
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// 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)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
//判断是否设置了代理,如果设置了代理,将事件传递给代理view去处理。例如
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//当viewCLICKABLE、LONG_CLICKABLE、CONTEXT_CLICKABLE有一个为true时,才能处理事件。
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
//UP事件的处理
case MotionEvent.ACTION_UP:
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)) {
//判断一个事件为点击事件后,如果设置了onCLickListener,那么performClick方法会调用onCLickListener的onClick方法
performClick();
}
}
}
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;
//DOWN事件的处理
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
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(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
//MOVE事件的处理
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
}
//默认返回true
return true;
}
return false;
}