基础知识
-
事件分发的对象
事件分发的对象是 点击事件(Touch事件),当用户触摸屏幕(View或者ViewGroup派生控件)时,将会产生点击事件(Touch事件)
Touch事件的相关细节,如触摸位置、时间等被封装为MotionEvent对象
-
事件类型
MotionEvent.ACTION_DOWN----按下View(所有事件的开始)
MotionEvent.ACTION_MOVE----滑动View
MotionEvent.ACTION_UP----抬起View
MotionEvent.ACTION_CANCEL----结束事件(所有事件的开始)
-
事件列
一般,事件列都是以DOWN事件开始,UP事件结束,中间无数个MOVE事件
-
事件分发的本质
将点击事件MotionEvent传递到具体某个View以及处理该事件的整个过程
-
事件分发在哪些对象间进行传递
Activity----控制生命周期,处理事件
View----所有UI组件的基类
ViewGroup----一组View的集合,本身也是View的子类
-
事件分发的顺序
Activity -> ViewGroup -> View
-
事件分发由哪些主要方法协作完成
1、dispatchTouchEvent()
分发(传递)事件
当点击事件能够传递到当前View,该方法就会被调用
2、onTouchEvent()
处理点击事件
在dispatchTouchEvent方法内部调用
3、onInterceptTouchEvent()
判断是否拦截事件
该方法只存在于ViewGroup中,在ViewGroup的dispatchTouchEvent方法内部调用。若是重新该方法为true,则表示父容器拦截了事件,子容器View将无法处理该事件
源码流程分析
image.png当点击事件发生后,事件先传到Activity、再传到ViewGroup、最终传到View
从上图可知,要充分理解事件分发机制,本质上是需要理解:
1、Activity对点击事件的分发机制
2、ViewGroup对点击事件的分发机制
3、View对点击事件的分发机制
下面我们将对这三个分发机制进行逐一分析
-
Activity对点击事件的分发机制
当点击事件发生时,最先响应的是Activity的dispatchTouchEvent方法,那么为什么是它最先响应呢?Activity之dispatchTouchEvent前传
public boolean dispatchTouchEvent(MotionEvent ev) {
// 事件都是以按下事件(DOWN)为开始,所以此处为true
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction(); // 分析1
}
if (getWindow().superDispatchTouchEvent(ev)) { // 分析2
return true;
}
return onTouchEvent(ev); // 分析3
}
- 分析1(当前在Activity类)
onUserInteraction();
// onUserInteraction方法是一个空方法,主要作用是实现屏保功能
// 当此activity在栈顶时,触屏点击按home,back,recent键等都会触发此方法
public void onUserInteraction() {}
- 分析2(当前在Activity类)
getWindow().superDispatchTouchEvent(ev)
// getWindow方法返回 mWindow,它是一个PhoneWindow对象
public Window getWindow() {
return mWindow;
}
private Window mWindow;
// PhoneWindow为Window抽象类的子类
mWindow = new PhoneWindow(this, window, activityConfigCallback);
- 分析2(当前在Window类)
// 抽象方法,由PhoneWindow类实现
public abstract boolean superDispatchTouchEvent(MotionEvent event);
- 分析2(当前在PhoneWindow类)
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
// 调用DecorView类中的superDispatchTouchEvent方法
return mDecor.superDispatchTouchEvent(event);
}
private DecorView mDecor;
- 分析2(当前在DecorView类)
// 调用父类的dispatchTouchEvent方法
// DecorView是所有UI界面的父类
// DecorView的父类是FrameLayout,但FrameLayout并没有实现dispatchTouchEvent方法
// 所以继续向上寻找到FrameLayout的父类,即调用ViewGroup类的dispatchTouchEvent方法
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
此时,事件被分发到了ViewGroup
- 分析3(当前在Activity类)
return onTouchEvent(ev);
// 当点击事件未被任何一个View接收并处理就会执行onTouchEvent方法
// 场景如:发生在Window边界外的触摸事件
public boolean onTouchEvent(MotionEvent event) {
// 点击事件发生在Windows边界外,返回true
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
- 分析3(当前在Window类)
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
// 主要是对于处理边界外点击事件的判断:是否是DOWN事件,event的坐标是否在边界内等
final boolean isOutside =
event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event)
|| event.getAction() == MotionEvent.ACTION_OUTSIDE;
if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
// 说明事件在边界外,即 消费事件
return true;
}
// 未消费(默认)
return false;
}
-
Activity对点击事件的分发机制总结
至此,流程已经走到了ViewGroup类中的dispatchTouchEvent方法,继续分析~
-
ViewGroup对点击事件的分发机制
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// 这是一个验证事件完整性的一个类,一会会看到在本方法结束的地方也有这个类出现
// 防止event事件在分发过程中不一致,这个类还有记录的功能
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)) {// 分析1
// 获取Touch Action
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) { // 分析2
// 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();
}
// 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); // 分析3
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;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// 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;
if (!canceled && !intercepted) {// 分析4
// If the event is targeting accessibility focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
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 (newTouchTarget == null && childrenCount != 0) {
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.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) { // 分析8
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;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
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);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
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();
}
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;
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {// 分析5
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {// 分析6
// 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;
}
- 分析1(当前在View类)
// 检测是否分发Touch事件(判断窗口是否被遮挡住)
// 如果该 Touch 事件没有被窗口遮挡,则继续后续逻辑
// 一般情况下都会返回true
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
&& (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
// Window is obscured, drop this touch.
return false;
}
return true;
}
- 分析2(当前在ViewGroup类)
// 判断当前是否是ACTION_DOWN,刚进来的时候肯定是ACTION_DOWN
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
private void cancelAndClearTouchTargets(MotionEvent event) {
....
clearTouchTargets();
....
}
// 将mFirstTouchTarget置为null
// 回收TouchTarget对象
// 清空所有接收触摸事件View的引用
private void clearTouchTargets() {
// TouchTarget和Handler中的Message一样,都是一个单向链表,链式结构,通过next来访问下一个元素
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
TouchTarget next = target.next;
target.recycle();
target = next;
} while (target != null);
mFirstTouchTarget = null;
}
}
private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
// 默认允许拦截事件
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
// 默认视图不滚动
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
- 分析3(当前在ViewGroup类中)
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 通常不会执行下面的语句
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
// 默认返回false
return false;
}
通常,如果ViewGroup或其派生类重写了onInterceptTouchEvent并返回为true,这说明父容器需要拦截当前点击事件,即onInterceptTouchEvent为true。
代码到了这里,出现了一个分支:
即,如果onInterceptTouchEvent为true,则执行 [分析4],否则不执行。
接下来我们先分析父容器拦截事件的情况。
- 分析5(当前在ViewGroup类)
// 此时 mFirstTouchTarget必定为null,因为之前调用cancelAndClearTouchTargets方法时做了清空处理
// mFirstTouchTarget必定为null,意味着 没有任何 view 消费该 Touch 事件
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
// ev: event事件类型
// canceled:是否是cancel,因为没有取消,所以当前是false
// null:View child字段传了null
// TouchTarget.ALL_POINTER_IDS:位置
// viewGroup 会调用dispatchTransformedTouchEvent处理,但是child==null,会调用View#dispatchTouchEvent处理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS); // 分析5-1
}
- 分析5-1(当前在ViewGroup类)
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();
// cancel为false,下方if语句块不会执行
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;
}
// 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语句块是一个新老事件的处理,也不会进入
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
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());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
由于child传进来为null,所以调用了super.dispatchTouchEvent方法,而ViewGroup类的父类是View,所以这里进入并执行了View类中的dispatchTouchEvent方法。
当前 handler返回为true(逻辑见View的事件分发机制流程分析),则回到 [分析5]处, [分析5]的代码也会返回true,则会回到Activity的dispatchEvent方法中。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
getWindow().superDispatchTouchEvent(ev)返回为true,至此,在“父容器拦截事件”的情况下的流程已经走完。
下面分析父容器不拦截子View事件的情况
再次展示VIewGroup的dispatchTouchEvent方法的代码
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// 这是一个验证事件完整性的一个类,一会会看到在本方法结束的地方也有这个类出现
// 防止event事件在分发过程中不一致,这个类还有记录的功能
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)) {// 分析1
// 获取Touch Action
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) { // 分析2
// 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();
}
// 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); // 分析3
ev.setAction(action); // restore action in case it was changed
} else {
// 不拦截状态下,intercepted = false
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;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// 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;
if (!canceled && !intercepted) {// 分析4
// If the event is targeting accessibility focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
// 因为还是处于按下状态,所以当前为ACTION_DOWN
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);
// 对于当前事件,有处理权限的View的个数(肯定不为0)
final int childrenCount = mChildrenCount;
// 因为newTouchTarget还未赋值,所以为null
if (newTouchTarget == null && childrenCount != 0) {
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.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();// 分析7
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
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;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
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);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
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();
}
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;
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {// 分析5
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {// 分析6
// 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;
}
父容器不拦截子View事件,intercepted 为 false,canceled也为false,在[分析4]处的代码块会被执行
- 分析7(当前在ViewGroup类)
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
public ArrayList<View> buildTouchDispatchChildList() {
return buildOrderedChildList();
}
ArrayList<View> buildOrderedChildList() {
final int childrenCount = mChildrenCount;
if (childrenCount <= 1 || !hasChildWithZ()) return null;
if (mPreSortedChildren == null) {
mPreSortedChildren = new ArrayList<>(childrenCount);
} else {
// callers should clear, so clear shouldn't be necessary, but for safety...
mPreSortedChildren.clear();
mPreSortedChildren.ensureCapacity(childrenCount);
}
final boolean customOrder = isChildrenDrawingOrderEnabled();
for (int i = 0; i < childrenCount; i++) {
// add next child (in child order) to end of list
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View nextChild = mChildren[childIndex];
final float currentZ = nextChild.getZ();
// insert ahead of any Views with greater Z
int insertIndex = i;
while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
insertIndex--;
}
mPreSortedChildren.add(insertIndex, nextChild);
}
return mPreSortedChildren;
}
image.png对当前有处理事件权限的View做一个排序,将View按照getZ()方法为排序依据一个个的放入ArrayList数组中
getZ方法拿到的是View的Z坐标,getZ越大,在数组中的位置越靠后。
如果一个Activity中包含一个LinearLayout,LinearLayout中有子View为Button,那么数组的排序为Activity、LinearLayout、Button。
在[分析7]处,依据拿到了排过序的ArrayList,进入[分析8]
- 分析8(当前在ViewGroup类)
// 这里做了倒序处理(如上图,就是先把Button取出来了)
for (int i = childrenCount - 1; i >= 0; i--) {
// 获取child对应的index
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
// 从数组中获取View
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
...
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) { // 分析8-1
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child); // 分析8-2
...
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {// 分析8-3
// Child wants to receive touch within its bounds.
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;
}
}
- 分析8-1(当前在ViewGroup类)
// 如果是View不可见或者正在执行动画,则返回false;否则返回true
private static boolean canViewReceivePointerEvents(@NonNull View child) {
return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null;
}
// 判断当前位置
protected boolean isTransformedTouchPointInView(float x, float y, View child,
PointF outLocalPoint) {
final float[] point = getTempPoint();
point[0] = x;
point[1] = y;
transformPointToViewLocal(point, child);
final boolean isInView = child.pointInView(point[0], point[1]);
if (isInView && outLocalPoint != null) {
outLocalPoint.set(point[0], point[1]);
}
return isInView;
}
[分析8-1]处的代码,主要是分析当前View是否符合处理事件的条件,如果不符合,continue。
- 分析8-2(当前在ViewGroup类)
newTouchTarget = getTouchTarget(child);
private TouchTarget getTouchTarget(@NonNull View child) {
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
if (target.child == child) {
return target;
}
}
return null;
}
由于mFirstTouchTarget目前暂未赋值,还是为null,所以方法体返回null,即newTouchTarget为null
- 分析8-3(当前在ViewGroup类)
这次流程进入了dispatchTransformedTouchEvent方法中,和之前遇到的[分析5-1]代码流程不同,这时的第三个入参View child是有值的,按照之前分析的流程来看,这里传入的应该是button。
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;
}
// 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) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
// 此时child不为null
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(也就是View)的dispatchTouchEvent方法。
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
调用 child.dispatchTouchEvent方法,流程就进入到了View类中的 dispatchTouchEvent方法里了
dispatchTransformedTouchEvent方法会返回true,也就是[分析8-3]处的代码返回为true,进入if语句块。
再次放出[分析8-3]处if语句块中代码
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
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); // 分析9
alreadyDispatchedToNewTouchTarget = true;
// 跳出for循环
break;
}
我们知道,如果是子View处理了事件,那么其父容器是无法处理该事件的,为什么呢?我们继续分析addTouchTarget方法
- 分析9(当前在ViewGroup类)
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
// 根据child获取到TouchTarget
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
// 此时 mFirstTouchTarget依然为null,所以target.next为null
target.next = mFirstTouchTarget;
// target赋值给mFirstTouchTarget,此时mFirstTouchTarget不为null
mFirstTouchTarget = target;
return target;
}
public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
...
final TouchTarget target;
// 同步方法
synchronized (sRecycleLock) {
if (sRecycleBin == null) {
// 首次进来 new 一个TouchTarget
target = new TouchTarget();
} else {
target = sRecycleBin;
sRecycleBin = target.next;
sRecycledCount--;
target.next = null;
}
}
// 传进来的View赋值给target.child,并将target返回出去
target.child = child;
target.pointerIdBits = pointerIdBits;
return target;
}
此时再看[分析8-3]处的代码:
newTouchTarget不为null,
newTouchTarget.next为null,
mFirstTouchTarget不为null,
mFirstTouchTarget.next为null,
且newTouchTarget = mFirstTouchTarget,
alreadyDispatchedToNewTouchTarget为true
根据刚才我们得到的结果,可以看到流程已经开始执行下面代码
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
// 首次target不为null
while (target != null) {
// 由于 target.next为null,此时next为null
final TouchTarget next = target.next;
// alreadyDispatchedToNewTouchTarget为true
// target == newTouchTarget根据之前分析的为true
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
// handled为true
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;
// 将next赋值给target,而此时的next为null,所以target为null,跳出while循环
target = next;
}}
流程到此,ViewGroup的dispatchTouchEvent方法返回为true, 则回到 [分析5]处, [分析5]的代码也会返回true,则会回到Activity的dispatchEvent方法中。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
getWindow().superDispatchTouchEvent(ev)返回为true,至此,在“父容器不拦截事件”的情况下的流程已经走完。
-
ViewGroup对点击事件的分发机制总结
-
View对点击事件的分发机制
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
// 检测是否分发Touch事件(判断窗口是否被遮挡住)
// 如果该 Touch 事件没有被窗口遮挡,则继续后续逻辑
// 一般情况下都会返回true
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo; // 分析10
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {// 分析11
result = true;
}
if (!result && onTouchEvent(event)) {// 分析12
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
- 分析10(当前在View类)
ListenerInfo li = mListenerInfo;
那么mListenerInfo是在哪里赋值的呢?
- 分析10(当前在View类)
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
继续寻找getListenerInfo方法调用的地方
- 分析10(当前在View类)
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
setOnClickListener方法是Android开发者最最常用的一个方法了,执行此方法,传递一个OnClickListener进来。
- 分析11(当前在View类)
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
- 分析12(当前在View类)
if (!result && onTouchEvent(event)) {
result = true;
}
[分析11]和[分析12]可以结合起来看,如果某个View重写了onTouch方法并返回为true,就不能执行onTouchEvent方法。也就是说执行了[分析11]处的代码,[分析12]就不会执行了。
当然,如果不重写onTouch,或者onTouch方法返回false,就会执行[分析12]处的onTouchEvent。我们经常使用的onClick方法就是在此。
public boolean onTouchEvent(MotionEvent event) {
...
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
// 如果是点击事件,就得满足先DOWN后UP
// 那么就可以直接在ACTION_UP中查看方法
case MotionEvent.ACTION_UP:
...
performClickInternal();
...
case MotionEvent.ACTION_DOWN:
...
}
return true;
}
return false;
}
查看performClickInternal方法源码
private boolean performClickInternal() {
notifyAutofillManagerOnClick();
return performClick();
}
继续查看performClick方法源码
public boolean performClick() {
notifyAutofillManagerOnClick();
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
可以看到,这里执行了OnClickListener接口的onClick回调,也就是我们经常使用的onClick方法了。
-
View对点击事件的分发机制总结
网友评论