在开始分析事件分发以前, 须要了解一下 TouchTarget 这个类的做用
在ViewGroup的事件分发中, 它起到了不可获取的做用, 不了解其原理, 看起源码可能会致使身体不适
下面开始分析bash
/**
* ViewGroup.TouchTarget 链表结构
*/
private static final class TouchTarget {
// 链表最大的长度
private static final int MAX_RECYCLED = 32;
// 用于控制同步的锁
private static final Object sRecycleLock = new Object[0];
// sRecycleBin 内部可复用实例链表表头
// 注意 ViewGroup 中还维护着一个 mFirstTouchEvent, 它是外部记录正在响应事件 View 的链表, 响应完成以后会调用 recycler 方法, 加入 sRecycleBin 这个可复用的链表中
private static TouchTarget sRecycleBin;
// 内部可复用的实例链表的长度
private static int sRecycledCount;
public static final int ALL_POINTER_IDS = -1; // all ones
// 当前被触摸的 View
public View child;
// The combined bit mask of pointer ids for all pointers captured by the target.
// 对目标捕获的全部指针的指针id的组合位掩码
public int pointerIdBits;
// 链表中指向的下一个目标
public TouchTarget next;
private TouchTarget() {
}
public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
if (child == null) {
throw new IllegalArgumentException("child must be non-null");
}
final TouchTarget target;
synchronized (sRecycleLock) {
if (sRecycleBin == null) {
target = new TouchTarget();
} else {
// 从链表复用池中取出一个对象, 并重至属性值
target = sRecycleBin; // 将当前的表头赋给这个变量
sRecycleBin = target.next; // 表头移动到下个位置
sRecycledCount--; // 当前复用池的数量 -1
target.next = null; // 将它的 next 置空
}
}
// 从新绑定数据
target.child = child;
target.pointerIdBits = pointerIdBits;
return target;
}
public void recycle() {
if (child == null) {
throw new IllegalStateException("already recycled once");
}
synchronized (sRecycleLock) {
if (sRecycledCount < MAX_RECYCLED) {
// 以前的表头变成了 next
next = sRecycleBin;
// 更新链表表头位置为本身
sRecycleBin = this;
// 复用池的数量 +1
sRecycledCount += 1;
} else {
next = null;
}
child = null;
}
}
}
/**
* ViewGroup.addTouchTarget
*/
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
// 1. 建立一个新的 TouchTarget 对象
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
// 2. 让他链上上一个对象, 第一次的话 mFirstTouchTarget 确定为 null
target.next = mFirstTouchTarget;
// 3. 更新 mFirstTouchTarget 的值
mFirstTouchTarget = target;
return target;
}
/**
* ViewGroup.getTouchTarget
*/
private TouchTarget getTouchTarget(@NonNull View child) {
// 变量 mFirstTouchTarget 造成的链表, 寻找当前 child 对应的 TouchTarget
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
if (target.child == child) {
return target;
}
}
return null;
}
复制代码
须要着重注意的两点post
/**
* ViewGroup.dispatchTouchEvent
* mFirstTouchTarget 是当前正在响应事件链表的表头
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
// 1. 若为 ACTION_DOWN 则清除以前的状态, ACTION_DOWN 为整个序列事件的初始化事件
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 在开始一个新的触摸手势时,抛弃全部之前的状态。
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 2. 判断是否须要当前 ViewGroup 拦截事件
// 2.1 DOWN 事件会判断是否须要拦截
// 2.2 当前事件序列已经以前有子 View 响应(mFirstTouchTarget != null) 判断是否须要拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// 调用本身的 onInterceptTouchEvent 方法, 判断是否拦截事件
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// 没有触摸的目标, 而且不是初始化事件 ACTION_DOWN 了, 则直接将标记位设置为拦截
intercepted = true;
}
// 3. 不是 ACTION_CANCEL 事件, 而且没有本身被拦截, 则开始寻找响应事件的子 View
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
// 只有 Down 事件才会去找寻响应事件的子 View
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// 3.1 判断这个子 View 是否能够接收事件, 事件是否在它的区域内
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//... 走到这里说明在咱们手指触摸的区域, 已经找到了一个子 View
// 3.2 判断当前响应事件序列的链表中, 这个 View 是否已经在响应一个事件序列了
// Sample: 当你一根手指按在了这个子 View 上, 令一根手指也按在了这个子View上时, 不会从新响应 Down 事件的
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// 若当前找到的 View 正在响应事件, 则跳出循环
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
// 3.3 找到的 View 没有在处理事件, 则经过 dispatchTransformedTouchEvent 将事件传递到这个子 View 中
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// ... 忽略部分代码, 只关注细节
// 3.3.1 走到这里, 说明事件成功分发给了子 View 而且被其成功消费了
// 3.3.2 调用 addTouchTarget 给 mFirstTouchTarget 赋值
// 3.3.3 若是处理掉了的话,将此 child 添加到 TouchTarget 链表的首部, 并让 mFirstTouchTarget -> 链表的首部
newTouchTarget = addTouchTarget(child, idBitsToAssign);
break;
}
}
// 4. 本次没找到响应该事件的 View, 但以前存在响应当前事件序列的 View (即当前 ViewGroup 的 mFirstTouchTarget 链不为 null)
// 例如我一开始一根手指按在了一个 Button 上, 后来我另外一根手指按在了 ViewGroup 空白的地方, 那么就会走下面的逻辑
if (newTouchTarget == null && mFirstTouchTarget != null) {
// 遍历链表
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
// while 结束后,newTouchTarget 指向了链表末尾的 TouchTarget 实例
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
if (mFirstTouchTarget == null) {
// 5. mFirstTouchTarget == null 则说明没有任何子 View 消耗当前事件序列
// 调用 dispatchTransformedTouchEvent 交由本身处理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// 6. 走到这里说明存在一个子 View 正在响应事件序列
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
// 遍历 mFirstTouchTarget 造成的链表中
while (target != null) {
final TouchTarget next = target.next;
// 6.1 遍历到的 target 刚好为 newTouchTarget 直接标记为 handle, 通常 ACTION_DOWN 会进入到这个 if 里去, 由于 Down 事件的分发在上面进行
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
// 判断是否须要向以前响应事件的子 view 发送 ACTION_CANCEL 事件
// 当前 ViewGroup 的父容器忽然拦截了事件序列, 咱们收到了 ACTION_CANCEL 事件
// 当前 ViewGroup 本身拦截了事件序列, 给子 View 分发 ACTION_CANCEL 事件
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
// 6.2 将事件继续分发给子 View, 用于分发 ACTION_DOWN 之外的事件
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true; // TouchTarget 链中任意一个处理了则设置 handled 为true
}
// 6.3 若是须要 cancelChild 的话,
// 则将此节点从 mFirstTouchTourget 所在的链表中回收到 TouchTarget.sRecycleBin 所在的链表中
if (cancelChild) {
if (predecessor == null) {
// 若表头须要被回收, 则移动表头到下一个位置
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target; // 访问下一个节点
target = next;
}
}
return handle;
}
/**
* ViewGroup.dispatchTransformedTouchEvent
*/
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// ... 只关注核心部分
if (child == null) {
// 若子 View 为 null, 则回调当前 ViewGroup 父类的 dispatchTouchEvent 方法, 即本身处理
handled = super.dispatchTouchEvent(transformedEvent);
} else {
// 若子 View 不为null, 则把事件传递给子 View 处理
handled = child.dispatchTouchEvent(transformedEvent);
}
transformedEvent.recycle();
return handled;
}
/**
* View.dispatchTouchEvent
*/
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;
// 1. 先执行 onTouchListener
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// 2. onTouchListener 返回 false 会执行 onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
复制代码
好了事件的分发到此出就结束了, 全部的重点仍是 ViewGroup 中的 dispatchTouchEvent 这个方法中, 尤为是对 TouchTarget 链表的理解, 简单的总结一下ui
ViewGroup.dispatchTouchEvent 几个关键点:this
判断是否须要拦截这个事件spa
若为 ACTION_DOWN 事件: 找寻可以响应这个事件的 View, 即给 newTouchTarget 赋值指针
若为 ACTION_DOWN 之外的其余事件:rest
View.dispatchTouchEvent 中的关键点:
若是设置了 onTouchListener 而且返回了 true, 是不会回调 onTouchEvent 方法的code
具体的细节源码中的注释写的很详细, 就再也不赘述了orm
public boolean onTouchEvent(MotionEvent event) { // View对touch事件的默认处理逻辑
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
// 判断是否可响应点击
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
// DISABLED 的状态下
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// 若是以前是PRESSED状态则复原
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// Disable 的视图依旧能够响应触摸事件, 只不过它是以无响应的方式
// 只不过是以无响应的方式处理了
return clickable;
}
// 若是有 TouchDelegate 的话,优先交给它处理
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true; // 处理了返回 true,不然接着往下走
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
// 若为不可点击的则移除相关回调, 直接结束对 ACTION_UP 的处理
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
// 若是外围有能够滚动的parent的话,当按下时会设置这个标志位
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// 设置了FocusableInTouchMode后,View在点击的时候就会
// 尝试requestFocus(),并将focusToken设置为true
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// 确保用户可以看到按钮的Press状态
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// 若是没有长按发生的话, 移除长按 Callback
removeLongPressCallback();
// focusTaken 为 false 才执行下面的对 onClick 的回调
// 若本次事件请求了焦点(focusTaken 为 true), 则不会执行 onClick
if (!focusTaken) {
// 优先使用 post 去处理点击事件, 这样可让视觉状态在 performClick 以前呈现
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
// 若是 post 失败了,则直接调用 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:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
// 判断是否在能够滚动的容器中
boolean isInScrollingContainer = isInScrollingContainer();
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED; // 设置 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, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
// 事件被父容器拦截了再也不下发, 则清除回调和标记位
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
// 判断是否移动到了当前 View 的界外
if (!pointInView(x, y, mTouchSlop)) {
// 移除回调
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
// 只要能进来就会返回 true
return true;
}
return false;
}
复制代码
源码注释很详细, 有几个须要关注的点:对象
// 判断是否可响应点击
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
复制代码