写点什么

带你一起探究 Android 事件分发机制,- 让面试提问不在畏惧!

用户头像
Android架构
关注
发布于: 刚刚

}} else {intercepted = true;}


ViewGroup 的 dispatchTouchEvent()方法会先判断自己是否要拦截当前事件,是否拦截的作用在于,是自己处理事件,还是要将事件传递下去。即 intercepted 为 true 自己处理,为 false 则寻找子 View 向下传递。当然如果没有符合传递要求的子 View,事件还是会由当前 View 自己处理。


if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)


actionMasked == MotionEvent.ACTION_DOWN 好理解。其二,当子 View 消费事件时,父 View 会把消费事件的子 View 用链表记录下来,方便后续事件传递,而 mFirstTouchTarget 就是链表表头。没有子 View 能够接受事件,或者子 View 接受到事件但是不消费,mFirstTouchTargets 为 null。就会造成下次产生其它事件,走到这里该表达式没有一个为真,则当前 View 直接拦截事件处理。以后的事件子 View 想都不要想了。


如果 View 开始处理事件,但是不消耗最开头的 ACTION_DONW 事件(例如:执行 onTouchEvent()方法却返回 false),以后同系列的事件都不会再交给他。

同时父 View 以后也不会再有机会执行 onInterceptTouchEvent()方法。

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;


该表达式默认情况下为 false,所以会调用 onInterceptTouchEvent()方法,但 onInterceptTouchEvent()还是不拦截返回 false。我们可以调用 requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法修改标志位 mGroupFlags,即(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0 表达式成立为 ture。


但 intercepted 还是为 false 不拦截?那这个表达式和 requestDisallowInterceptTouchEvent()方法的价值和意义在哪儿??


如我们表面所看到的,目的就是为了执不执行 onInterceptTouchEvent()方法。例如:当我们自定义 View 时,在 InterceptTouchEvent()方法中处理事件拦不拦截的逻辑。子 View 可以调用 parent.requestDisallowInterceptTouchEvent(true)方法可以让父 View 没机会执行处理拦截的逻辑,直接让父 View 开始传递事件。就像小明儿子不管父亲对自己有什么样的看法,直接把传家宝抢到自己手上一个道理。


事件为 Down 时会重置 mGroupFlags 标志位状态,即无论怎样还是要走一遭 onInterceptTouchEvent()方法。


// 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);//重置 mGroupFlagsresetTouchState();}


走到这后,如果 intercept 为 false,则小明心里面已经没有自己卖掉传家宝的想法了。此时是想把传家宝传下去的,接下来就是挑选合格的继承人了。


遍历子 View,将事件传递给符合条件的 View。


final View[] children = mChildren;for (int i = childrenCount - 1; i >= 0; i--) {//ifinal int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);//children[i]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;}/**canViewReceivePointerEvents()确保子 View 要可见。执行补间动画时 View 会变成可见,即使 View 的 Visibility 属性为 INVISIBLE。*isTransformedTouchPointInView()判断事件的坐标是否落在当前子 View 的区域内。*/if (!canViewReceivePointerEvents(child)|| !isTransformedTouchPointInView(x, y, child, null)) {ev.setTargetAccessibilityFocus(false);continue;}//如果之前已有事件交由子 View 处理消费,则直接跳出循环,将事件传递下去 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);//将事件传递给子 Viewif (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {// Child wants to receive touch within its bounds.mLastTouchDownTime = ev.getDownTime();//反射测试 preorderedList == null,该集合按照子 View 绘制顺序和 Z 轴排序子 View。if (preorderedList != null) {// childIndex points into presorted list, find original indexfor (int j = 0; j < childrenCount; j++) {if (children[childIndex] == mChildren[j]) {mLastTouchDownIndex = j;break;}}} else {mLastTouchDownIndex = childIndex;}mLastTouchDownX = ev.getX();mLastTouchDownY = ev.getY();//链表存储消费事件的子 ViewnewTouchTarget = 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);}


ViewGroup,挑选传递事件的子 View 要符合两个条件:


可见状态事件的坐标在子 View 范围


符合这两个条件,则调用 dispatchTransformedTouchEvent()方法把事件传递给子 View。dispatchTransformedTouchEvent()方法会根据 child 参数来做不同的处理,当子 View 为 null 时调用 View 的 dispatchTouchEvent()传递事件,意味当前 View 自己处理事件。child 不为 null 的情况下,则调用 child 的 dispatchTouchEvent()把事件交给子 View。


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);}


我们以上面布局为例,当我们点击 TextView 产生 Down 事件,交到 DecorView 手上。事件的坐标在 ContentView(FrameLayout)区域内,DecorView 调用 dispatchTransformedTouchEvent()方法,把事件交给 ContentView。ContentView 又把事件交给 RelativeLayout->LinearLayout->TextView。DecorView 执行的 dispatchTransformedTouchEvent()要等待 ContentView 的 dispatchTouchEvent()方法执行结束才有结果,而 ContentView 又需要等 RelativeLayout 执行结束。


