本文主要是介绍Anroid触摸事件分发机制---ViewGroup相关,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
这篇文章只介绍ViewGroup事件分发机制,因为Activity和View的事件分发,相对很简单,网上随便找一篇文章基本就能理解了,但是对于ViewGroup的事件分发机制,我真的看了不下20篇文章,没有一篇讲的到位的,没有一篇告诉你重点关注哪个点,最后只是告诉你一堆结论,或者给你看一堆流程图,最气人的是没有一篇文章告诉你ACTION_CANCEL怎么回事,ACTION_CANCEL时系统其实做了一个很重要的操作,这个操作你理解了,你之前的疑惑也许就都 理解了,后面会提到,其实对于ViewGroup的分发源码,你看Android2.3的源码就可以了,真的很简单,甚至让你看了后一辈子都忘不了,肯本不需要记任何结论或画任何复杂的流程图,高版本的源码太过复杂但是原理和Android2.3基本一样。
重点要关注的点,我会在涉及到的时候强调。
一:Android事件分发整体流程简图
这是个关注点
这个图很重要,可能你也懂这个图,但是我只是想提醒你看源码的时候心里想着这个图,这样逻辑才清晰。
二:ViewGroup事件分发Anroid2.3源码
我先把源码帖出来,这个源码跟高版本相比已经很简单了,但是我们看的时候还是可以简化一些,我下面会贴出重点代码
public boolean dispatchTouchEvent(MotionEvent ev) {final int action = ev.getAction();final float xf = ev.getX();final float yf = ev.getY();final float scrolledXFloat = xf + mScrollX;final float scrolledYFloat = yf + mScrollY;final Rect frame = mTempRect;boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (action == MotionEvent.ACTION_DOWN) {if (mMotionTarget != null) {mMotionTarget = null;}if (disallowIntercept || !onInterceptTouchEvent(ev)) {ev.setAction(MotionEvent.ACTION_DOWN);final int scrolledXInt = (int) scrolledXFloat;final int scrolledYInt = (int) scrolledYFloat;final View[] children = mChildren;final int count = mChildrenCount;for (int i = count - 1; i >= 0; i--) {final View child = children[i];if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE|| child.getAnimation() != null) {child.getHitRect(frame);if (frame.contains(scrolledXInt, scrolledYInt)) {final float xc = scrolledXFloat - child.mLeft;final float yc = scrolledYFloat - child.mTop;ev.setLocation(xc, yc);child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;if (child.dispatchTouchEvent(ev)) {mMotionTarget = child;return true;}}}}}}boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||(action == MotionEvent.ACTION_CANCEL);if (isUpOrCancel) {mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;}final View target = mMotionTarget;if (target == null) {ev.setLocation(xf, yf);if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {ev.setAction(MotionEvent.ACTION_CANCEL);mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;}return super.dispatchTouchEvent(ev);}if (!disallowIntercept && onInterceptTouchEvent(ev)) {final float xc = scrolledXFloat - (float) target.mLeft;final float yc = scrolledYFloat - (float) target.mTop;mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;ev.setAction(MotionEvent.ACTION_CANCEL);ev.setLocation(xc, yc);if (!target.dispatchTouchEvent(ev)) {}mMotionTarget = null;return true;}if (isUpOrCancel) {mMotionTarget = null;}final float xc = scrolledXFloat - (float) target.mLeft;final float yc = scrolledYFloat - (float) target.mTop;ev.setLocation(xc, yc);if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {ev.setAction(MotionEvent.ACTION_CANCEL);target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;mMotionTarget = null;}return target.dispatchTouchEvent(ev);
}
上面的代码我们再简化一下,并做 一些说明:
pirvate View mMotionTarget;
public boolean dispatchTouchEvent(MotionEvent ev) {final int action = ev.getAction();//是否允许拦截boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (action == MotionEvent.ACTION_DOWN) {//只有ACTION_DOWN时才走这个if语句//ACTION_DOWN时重置mMotionTarget为null,mMotionTarget指向处理并且消费掉当前ACTION_DOWN的子View。//如果不为null说明指向上次处理并消费了ACTION_DOWN的子View,因此要重置为nullif (mMotionTarget != null) {mMotionTarget = null;}//disallowIntercept默认为false,可通过requestDisallowInterceptTouchEvent(true)修改其值//高版本anroid中我有看到,在ACTION_DOWN时有一个对FLAG_DISALLOW_INTERCEPT重置的操作,避免requestDisallowInterceptTouchEvent(true)造成的影响//所以我在这里加这句代码和高版本一致mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;//不拦截有两种情况:1,不允许拦截;2,虽然允许拦截,但是并没有拦截。//既然不拦截就找对应的子View处理这个ACTION_DOWN.就是for循环判断 点击区域落到哪个子View身上。if (disallowIntercept || !onInterceptTouchEvent(ev)) {for (所有子View) {final View child = 找到的那个可以接收ACTION_DOWN的子View;if (child.dispatchTouchEvent(ev)) {//交给这个子View处理,如果子View消费掉了,就让mMotionTarget指向这个子View,且直接return,本次ACTION_DOWN事件分发结束。//如果这个子View虽然处理了但是没有消费掉该ACTION_DOWN,则继续向下执行。mMotionTarget = child;//唯一给mMotionTarget赋值的地方,谨记!!!return true;}}//没有找到合适的子View处理该ACTION_DOWN,例如点击了空白区域,即没有子View的区域等等。继续向下执行}}//targt和mMotionTarget指向同一个对象,需要注意的是,只有在ACTION_DOWN时ViewGroup不拦截且子View消费掉ACTION_DOWN时,才给mMotionTarget,只有这一个地方,谨记!!!final View target = mMotionTarget;//target为null,说明没有可接收事件的子View,需要ViewGroup自己处理该事件//target为null的原因有很多://1,ACTION_DOWN时,可能是之前的逻辑中onInterceptTouchEvent()返回true即拦截了,或者虽然没拦截但是没有找到合适的子View处理该事件,//或者虽然找到子View处理该事件了但是子View没有消费掉该事件,这些都导致mMotionTarget没有被赋值(参考前面的for循环部分)。//2,非ACTION_DOWN时,则说明之前的事件序列都是ViewGroup自己处理的(参考1)或者之前ViewGroup拦截了某次事件(后面的代码可看到拦截时会将mMotionTarget置为null)if (target == null) {//ViewGroup自己处理事件的逻辑return super.dispatchTouchEvent(ev);//若执行了这里则后续代码不再执行}//能走到这里的不可能是ACTION_DOWN,且target不是null,说明子View有处理能力同时ViewGroup也可以拦截,体会一下。//如果拦截则给子View(target)一个ACTION_CANCEL事件,同时mMotionTarget置为null,本次分发结束,下次分发的事件就交给ViewGroup自己处理,if (!disallowIntercept && onInterceptTouchEvent(ev)) {ev.setAction(MotionEvent.ACTION_CANCEL);if (!target.dispatchTouchEvent(ev)) {}//下面两行可看到,所谓的拦截其实并不是拦截,而是让子View执行ACTION_CANCEL事件,但是ViewGroup并不处理拦截下来的这个事件,而是直接消费掉(true),只是说下次事件//由我ViewGroup来处理。mMotionTarget = null;return true;}if (isUpOrCancel) {mMotionTarget = null;}//如果不拦截,则由子View处理return target.dispatchTouchEvent(ev);
}
这段代码你肯定看的懂,没必要告诉你一堆结论,给你一堆流程图,记结论很累,你的理解就是结论。
这里并没有详细介绍onInterceptTouchEvent(ev)和requestDisallowInterceptTouchEvent(true)两个方法,这两个方法很好理解,也有很多文章介绍,,简单说下就是:
requestDisallowInterceptTouchEvent(true)用于修改FLAG_DISALLOW_INTERCEPT标记为,为true说明禁止Viewgroup拦截,这样也就不会调用onInterceptTouchEvent(ev)判断是否拦截了,为false说明允许拦截,注意,允许拦截不代表肯定拦截,还要调用onInterceptTouchEvent(ev)判断最终拦截不拦截。
关注点*:再次强调看源码时一定要结合事件分发大体过程看,结合最上面的图看,脑子里要有这个分发的过程,提醒 自己时间是随着手指边移动边不断分发的。且是不断调用dispatchTouchEvent(MotionEvent ev) 的
关注点1:mMotionTarget默认为null,唯一给它赋值的地方在有子View消费掉ACTION_DOWN时,注意是唯一。
关注点2:target就是mMotionTarget
关注点3:ViewGroup拦截子View的某个非ACTION_DOWN事件时,会给这个子View一个ACTION_CANCEL事件同时把mMotionTarget置为null,这样下次分发的事件就由ViewGroup处理了。因为mMotionTarget为null了,这个事件序列之后的事件就全部交给ViewGroup处理了。
如果对您有用,请打赏仨瓜俩枣的!
这篇关于Anroid触摸事件分发机制---ViewGroup相关的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!