Android 事件分发事件分发源码分析 ( ViewGroup 事件传递机制 六 )
Posted 韩曙亮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 事件分发事件分发源码分析 ( ViewGroup 事件传递机制 六 )相关的知识,希望对你有一定的参考价值。
android 事件分发 系列文章目录
【Android 事件分发】事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 )
【Android 事件分发】事件分发源码分析 ( Activity 中各层级的事件传递 | Activity -> PhoneWindow -> DecorView -> ViewGroup )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 一 )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 二 )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 三 )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 四 | View 事件传递机制 )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 五 )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 六 )
文章目录
一、按下触摸事件记录
在上一篇博客 【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 五 ) 中 , 着重分析了 ViewGroup 事件分发中 , 触摸事件没有被消费 , 或被父容器拦截的情况 ;
这里再分析下触摸事件被消费之后的 , 触摸事件记录过程 ;
触摸事件如果成功被消费 , 则 dispatchTransformedTouchEvent 方法返回 true ;
对应的会调用 addTouchTarget 方法 , 创建 TouchTarget 对象 , 赋值给 newTouchTarget 成员变量 ;
newTouchTarget = addTouchTarget(child, idBitsToAssign)
TouchTarget 代表了个触摸事件 , 每个 TouchTarget 中都封装了消费该触摸事件的 View 组件 ;
// The touched child view.
// 当前 View 对象
public View child;
TouchTarget 经过优化后 , 以链表形式存储 , 每个 TouchTarget 都定义了一个 next 成员变量 , 指向下一个 TouchTarget 被消费的触摸事件 ;
// The next target in the target list.
// 链表操作 , 该引用指向下一个触摸事件
public TouchTarget next;
相关源码 :
@UiThread
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
// 正式开始分发触摸事件
// 处理以下两种情况 :
// ① 情况一 : 子控件触摸事件返回 true
// ② 情况二 : 子控件触摸事件返回 false
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
// 如果返回值为 true , 说明该事件已经被消费了
// 此时记录这个已经被消费的事件
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();
}
// 如果上述事件分发方法 dispatchTransformedTouchEvent 返回 true
// 就会创建 newTouchTarget 值 , 该值不会为空 , 同时 mFirstTouchTarget 不为空
// 如果上述事件分发方法 dispatchTransformedTouchEvent 返回 false
// 此时 newTouchTarget 值 , 就会为空 , 同时 mFirstTouchTarget 为空
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;
}
}
}
// 如果事件被消费 , 事件分发方法 dispatchTransformedTouchEvent 返回 true
// 就会创建 newTouchTarget 值 , 该值不会为空 , 同时 mFirstTouchTarget 不为空
// 反之
// 如果上述事件分发方法 dispatchTransformedTouchEvent 返回 false
// 此时 newTouchTarget 值 , 就会为空 , 同时 mFirstTouchTarget 为空
//
// 还有一个逻辑就是 , 如果该事件被父容器拦截 , mFirstTouchTarget 也是 null 值
// 调用 dispatchTransformedTouchEvent , 但是传入的子组件时 null
// 在 dispatchTransformedTouchEvent 方法中触发调用 if (child == null) 分支的
// handled = super.dispatchTouchEvent(event) 方法 , 调用父类的事件分发方法
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// 事件没有被消费的分支
// 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);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
/**
* Adds a touch target for specified child to the beginning of the list.
* Assumes the target child is not already present.
*/
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
}
二、完整的触摸事件处理机制
完整触摸事件 :
一个完整的触摸动作 , 由 1 1 1 次 按下触摸事件 , 若干次 移动触摸事件 , 1 1 1 次 抬起触摸事件 组成 ,
1 1 1 个触摸动作只有 1 1 1 次按下操作 , 并且是整个触摸动作的起始 触摸事件 ;
一个完整的动作 , 只有第一次按下 , 才执行 子组件的 排序 , 遍历 , 事件分发 等操作 ; 第一次按下后 , 手指按着移动 , 属于第2次以及之后的第n次动作 , 不再走该分支 , 直接执行该分支后面的代码 ;
这里在第 1 1 1 次按下时 , 创建触摸事件记录 TouchTarget ; 假如当前动作时按下以后的移动/抬起动作 , 则跳过上面的分支 , 直接执行后面的代码逻辑 , 按下之后 mFirstTouchTarget 肯定不为空 ;
TouchTarget 事件记录封装 :
TouchTarget 对象对应着一个完整的动作 , 该动作包含 1 个按下事件 , 若干 移动 事件 , 1 个抬起事件 ;
第一次按下 , 负责构建 TouchTarget 链表 , 将消费事件的 View 组件封装到 TouchTarget 中 ;
然后的移动/抬起操作 , 不再重复的创建 TouchTarget 对象了 ; 直接使用第一次按下的 TouchTarget 对象作为当前动作的标识 , 直接向该 TouchTarget 对象中的 View 组件分发事件 ;
这也是我们按下按钮时 , 即使将手指按着移出边界 , 按钮也处于按下状态 ;
相关源码 :
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
// First touch target in the linked list of touch targets.
private TouchTarget mFirstTouchTarget;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// 判断是否是按下操作
// 一个完整的动作 , 只有第一次按下 , 才执行下面的逻辑
// 第一次按下后 , 手指按着移动 , 属于第2次以及之后的第n次动作 , 不再走该分支
// 直接执行该分支后面的代码
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;
// 计算 ViewGroup 父容器下面有多少个子 View 组件 ;
final int childrenCount = mChildrenCount;
// TouchTarget newTouchTarget = null; 在上面声明为空 , 此处肯定为 null ;
// childrenCount 子组件个数不为 0
// 如果子组件个数为 0 , 则不走下一段代码 , 如果子组件个数大于 0 , 则执行下一段代码 ;
// 说明下面的代码块中处理的是 ViewGroup 中子组件的事件分发功能 ;
if (newTouchTarget == null && childrenCount != 0) {
// 获取单个手指的 x,y 坐标
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.
// 子组件排序 , 按照 Z 轴排列的层级 , 从上到下进行排序 ,
// 控件会相互重叠 , Z 轴的排列次序上 ,
// 顶层的组件优先获取到触摸事件
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
// 倒序遍历 按照 Z 轴的上下顺序 , 排列好的组件
// 先遍历的 Z 轴方向上 , 放在最上面的组件 , 也就是顶层组件
for (int i = childrenCount - 1; i >= 0; i--) {
// 获取索引
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
// 获取索引对应组件
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
// 无障碍 辅助功能
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
// X 控件范围 A , 如果手指按在 B 范围 , 不会触发 X 控件的事件
// 判定当前的组件是否可见 , 是否处于动画过程中
// ① canViewReceivePointerEvents 判定组件是否可见 , 会否处于动画
// ② isTransformedTouchPointInView 判定手指是否在控件上面 ;
// 上述两种情况 , 不触发事件
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
// 不触发事件
continue;
}
// 截止到此处 , 可以获取子组件进行操作
// 提取当前的子组件
// 第一次执行 getTouchTarget 代码时 , 是没有 mFirstTouchTarget 的
// 此时第一次返回 null
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);
// 正式开始分发触摸事件
// 处理以下两种情况 :
// ① 情况一 : 子控件触摸事件返回 true
// ② 情况二 : 子控件触摸事件返回 false
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
// 如果返回值为 true , 说明该事件已经被消费了
// 此时记录这个已经被消费的事件
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();
}
// 上面的分支是只有第一次按下时才执行的
// 假如当前动作时按下以后的移动/抬起动作
// 则跳过上面的分支 , 直接执行后面的代码逻辑
// 按下之后 mFirstTouchTarget 肯定不为空
// 如果事件被消费 , 事件分发方法 dispatchTransformedTouchEvent 返回 true
// 就会创建 newTouchTarget 值 , 该值不会为空 , 同时 mFirstTouchTarget 不为空
// 反之
// 如果上述事件分发方法 dispatchTransformedTouchEvent 返回 false
// 此时 newTouchTarget 值 , 就会为空 , 同时 mFirstTouchTarget 为空
//
// 还有一个逻辑就是 , 如果该事件被父容器拦截 , mFirstTouchTarget 也是 null 值
// 调用 dispatchTransformedTouchEvent , 但是传入的子组件时 null
// 在 dispatchTransformedTouchEvent 方法中触发调用 if (child == null) 分支的
// handled = super.dispatchTouchEvent(event) 方法 , 调用父类的事件分发方法
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// 事件没有被消费的分支
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// TouchTarget 对象对应着一个完整的动作 , 该动作包含 1 个按下事件 , 若干 移动 事件 , 1 个抬起事件 ;
// 第一次按下 , 负责构建 TouchTarget 链表 , 将消费事件的 View 组件封装到 TouchTarget 中
// 然后的移动/抬起操作 , 不再重复的创建 TouchTarget 对象了
// 直接使用第一次按下的 TouchTarget 对象作为当前动作的标识
// 直接向该 TouchTarget 对象中的 View 组件分发事件
// 这也是我们按下按钮时 , 即使将手指按着移出边界 , 按钮也处于按下状态 ;
// 事件被消费的分支 , 事件消费成功 , 会走这个分支
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
// 将当前所有的消费的事件以及消费的 View 组件做成了一个链表
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;
// 找到了 View , 开始分发触摸事件
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;
}
}
}
}
三、ViewGroup | dispatchTouchEvent 方法返回
在 【Android 事件分发】事件分发源码分析 ( Activity 中各层级的事件传递 | Activity -> PhoneWindow -> DecorView -> ViewGroup ) 博客中分析了从 Activity -> PhoneWindow -> DecorView -> ViewGroup 的调用链 ;
Activity | dispatchTouchEvent 方法调用 PhoneWindow | superDispatchTouchEvent 方法 ,
PhoneWindow | superDispatchTouchEvent 方法调用 DecorView | superDispatchTouchEvent 方法 ,
DecorView | superDispatchTouchEvent 方法调用 ViewGroup | dispatchTouchEvent 方法 ;
ViewGroup 的 dispatchTouchEvent 事件分发方法执行完毕后 ,
先返回到 DecorView | superDispatchTouchEvent 方法 ,
然后返回到 PhoneWindow | superDispatchTouchEvent 方法 .
最终返回到 Activity 的 dispatchTouchEvent ;
如果在 Activity 的 dispatchTouchEvent 方法中 , PhoneWindow 的 superDispatchTouchEvent 方法的返回值是 true , 即 ViewGroup 的 dispatchTouchEvent 方法返回 true ;
则直接返回 , 不再向后执行 ;
// getWindow() 获取的是 PhoneWindow 窗口
// 调用 PhoneWindow 的 superDispatchTouchEvent ;
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
相关源码 :
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback, WindowControllerCallback,
AutofillManager.AutofillClient {
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// 判定是否是第一次按下
// 该方法与事件传递机制流程无关
// 提供给用户按下时可以执行的业务逻辑
onUserInteraction();
}
// getWindow() 获取的是 PhoneWindow 窗口
// 调用 PhoneWindow 的 superDispatchTouchEvent ;
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
public void Android 事件分发事件分发源码分析 ( ViewGroup 事件传递机制 二 )
Android 事件分发事件分发源码分析 ( ViewGroup 事件传递机制 一 )
Android 事件分发事件分发源码分析 ( ViewGroup 事件传递机制 七 )
Android 事件分发事件分发源码分析 ( ViewGroup 事件传递机制 六 )
Android 事件分发ItemTouchHelper 事件分发源码分析 ( 绑定 RecyclerView )
Android 事件分发ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 )