1.点击事件的传递规则
点击事件的分发就是对MotionEvent事件的分发过程,即当一个MontionEvent产生以后,系统需要把这个事件传递给一个具体的View,传递的过程就是分发过程。
点击事件的分发由三个方法共同完成dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent。
1).public boolean dispatchTouchEvent(MotionEvent ev)
用来进行事件的分发,如果事件能够传递给当前View,此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent影响,表示是否消耗当前事件。
2).public boolean onInterceptTouchEvent(MotionEvent ev)
用来判断是否拦截某个时间,如果当前view拦截了某个事件,那么在同一个事件序列中,此方法不会被再次调用,返回结果表示是否拦截当前事件。
3).public boolean onTouchEvent(MotionEvent ev)
在dispatchTouchEvent方法中调用,用来处理点击事件,只有onInterceptTouchEvent方法返回true才会调用,返回结果表示是否消耗当前事件,如果不消耗,则在同一事件序列中,当前View无法再接收到事件。
对于一个根ViewGroup,点击事件产生后,首先会传递给它调用他的dispatchTouchEvent,如果这个ViewGroup的onInterceptTouchEvent方法返回true表示该ViewGroup要拦截当前事件,接着事件会交给这个ViewGroup处理,调用它的onTouchEvent,如果onInterceptTouchEvent返回false则表示它不拦截当前事件,则该事件会传递给它的子元素,并且调用子元素的dispatchTouchEvent开始下一轮分发流程。
如果一个View需要处理事件时,如果设置了onTouchListener,则会回调onTouchListener中的ouTouch方法。如果onTouch返回false,则当前view的onTouchEvent会被调用,如果返回true,则onTouchEvent不会被调用。所以onTouchListener的优先级比onTouchEvent高,如果执行了onTouchEvent,如果设置有onClickListener 那么onClick方法会被调用,所以onClickListener优先级最低,处于事件传递的尾端。
点击事件传递过程:Activity–>Window–>View ,事件总是先传给Activity,再传给Windows,最后Windows再传给顶层View,顶层View接到事件后,就会按照事件分发机制分发事件。如果一个View的onTouchEvent返回false,就会调用它的父容器的onTouchEvent,以此类推,如果所有元素都不处理这个事件,那么该事件最终会传递给Activity处理,会调用Activity的onTouchEvent。
结论:
(1)同一事件序列指手指接触屏幕的那一刻起,到手指离开屏幕的那一刻所产生的一系列事件,以down事件开始,中间含有不定量的move时间,最终以up时间结束。
(2)正常情况下,一个事件序列只能被一个View拦截且消耗,一旦一个元素拦截了某事件,那么同一事件序列内的所有事件都会交由该元素处理。通过特殊手段可以将同一事件序列中的时间交给不同的view同时处理,比如通过一个view的onTouchEvent将时间传递给其他view处理。
(3)某个View一旦决定拦截,那么这个事件序列都只能由它来处理,并且它的onInterceptTouchEvent不会再被调用。当一个View决定拦截一个事件后,系统会把一个事件序列内的其他方法都直接交给他来处理,因此就不会在调用onInterceptTouchEvent来询问他是否要拦截。
(4)如果某个view通onTouchEvent开始处理事件,但是他不消耗ACTION_DOWN事件,即onTouchEvent返回了false,那么该view就不会再接收到事件,并且事件将重新交给他的父元素去处理,即调用父元素的onTouchEvent。即事件一旦交给一个View处理,他就必须消耗掉,否则同一时间序列中剩下的事件就不会再交给他处理。
(5)如果view只消耗ACTION_DOWN事件,那么这个点击事件会消失,并且父元素的onTouchEvent并不会被调用(事件的开始ACTION_DOWN已被消耗),并且当前view可持续受到后续的时间,最终这些消失的点击事件会传递给Activity处理。
(6)ViewGroup默认不拦截任何事件,源码中的ViewGroup的onInterceptTouchEvent方法默认返回false
(7)View没有onInterceptTouchEvent,一旦有点击事件传递给它,他的onTouchEvent就会被调用。
(8)View的onTouchEvent默认都会消耗事件(即返回true),除非他是不可点击的(clickable和longClickable同是为false),view的longClickable默认为false,clickable则要分情况。
(9)View的enable属性不影响onTouchEvent的默认返回值,就算一个view是disable状态的,只要他的clickable和longClickable有一个为true,则onTouchEvent就返回true。
(10)onClick会发生的前提是当前view是可以点击的,并且既收到了down又收到了up事件。
(11)事件的传递过程是由外到内传递的,事件总是先传递给父元素,然后在由父元素分发给子View,通过requestDisallowInterceptTouchEvent可以在子元素中干预父元素的事件分发(改变FLAG_DISALLOW_INTERCEPT的值,但是当事件类型是ACTION_DOWN时,总会重置该值,所以当ACTION_DOWN传递来时,总是会询问是否拦截),但是不能干预ACTION_DOWN。
2.事件分发源码分析
1)Activity对点击事件的分发
当一个点击操作发生时,事件最先传递给当前activity,由activity的dispatchTouchEvent来进行分发,具体工作有Activity内部的Windows完成。Windows会将事件传递给decor view,decor view一般就是当前界面的底层容器(即setContentView所设置的view的父容器),通过Activity.getWindows.getDecorView()可以获得。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction(); //空方法,若当前activity位于栈顶,且收到ACTION_DOWN事件时触屏点击按home,back,menu键等都会触发此方法。下拉statubar、旋转屏幕、锁屏不会触发此方法。
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true; //调用windows的dispatchTouchEvent 如果windows的方法返回了true则说明有子View消耗了事件序列,则循环结束
}
return onTouchEvent(ev); //如果windows的dispatch返回false,则所有View的onTouchEvent都返回了false,则会调用activity的onTouchEvent
}
接下来看Window如何传递给ViewGroup,由源码可知,window是一个抽象类,window的superDispatchTouchEvent也是个抽象方法,PhoneWindow是Window的唯一实现类。
PhoneWindow中的superDispatchTouchEvent()源码
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
//mDecor是DecorView的实例
//DecorView是视图的顶层view,继承自FrameLayout,是所有界面的父类
}
DecorView继承自FrameLayout,所以实际上事件传递到了ViewGroup,执行了ViewGroup的dispatchTouchEvent,到这里事件已经传递到顶级View了,即在activity
setContentView所设置的View。顶级View一般来说都是ViewGroup。
2)顶级View对点击事件的分发过程
顶级View的分发流程:事件到达顶级View以后,会调用ViewGroup的dispatchTouchEvent,如果顶级ViewGroup拦截事件(ViewGroup的onInterceptTouchEvent默认为返回false,所以默认情况下不会拦截事件),即onInterceptTouchEvent返回true,则事件由ViewGroup处理,这是如果ViewGroup设置了onTouchlistener则会调用onTouch,onTouch返回true则不会调用onTouchEvent,如果返回false则调用onTouchEvent,即onTouchListener的优先级比onTouchEvent高。如果顶层ViewGroup不拦截事件,事件会传递给他所在点击事件链上的子View,调用子View的dispatchTouchEvent,接下来则循环顶层ViewGroup的分发流程。
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
//当ACTION_DOWN事件到来时,会调用resetTouchState()对FLAG_DISALLOW_INTERCEPT进行重置
//因此子View调用disallow方法并不能影响ViewGroup对ACTION_DOWN的处理
resetTouchState();
}
// Check for interception.
final boolean intercepted;
//通过这个if结构判断是否要进行拦截,当事件类型为ACTION_DOWN或mFirstTouchTarget != null时会进入该if分支进行判断是否拦截。
//mFirstTouchTarget将会在ViewGroup的子元素成功处理事件时赋值并指向该子元素。所以一旦ViewGroup拦截事件,mFirstTouchTarget的值就
//为空,接下来的事件就不会进入if分支调用onInterceptTouchEvent。
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
//是否禁止拦截事件的功能,默认不禁止,false。当事件类型为ACTION_DOWN时,就会充值这个标记位,所以当ACTION_DOWN事件传递过来时
//ViewGroup总会询问自己是否要拦截事件。
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 {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
当事件由ViewGroup的子元素成功处理时,mFirstTouchTarget会被赋值并指向该元素,所以如果ViewGroup不拦截事件并将事件传递给子元素处理时就会令mFirstTouchTarget != null成立。那么如果ViewGroup拦截事件,mFirstTouchTarget == null,当ACTION_MOVE和ACTION_UP事件到来时,if结构中(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)为false,就会导致intercepted置为true,且不会再调用onInterceptTouchEvent,同一事件序列的其他事件都会默认交给他处理。
FLAG_DISALLOW_INTERCEPT标记位是通过requestDisallowInterceptTouchEvent方法来设置的,一般用于子View中,表示是否禁用事件拦截的功能,默认为false(默认不禁用),一旦设置了之后,ViewGroup将无法拦截除了ACTION_DOWN以外的点击事件,因为ViewGroup在分发事件时,如果事件类型是ACTION_DOWN就会重置FLAG_DISALLOW_INTERCEPT,导致子View中设置的标记位无效。因此,当事件类型是ACTION_DOWN时,ViewGroup总会调用自己的onInterceptTouchEvent来询问自己是否拦截事件。
ViewGroup不拦截事件时,事件向下分发给子View的源码
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);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
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) {
// 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);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { //调用子View的dispatchTouchEvent
// 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;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//如果子元素的dispatchTouchEvent返回了true,就会通过addTouchTarget给mFirstTouchTarget赋值并跳出遍历子View 的循环。如果
//子元素的dispatchTouchEvent返回了false,ViewGroup就会把事件分发给下一个满足条件的子元素。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
dispatchTransformedTouchEvent部分源码:
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event); //传递过来的child不为null,所以执行了child的dispatchTouchEvent。
}
event.setAction(oldAction);
return handled;
}
addTouchTarget()源码:给mFirstTouchTarget赋值
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
首先遍历ViewGroup的所有子元素,然后判断子元素是否能够接受到事件,判断条件:1。子元素是否在播动画2。点击事件的坐标是否落在子元素区内。如果子元素满足条件,事件就会传递给他处理。dispatchTransformedTouchEvent实际上就是调用了子元素的dispatchTouchEvent方法,这样事件就会交给满足条件的子元素处理了。如果子元素的dispatchTouchEvent返回了true,就会通过addTouchTarget给mFirstTouchTarget赋值并跳出遍历子View 的循环。如果子元素的dispatchTouchEvent返回了false,ViewGroup就会把事件分发给下一个满足条件的子元素。
两种情况下会发生遍历所有子元素后事件都没有被处理:1.ViewGroup没有子元素。2.子元素处理了点击事件,但是在dispatchTouchEvent中返回了false。这时,ViewGroup就会自己处理点击事件。源码如下,同样调用了dispatchTransformedTouchEvent
但是第三个参数child传了null,所以会调用super.dispatchTouchEvent(event),ViewGroup的父类是View,所以会调用View的dispatch,即点击事件会交给View来处理。
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
}
3)View对点击事件的处理
首先会判断有没有设置onTouchListener,如果onTouchListener中的onTouch返回true,那么onTouchEvent就不会被调用,所以ouTouchListener优先级高于onTouchEvent,
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
. . . . . .
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//检查当前view是否设置了onTouchListener,如果设置了并且onTouch中返回了true,就会将result置true,下面的if判断就不会成
//立,就不会调用onTouchEvent。
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;
}
onTouchEvent部分源码:
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//当View处于不可用状态下点击事件的处理过程,不可用状态下View照样会消耗点击事件。
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
onTouchEvent对点击事件的具体处理:
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//只要View的CLICKABLE 和 LONG_CLICKABLE有一个为true,clickable就是true,if条件就成立,则onTouchEvent就会返回true
//那么该View就会消耗该事件,
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
...
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)) {
performClick();
}
}
}
...
break;
}
...
return true;
}