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 事件分发】事件分发源码分析 ( View 事件传递机制 ) , 继续分析 ViewGroup 的事件分发机制后续代码 ;
Activity 事件传递 , 最终调用到了 ViewGroup 的 dispatchTouchEvent 方法 , 在该方法中有调用了 View 的 dispatchTouchEvent 方法 ;
View 的 dispatchTouchEvent 调用完毕后 , 会返回 true 或 false 结果 ;
那么继续回到 ViewGroup 的 dispatchTransformedTouchEvent 方法 , 在该方法中 , 也就是遍历 ViewGroup 子组件时 , 调用遍历的子组件 View 的 dispatchTouchEvent 方法 , 处理相关触摸和点击相关的操作 ;
本博客继续分析 ViewGroup 的后续调用链 ;
一、ViewGroup | dispatchTransformedTouchEvent 方法返回值含义
在 ViewGroup | dispatchTouchEvent 方法中 , 调用 ViewGroup | dispatchTransformedTouchEvent 方法进行事件的转发 , 主要是遍历子组件调用子组件的触摸方法 ;
在 ViewGroup | dispatchTransformedTouchEvent 方法中 , 调用了 View 子组件的 View | dispatchTouchEvent 方法 , 该方法返回 true/false , 那么对应的 ViewGroup | dispatchTransformedTouchEvent 方法也会返回相同的值 ;
由下面的代码可知 , handled 是子组件 child.dispatchTouchEvent 调用返回值 , 返回结果就是 handled , 然后有将该值直接返回 , 说明 View 子组件的 dispatchTouchEvent 调用结果 , 就是 ViewGroup 的 dispatchTransformedTouchEvent 方法的返回结果 ;
ViewGroup | dispatchTransformedTouchEvent 方法的返回值含义 :
① 如果返回 true , 证明 子组件 View 已经将事件消费完毕 , 后续不需要再继续执行遍历了 ;
② 如果返回 false , 证明 子组件并没有将事件消费 , 后续还可以进行触发 ;
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
...
if (child == null) {
// 被遍历的 child 子组件为空
// 调用父类的分发方法
handled = super.dispatchTouchEvent(event);
} else {
// 被遍历的 child 子组件不为空
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
// 子组件分发触摸事件
// 此处调用的是 View 组件的 dispatchTouchEvent 方法 ;
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
...
}
}
二、记录被消费的触摸事件
如果 ViewGroup | dispatchTransformedTouchEvent 方法的返回值为 true , 则会触发记录已经被消费的事件的分支 ; 如果返回 false , 则不会触发 ;
// 正式开始分发触摸事件
// 处理以下两种情况 :
// ① 情况一 : 子控件触摸事件返回 true
// ② 情况二 : 子控件触摸事件返回 false
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
记录时 , 同时会创建 TouchTarget , 并赋值给 ViewGroup | dispatchTouchEvent | newTouchTarget 局部变量 ;
// 记录消费事件
// 添加触摸目标
newTouchTarget = addTouchTarget(child, idBitsToAssign);
调用 addTouchTarget 方法 , 创建被消费的事件 , 并将创建的值赋值给了 ViewGroup | mFirstTouchTarget 成员 ; 第一次消费事件被赋值 ;
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
完整代码如下 :
@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();
...
}
/**
* 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;
}
}
三、触摸事件没有被消费的调用链分析
如果上述事件分发方法 dispatchTransformedTouchEvent 返回 true , 就会创建 newTouchTarget 值 , 该值不会为空 , 同时 mFirstTouchTarget 不为空 ;
如果上述事件分发方法 dispatchTransformedTouchEvent 返回 false , 此时 newTouchTarget 值 , 就会为空 , 同时 mFirstTouchTarget 为空 ;
如果 ViewGroup | dispatchTransformedTouchEvent 方法的返回值为 false , 则不会记录事件消费 ; 也就不会调用 ViewGroup | addTouchTarget 方法 , 此时
ViewGroup | dispatchTouchEvent | newTouchTarget 局部变量
ViewGroup | mFirstTouchTarget 成员变量
为空 ;
/**
* 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;
}
当执行到 if (mFirstTouchTarget == null)
分支时 , 会命中该分支 , 进而调用 dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS)
方法 , 在 dispatchTransformedTouchEvent
方法中 , 如果 child 为空 , 则会调用父类的 super.dispatchTouchEvent
方法 ;
@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();
// 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);
}
...
}
/**
* 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;
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
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;
}
...
}
}
ViewGroup 继承自 View , 在 ViewGroup 中调用 super.dispatchTouchEvent
方法 , 就是调用 View 的 dispatchTouchEvent 方法 ;
super.dispatchTouchEvent(event) 方法消费的是父容器自己的事件 ;
child.dispatchTouchEvent(event) 方法消费的是子组件的事件 ;
四、触摸事件被拦截的调用链分析
如果上述事件分发方法 dispatchTransformedTouchEvent 返回 true , 就会创建 newTouchTarget 值 , 该值不会为空 , 同时 mFirstTouchTarget 不为空 ;
如果上述事件分发方法 dispatchTransformedTouchEvent 返回 false , 此时 newTouchTarget 值 , 就会为空 , 同时 mFirstTouchTarget 为空 ;
假设事件已经被消费了 , if (newTouchTarget == null && mFirstTouchTarget != null)
分支的代码不会被命中 , 继续向下分析 ;
同理如果事件被消费 , 也不会命中 if (mFirstTouchTarget == null)
分支 ;
下面讨论另外一种情况 , 就是父容器拦截触摸事件 ;
调用 ViewGroup | requestDisallowInterceptTouchEvent 可以设置是否拦截触摸事件 , 如果传入 true 则拦截触摸事件 , 如果传入 false , 则不拦截触摸事件 ;
在该 ViewGroup | requestDisallowInterceptTouchEvent 方法中 , 会修改 mGroupFlags 值 ;
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
// 设置父容器是否要拦截子组件的触摸事件
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
在 ViewGroup | dispatchTouchEvent 方法中 , 通过 mGroupFlags & FLAG_DISALLOW_INTERCEPT
判定父容器是否需要拦截子组件的触摸事件 , 该值最终影响 ViewGroup | dispatchTouchEvent | intercepted 局部变量的判断 ;
// Check for interception.
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 {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
假如父容器要拦截事件 , 此时 intercepted 值为 true , if (!canceled && !intercepted) {
分支就不会被命中 , 该分支是事件分发的核心逻辑 , 全都被屏蔽了 , 最终 :
ViewGroup | dispatchTouchEvent | newTouchTarget 局部变量
ViewGroup | mFirstTouchTarget 成员变量
都为空 , 执行到 if (mFirstTouchTarget == null)
分支时 , 会命中该分支 , 进而调用 dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS)
方法 , 在 dispatchTransformedTouchEvent
方法中 , 如果 child 为空 , 则会调用父类的 super.dispatchTouchEvent
方法 ;
ViewGroup 继承自 View , 在 ViewGroup 中调用 super.dispatchTouchEvent
方法 , 就是调用 View 的 dispatchTouchEvent 方法 ;
super.dispatchTouchEvent(event) 方法消费的是父容器自己的事件 ;
child.dispatchTouchEvent(event) 方法消费的是子组件的事件 ;
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
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;
}
...
}
ViewGroup | dispatchTouchEvent 相关代码 :
@UiThread
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
// 如果上述事件分发方法 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);
}
...
}
/**
* 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;
}
/**
* 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) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
...
}
}
五、ViewGroup 事件分发相关源码
@UiThread
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) {
// 辅助功能 , 残疾人相关辅助 , 跨进程调用 无障碍 功能
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
// 判断产生事件的目标组件是可访问性的 , 那么按照普通的事件分发进行处理 ;
// 可能由其子类处理点击事件 ;
// 判断当前是否正在使用 无障碍 相关功能产生事件
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
// 是否按下操作 , 最终的对外返回结果 , 该方法的最终返回值
boolean handled = false;
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.
cancelAndClearTouchTargets(ev);
// 如果是第一次按下 , 那么重置触摸状态
resetTouchState();
Android 事件分发事件分发源码分析 ( ViewGroup 事件传递机制 二 )
Android 事件分发事件分发源码分析 ( ViewGroup 事件传递机制 一 )
Android 事件分发事件分发源码分析 ( ViewGroup 事件传递机制 七 )
Android 事件分发事件分发源码分析 ( ViewGroup 事件传递机制 六 )
Android 事件分发ItemTouchHelper 事件分发源码分析 ( 绑定 RecyclerView )
Android 事件分发ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 )