当我们点击屏幕时,会产生一个点击事件,这个事件由MotionEvent来表示。这个事件最先传递到Activity,会回调dispatchTouchEvent方法
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return super.dispatchTouchEvent(ev);
}
方法内调用父类Activity的dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
getWindow获取Window对象,并调用它的superDispatchTouchEvent方法,将事件传递给Window,Window是一个抽象类,它的实现是PhoneWindow
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
PhoneWindow中又将事件传递到DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
这个DecorView继承自FrameLayout,它就是我们通过setContentView方法设置View的父容器,这样事件就从Window传递到了Activity的顶级View,接下来是在ViewGroup中的事件分发过程,继续调用ViewGroup的dispatchTouchEvent方法
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//...省略部分代码
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
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是否要拦截事件的,有两种情况,事件是否是ACTION_DOWN;mFirstTouchTarget是否为空,当前ViewGroup不拦截事件,该事件由子元素处理时mFirstTouchTarget!= null成立。再判断FLAG_DISALLOW_INTERCEPT标记位,这个标记位由子元素设置,设置后ViewGroup不能拦截除ACTION_DOWN以外的其他事件,如果设置了则返回false不拦截,如果没设置则获取onInterceptTouchEvent的返回值。继续看该方法中的代码
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (!canceled && !intercepted) {
//...
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
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 (!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)) {
// 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();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
}
//...
}
判断如果当前事件是未被取消和拦截的,则会遍历ViewGroup中的child并将事件分发给他们。canViewReceivePointerEvents方法判断child是可见的以及未在执行动画,isTransformedTouchPointInView判断子child在点击的坐标范围内,如果这方法不满足的话,则continue进入下一个循环。否则,调用dispatchTransformedTouchEvent
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
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);
}
event.setAction(oldAction);
return handled;
}
//...省略部分代码
}
这里传入的child不为空,调用child的dispatchTouchEvent将点击事件分发给child处理。如果这个方法返回true即child将事件处理了,则来到if分支中,调用addTouchTarget方法,将child赋值给mFirstTouchTarget,所以当mFirstTouchTarget不为空时就证明事件传递到了child并由child消耗了。
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
现在看child即View中事件分发的处理
public boolean dispatchTouchEvent(MotionEvent event) {
//...
boolean result = false;
//...
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
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;
}
首先判断有没有设置mOnTouchListener,如果mOnTouchListener中的onTouch方法返回了true的话,onTouchEvent就不会被调用了,可以看出onTouch方法优先级比onTouchEvent要高,这样我们就可以在外面处理View的事件了。
如果没有设置mOnTouchListener,则调用View的onTouchEvent方法
public boolean onTouchEvent(MotionEvent event) {
//...
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
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;
}
}
//...
}
如上,当View处于disable不可用状态时,会将clickable状态return回去。所以,即使View是不可用状态但可以点击时,也会消耗当前事件。
public boolean onTouchEvent(MotionEvent event) {
//...
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
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)) {
performClick();
}
}
}
//...
mIgnoreNextUpEvent = false;
break;
//...
}
return true;
}
return false;
}
如果View是可用的,这里会根据点击事件的具体action做对应的处理,我们可以看出当clickable为true事件会被消费,其中包括CLICKABLE、LONG_CLICKABLE和CONTEXT_CLICKABLE这三种。
在ACTION_UP事件触发时,会执行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;
}
如果View设置了mOnClickListener,就会回调onClick方法。
总结
事件的分发流程已经分析完了,分发流程主要是:Activity接收屏幕的点击事件,将事件传递到Window即PhoneWindow上,然后Window又将事件传递到DecorView上,DecorView继承自FrameLayout,它是Activity设置的ContentView的父布局,于是事件最终就传递到了我们根布局上,它一般是一个ViewGroup。最后,由这个ViewGroup分发给它的子View,就完成了事件的分发。