用户触摸屏幕后,会生成touch事件,之后应用可以在Looper中获得当前应用需要处理的touch事件,接下来交给ViewRootImpl去处理,ViewRootImpl确定该事件是touch事件后,传递给当前activity的phoneWindow里的DecorView的dispatchTouchEvent方法
本文主要回答以下问题:
1. dispatchTouchEvent过程,怎么找到目标View来处理事件的?
2. onTouch超出View的范围时,会发生什么?
3. View的onClick方法能否和onTouch方法共用?
4. View是按照什么顺序获得touch事件的?如果有View不处理时,又如何?
如果已经知道具体答案的童鞋,那么本篇文章就可以跳过了
源码解析
先看decorView的
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Callback cb = getCallback();//cb即为Activity,也就是下面的代码会调用Activity的dispatchTouchEvent
return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
: super.dispatchTouchEvent(ev);
}
解释:decorView的getCallBack()为何为Activity,原因是Actyvity的attach方法中调用了以下两行代码,attach则会在Activity创建过程中被调用:
mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
Activity的dispatchTouchEvent方法:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();//默认空方法,可重写来查看事件
}
if (getWindow().superDispatchTouchEvent(ev)) {//调用PhoneWindow的dispatchTouchEvent方法
return true;
}
//如果所有View都不处理touch事件,那么调用Activity的onTouchEvent
return onTouchEvent(ev);
}
PhoneWindow的dispatchTouchEvent方法实际是ViewGroup的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)) {//如果View被遮挡,返回false,不继续分发(遮挡可能的方式:)
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
//当开始一个ACTION_DOWN时,清除原先的状态,因为当遇到ANR,切换APP等变化时,事件可能被丢弃或取消
cancelAndClearTouchTargets(ev);
resetTouchState();
}
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 {
//事件不是touch_down事件(之前已经处理touch_down事件,现在还有其他事件,说明之前进行了拦截和处理) 或者 没有处理这个事件的view(包含在mFirstTouchTarget中),那么继续拦截事件
intercepted = true;
}
//如果被打断了,或已经有View处理这个事件了,就将这个事件作为普通事件处理
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;
if (!canceled && !intercepted) {//不是取消事件 并且当前view没拦截
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
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<View> preorderedList = buildOrderedChildList();//按照Z向量值由小到大
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {//这里是倒序
//所以以下基本上是先看获得焦点的view(被点击的view),如果被点击的view,不处理,那么处理child的顺序 : Z值大 -> 小 (android5.0后) 或 view重叠顺序上层 -> 下层
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
//优先让焦点View处理这个事件
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
//canViewReceivePointerEvents判断view的状态是可以接受点击事件的
//isTransformedTouchPointInView判断点击事件在View的范围内
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {//已经处理过这个touch事件了,避免重复操作
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//这个方法会对Event进行一些转换,再调用child.dispatchTouchEvent方法,如果希望处理
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();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
// 没有找到View去处理这个event
// 把指针交给最久添加的target
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// child没有在点击范围时,或者都没处理时,让该View自己来处理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// 分发event给目标view,除了之前那些分发过的view
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 (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
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;
}
该方法如果child不为null,就要把Event进行一些转换,转成适合child处理的Event,在让child.dispatchTouchEvent,否则交给父类super.dispatchTouchEvent处理
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;
}
// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
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);
}
// Perform any necessary transformations and dispatch.
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());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
//一个disable的view但是是clickable时,也是可以接收事件的,只是不会做出xiang'ying响应
return (((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {//如果有Clickable,longClickable、ContextClickable后续直接返回true
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) {
// 调用setPressed方法,该方法可重写
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// 只有在点击状态是调用onClick方法
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();
}
}
}
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;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
// 查看是否在可scroll的容器里
boolean isInScrollingContainer = isInScrollingContainer();
if (isInScrollingContainer) {//如果在可scroll的容器里时,延迟事件分发,以确定是scroll事件,还是其他事件
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
setPressed(true, x, y);
checkForLongClick(0);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {//如果移动到View外面,则不为点击状态
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
结论
- dispatchTouchEvent过程,怎么找到目标View的?
目标View为第一个符合以下条件的View: 1 一个在touch范围内的 2 希望处理事件的 view - onTouch超出View的范围时,会发生什么?
由下面代码可知:touch事件仍然继续传递,但是onTouchEvent方法会取消click、longClick等状态,即不会调用onClick、onLongClick方法,如果touch始终在一个View的内部,那么click事件会被调用,即使手指移动了很长距离
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;
- View的onClick方法能否和onTouch方法共用?
View的onClick方法调用在onTouch方法内部,所以如果自定义View希望继续支持onClick,需要调用super.onTouch,至于自定义View的返回值是true or false都不影响,如下面示例代码:
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(this.getClass().getSimpleName(),"onTouchEvent" + EventUtil.getEventType(event));
super.onTouchEvent(event);
return true;
}
View是按照什么顺序获得touch事件的?如果有View不处理时,又如何?
本View的onInterceptTouchEvent()方法 > 容器中获得焦点的View,一般就是被点击的View > 覆盖在上层的View/Z轴值更大的View > 覆盖在下层的View/Z轴值更小的View > 本View的super.dispatchTouchEvent
如果有View不处理时,按照以上顺序不断传递,如果最后仍没有View处理,那么将调用Activity的onTouchEvent方法处理