在前面的讨论中,我们了解到,在InputDispatcher里进行分发处理的时候,如果有2个窗口,会进行splitMotionEvent操作,对MotionEvent进行分割加工,只传递窗口自身的事件给对应的窗口。
我们可以再对多窗口进行试验,在onTouchEvent中可以发现,touch的坐标MotionEvent的getX,getY打印出来是坐标是相对于窗口的坐标,而不是屏幕原点的坐标。这就说明在touch的分发中,这个相对坐标位置也进行了处理,这个处理是在InputDispatcher里进行的,还是在app层view中touch分发里处理的呢?
查看InputDispatcher的log , Deliving touch to…
或者在1837void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
1838 const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget)
里添加log,我们可以发现,InputDispatcher传递的坐标是屏幕坐标,
相对坐标是view中touch分发处理得到的,可以通过MotionEvent的getRawX, getRawY来获取屏幕坐标,
MotionEvent里有方法offsetLocation来设置坐标的偏移,
2942 /**
2943 * Adjust this event's location.
2944 * @param deltaX Amount to add to the current X coordinate of the event.
2945 * @param deltaY Amount to add to the current Y coordinate of the event.
2946 */
2947 public final void offsetLocation(float deltaX, float deltaY) {
2948 if (deltaX != 0.0f || deltaY != 0.0f) {
2949 nativeOffsetLocation(mNativePtr, deltaX, deltaY);
2950 }
2951 }
顺着这个思路,我们可以理解view中touch分发的处理,从MotionEvent的坐标变化角度来看,view分发中的dispatchTransformedTouchEvent处理中使用到offsetLocation来进行MotionEvent的处理。
参考:
作者:dehang0
链接:https://juejin.cn/post/6844904065617362952
-> ViewGroup.java
// 参数desiredPointerIdBits表示child期望接收哪些触摸点上的事件
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();
// 判断是否需要取消事件序列,若是的话则派发ACTION_CANCEL事件。
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
// 没有派发目标的情况下,child为null,交由ViewGroup自身处理。
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
// Calculate the number of pointers to deliver.
// 获取该事件上所有的触摸点ID
final int oldPointerIdBits = event.getPointerIdBits();
// 和期望接收的触摸点做相与操作得到新的触摸点集合。正常情况下newPointerIdBits就是
// desiredPointerIdBits,这里做这样操作的目的是一种校验目的。
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.
// 由于某些异常原因导致desiredPointerIdBits不存在于oldPointerIdBits,出现
// newPointerIdBits为0。此种情况下没有找到有效触摸点,则丢弃该事件。
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.
// transformedEvent用于保存事件副本
final MotionEvent transformedEvent;
// 判断触摸点是否产生变化,例如有新的触摸点按下或旧触摸点抬起。
if (newPointerIdBits == oldPointerIdBits) {
// 触摸点ID集合无变化,则不需要进行事件拆分。
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
// 坐标系偏移以适应子view坐标系
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
// 派发给child
handled = child.dispatchTouchEvent(event);
// 恢复坐标偏移
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
// 若child需要计算变化矩阵,这里获取一个事件副本
transformedEvent = MotionEvent.obtain(event);
} else {
// 触摸点ID有变化,进行事件拆分,保存拆分事件副本
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());
}
// 使用事件副本进行派发给child
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}