dispatchTouchEvent
事件派发显示隧道方式、再是冒泡方式
隧道方式传递,直道某一个元素消耗此事件,由上至下逐层分发视图。
冒泡方式传递,当某个视图消耗事件后其return boolean 是与分发相反的方法向上传递。
具体分发给哪一个视图是通过当前触摸点坐标在当前层哪个视图上判断
onInterceptTouchEvent
ViewGroup的方法,如果当前ViewGroup需要拦截传递给其子视图的事件,需要return true
/**
* {@inheritDoc}
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
boolean handled = false;
// 2. 未被其他窗口遮盖
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 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.
// 3. 清理触摸操作的所有痕迹,即派发取消操作
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 检测是否拦截Touch Event
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 是否允许拦截,可以通过requestDisallowInterceptTouchEvent方法设置
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;
}
// Check for cancelation.
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;
// 如果Touch Event没有取消并且没有被拦截,才会考虑是否向其子视图派发
if (!canceled && !intercepted) {
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 (childrenCount != 0) {
// Find a child that can receive the event.
// Scan children from front to back.
final View[] children = mChildren;
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// 遍历当前ViewGroup的所有子视图
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = children[i];
// 4 与5
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
// 满足以上条件,没必要接收Touch Event
continue;
}
// 如果是在子视图上触摸,经过以上过滤条件,只有当前手指正下方的子视图才会获取到此事件
// 子视图是否在TouchTarget中
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);
// 6. 执行触摸操作(会传递到最终视图的onTouchEvent方法)
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = i;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
// 会改变mFirstTouchTarget的值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
// 没有发现可以接收事件的子视图
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// 子视图未消耗Touch Event 或者 被拦截未向子视图派发Touch Event
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// 由当前ViewGroup处理Touch Event
// No touch targets so treat this as an ordinary view.
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;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
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);
}
}
// 1. 用于测试目,直接忽略
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
/**
* Cancels and clears all touch targets.
*/
private void cancelAndClearTouchTargets(MotionEvent event) {
// ## 如果指向其他视图,这里进行统一清理
if (mFirstTouchTarget != null) {
boolean syntheticEvent = false;
if (event == null) {
// 如果参数为空
final long now = SystemClock.uptimeMillis();
// 使用静态方法一个MotionEvent对象,动作为ACTION_CANCEL
event = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
// 输入源是一个触摸屏定位设备。
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
syntheticEvent = true;
}
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
resetCancelNextUpFlag(target.child);
// 6 向下传递触摸事件(当前传递取消操作)
dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
}
// 清理所有触摸目标
clearTouchTargets();
// 注销拷贝的对象
if (syntheticEvent) {
event.recycle();
}
}
}
/**
* Returns true if a child view can receive pointer events.
* @hide
*/
private static boolean canViewReceivePointerEvents(View child) {
// 当前视图显示或者正在执行动画,才可以接受触摸事件
return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null;
}
/**
* Returns true if a child view contains the specified point when transformed
* into its coordinate space.
* Child must not be null.
* @hide
*/
protected boolean isTransformedTouchPointInView(float x, float y, View child,
PointF outLocalPoint) {
// 视图有scrollTo或者scrollBy造成的滚动偏移也需要计算在内
float localX = x + mScrollX - child.mLeft;
float localY = y + mScrollY - child.mTop;
if (! child.hasIdentityMatrix() && mAttachInfo != null) {
final float[] localXY = mAttachInfo.mTmpTransformLocation;
localXY[0] = localX;
localXY[1] = localY;
child.getInverseMatrix().mapPoints(localXY);
localX = localXY[0];
localY = localXY[1];
}
// 触摸点是否在当前子视图内
final boolean isInView = child.pointInView(localX, localY);
if (isInView && outLocalPoint != null) {
outLocalPoint.set(localX, localY);
}
return isInView;
}
/**
* Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
*/
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) {
// 调用View类的方法,其内部会调用View.onTouchEvent
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) {
// 调用View类的方法,其内部会调用View.onTouchEvent
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
// 如果当前视图通过scrollTo或scrollBy对子视图进行滚动
// 向下传递的是x,y偏移mScrollX, mScrollY后的值
handled = child.dispatchTouchEvent(event);
// 一次手势操作,例如滚动视图,MotionEvent参数都是同一个对象
// 如果有疑问的话,可以在onTouchEvent中打印其hashCode看下
// 所以这里要对之前做出针对其子视图的偏移就还原,便于之后使用
event.offsetLocation(-offsetX, -offsetY);
}
// 如果以上消耗了当前触摸事件,直接返回
return handled;
}
// 获取当前参数的拷贝,对其做得修改不会影响当前参数对象
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// 与以上操作雷同,不具体解释
// newPointerIdBits != oldPointerIdBits ?1
// 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;
}