如果最终 TextView 消费了事件,dispatchTouchEvent()返回 ture。随之 LinearLayout 的 dispatchTransformedTouchEvent()执行结束为 ture。随后调用


newTouchTarget = addTouchTarget(child, idBitsToAssign);


将 TextView 记录下来插入表头


LinearLayout.mFirstTouchTarget.child = TextView;mFirstTouchTarget.next = null;


LinearLayout 的 dispatchTouchEvent()执行结束返回 true,随后 RelativeLayout 的 dispatchTransformedTouchEvent()执行结束为 ture;


RelativeLayout.mFirstTouchTarget.child = LinearLayout;mFirstTouchTarget.next = null;


如此反复向上,Down 事件分发结束。



如果 Dwon 事件交到 TextView 手上,但是 TextView 并未消费事件,TextView 的 dispatchTouchEvent()返回 false,随即 LinearLayout 的 dispatchTransformedTouchEvent()方法结果为 false,造成 mFirstTouchTarget 不能初始化还是为 null。随后 LinearLayout 会自己处理事件。


if (mFirstTouchTarget == null) {// No touch targets so treat this as an ordinary view.handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);}


如果 LinearLayout 依旧不消费事件,则 RelativeLayout 的 mFirstTouchTarget 为 null,RelativeLayout 自己处理事件。如此向上反复,最终 Activity 会处理事件。接下来如果又触摸屏幕产生了其它后续事件(Move)


if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)没有一个为真,当事件交给 DecorView 时就不向下走了,事件会在 Activity,window,DecorView 三者之间来回传递,最后还是 Activity 处理。


Case:TextView 之前消费了 Donw 事件,此时手指还未离开屏幕,来回移动产生 Move 事件。Move 事件经 Activity 交到 DecorView 手上,DecorView 还是一样需要先判断事件是否拦截。


if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (!disallowIntercept) {intercepted = onInterceptTouchEvent(ev);ev.setAction(action);} else {intercepted = false;}}


依旧会执行 onInterceptTouchEvent()方法,虽然结果还是不拦截。随后直接从 mFirstTouchTarget 表头找到之前消费 Down 事件的子 Viwe,将事件传递给它。不需要再遍历寻找子 View 了:


if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {//...........final View[] children = mChildren;for (int i = childrenCount - 1; i >= 0; i--) {}


只有当事件为 Down/ACTION_POINTER_DOWN/ACTION_HOVER_MOVE 这三种情况时才会遍历查找符合条件的子 View,所以当 TextView 消费 Down 事件后,LinearLayout 就认准他了,以后的事件都会交给他处理。即使我们的移动范围已经超出了 TextView,TextView 不可见。事件还是会交给他。


所以,同一系列事件只能由一个 View 消费。//除开自己的骚操作


if (mFirstTouchTarget == null) {handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);} else {TouchTarget predecessor = null;TouchTarget target = mFirstTouchTarget;while (target != null) {final TouchTarget next = target.next;//事件为 Down/ACTION_POINTER_DOWN/ACTION_HOVER_MOVE 并且被消费 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {handled = true;} else {final boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted;//第二次 Move 事


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


件 if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {handled = true;}}}


可以看到,下回产生的 Move 事件,确定不拦截后就会走到 while 中的 else 中去,如果 TextView 这回没有消费 Move 事件,这些事件最终还是会交给 Activity 处理,以后 TextView 还是接受到后续的事件。


###View 是怎么开始事件?View 和 ViewGroup 不同,View 的 dispatchTouchEvent()方法,意味将准备开始处理事件了。


public boolean dispatchTouchEvent(MontionEvent event){//.....ListenerInfo li = mListenerInfo;if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) {result = true;}


if (!result && onTouchEvent(event)) {result = true;}}


如果我们给 View 设置了 onTouchListener 监听器,则优先会回调 Listener 的 onTouch()方法。如果 onTouch()方法返回了 false,则还是会执行 onTouchEvent()方法。通常我们给 View 设置的 onClickListener,就是在 onTouchEvent()方法中的 Up 事件处理的。所以 onTouchListener 优先级大于 onClickListener。


switch (action) {case MotionEvent.ACTION_UP:


if (!post(mPerformClick)) {//该方法里会回调 onClick()performClick();


}}public boolean onTouchEvent(MotionEvent event) {


final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;//View,setEnable()后还是能处理事件。如果我们有给 View 设置监听器,该事件被消费。

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
带你一起探究Android事件分发机制,-让面试提问不在畏惧!