android触摸event传递源码分析二
来源:互联网 发布:练英语听力什么软件 编辑:程序博客网 时间:2024/06/07 00:59
上一篇文章分析了input event怎么传递到activity中,下面接着分析在activity中的具体传递,也是event事件传递最重要的地方。
接上回,先看看activity中收到event后:
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); //这里是空函数,什么都不干 } if (getWindow().superDispatchTouchEvent(ev)) { //这里传给phoneWindow return true; } return onTouchEvent(ev);}
所以会传递到phoneWindow的superDispatchTouchEvent()方法,这里可以看出,如果phoneWindow把传递的事件down, move,up拦截了,即返回了true,那么activity的onTouchEvent()将不会获得到这个事件,相反如果phoneWindow不处理这个事件,那么这个事件都会传给activity,同时也知道activity的onTouchEvent()也是最后处理event事件的,可以看下面phoneWindow代码的分析。
@Override public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); //phoneWindow的内部类DecorView }
事件传递给DecorView:
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker { @Override public boolean dispatchTouchEvent(MotionEvent ev) { final Callback cb = getCallback(); return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev); } public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); //这里传给了DecorView父亲,即ViewGroup }}
由于DecorView本身是一个FrameLayout,FrameLayout 继承于ViewGroup,那么事件最终传递到ViewGroup里面:
public boolean dispatchTouchEvent(MotionEvent ev){ final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; if (actionMasked == MotionEvent.ACTION_DOWN) { // Throw away all previous state when starting a new touch gesture. //一个down事件到来,就会认为是一个新的touch事件到来, //所以如果mFirstTouchTarget != null,就会给child view传递cancel事件 cancelAndClearTouchTargets(ev); resetTouchState(); //设置mFirstTouchTarget == null } final boolean intercepted; //只有在down事件下才进行判断是否拦截 , //mFirstTouchTarget != null 表示有child view要处理事件 if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { //默认viewgroup都是能拦截事件的 intercepted = onInterceptTouchEvent(ev); //判断当前ViewGroup有没有拦截触摸事件, 注意拦截不代表消费, //比如若一个viewGroup在这里onInterceptTouchEvent的down事件返回了ture, //就会后面调用它自己的onTouchEvent(),看看自己是否消费此事件。 //这里还有一种情况,不是down事件,比如move事件,而viewgroup的child view //消费了down事件,viewgroup中途对事件进行了拦截move事件,这里置intercepted==true, //还是会导致它的child view收不到以后的事件 } 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; //如果不是down事件, //且mFirstTouchTarget == null,即没有child View 对前面的down返回true, //则直接设置intercepted = true, //注意最顶层的View是PhoneWindow的DecorView, //所以如果它的child view没有把事件拦截了, //则以后所有的move,up事件都不会传给它下面的child View了, //这样activity里的view group或view,都有机会得到down事件, //但是如果不消费down事件,则以后无法得到后续的事件, //因为activity的最顶层DecorView就把事件给拦截了。 } TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; //如果此VierGruop 没有拦截了down事件,事件也不是cancel事件 if (!canceled && !intercepted) { if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { //如果是down事件 final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { //如果child view个数不是0 //x , y是down事件相对于此viewgroup的点击位置 final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { //循环遍历child view //判断点击的位置是否在此child View上 //canViewReceivePointerEvents是判断view是否可以接受点击事件, //只有visible和动画中的view可以接受点击事件, //isTransformedTouchPointInView是对点击的x,y换算, //判断点击事件是否在此child view上 if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; //如果没有点击在此child view上,则进入下一次循环 } //这里是最关键的方法,把down事件传递给当前被点击的child View , //注意这里必须是此有child View的的子view在dispatchTouchEvent, //OnTouchListener.onTouch或onTouchEvent(event)返回了true, //那么dispatchTransformedTouchEvent方法才会返回true,后面会单独分析它 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { //这里设置mFirstTouchTarget = child newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } } } } } //当前viewGroup的child view都不处理这次down事件,那么mFirstTouchTarget == null //不处理是指其Child View 的OnTouchListener.onTouch //或onTouchEvent(event)返回了flase 或 调用的super方法 if (mFirstTouchTarget == null) { //这里又传递给了dispatchTransformedTouchEvent方法,但是注意这里第三个参数,传递了null, //第三个参数表示child view,传递null,其实就是传递给viewgroup自己,看看自己有没有处理。 handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { //如果有child View处理点击事件,则down , move , up都进入这里 TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; // mFirstTouchTarget 就是被点中的child view while (target != null) { final TouchTarget next = target.next; //这里单指测试时next总为null if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; //如果是down 事件,已经传递过,就会进入这里 } else { //如果move事件突然被viewgroup拦截了,即intercepted==true,则导致cancelChild==true final boolean cancelChild = resetCancelNextUpFlag(target.child)||intercepted; // 非down 事件会进入这里, 又递归传递了, //这里每个被传递过down事件的ViewGroup的mFirstTouchTarget都不为null, //这样递归传递直到非down事件被传到最终被点击的View上 //注意这里面如果整个activity的child view 返回了false, //则 down 和 up 事件又会传递给activity 的 onTouchEvent() //注意如果是viewgroup中途拦截了move或up事件,这里会给child view传递一个cancel事件, //并且后面会设置mFirstTouchTarget = null,这样后续事件将不会传递给child view if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { //如果取消传递给child view if (predecessor == null) { //循环直到设置mFirstTouchTarget == null, //那么下次事件再到来时就不会传递给child view了 mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } return handled;}
下面看看上面函数里出现过三次的dispatchTransformedTouchEvent()方法,这个方法是实现传递的关键:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; final int oldAction = event.getAction(); //上面dispatchTouchEvent函数第三次调用本函数时, //若viewgroup中途拦截了事件时,cancel 会为true if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); //把事件设为取消事件 if (child == null) { //调用到了自己父类,因为viewgroup继承于view,最终调用到view的dispatchTouchEvent handled = super.dispatchTouchEvent(event); } else {//child不为空 //传递给child view上,这里实现了递归一级级传递 handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } final MotionEvent transformedEvent; if (newPointerIdBits == oldPointerIdBits) { //手指触摸数没有变化 if (child == null || child.hasIdentityMatrix()) { if (child == null) { //上面dispatchTouchEvent函数第二次调用本函数时,即没用child处理down事件时, //传下来的child为null,这就实现了事件在viewgroup中自己本身的传递 //调用自己父类,因为viewgroup继承于view,最终调用view的dispatchTouchEvent handled = super.dispatchTouchEvent(event); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; event.offsetLocation(offsetX, offsetY); //传递给child view上,这里实现了递归一级级传递, //上面dispatchTouchEvent函数第一次调用本函数时,走这里 handled = child.dispatchTouchEvent(event); event.offsetLocation(-offsetX, -offsetY); } return handled; } transformedEvent = MotionEvent.obtain(event); } else { transformedEvent = event.split(newPointerIdBits); } // Done. transformedEvent.recycle(); return handled; }
由上面可知事件最终都会传递到view上,并首先传递到view的dispatchTouchEvent上:
public boolean dispatchTouchEvent(MotionEvent event) { boolean result = false; ListenerInfo li = mListenerInfo; //先会调用OnTouchListener的监听方法,看看onTouch方法是否返回true if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } //OnTouchListener返回了true,这里就不会再调用onTouchEvent()方法了 if (!result && onTouchEvent(event)) { result = true; }}public boolean onTouchEvent(MotionEvent event) { switch (action) { case MotionEvent.ACTION_UP: if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); //执行onclick方法 } break }}public boolean performClick() { playSoundEffect(SoundEffectConstants.CLICK);//点击声音 li.mOnClickListener.onClick(this); //执行onClickListener 点击回调}
到此input event事件源码分析就完了,可以看出源码实现了一个精妙,但也很复杂的事件传递逻辑。
总结
event 事件传递流程:
phoneWindow —> Activity.dispatchTouchEvent() —> DecorView —> contentLayout(系统默认加载的activity父控件) —> activity layout —> ViewGroup —> View —> Activity.onTouchEvent()
event事件传递的函数流程:
dispatchTouchEven() —> onInterceptTouchEvent() (ViewGroup才有此方法) —> OnTouchListener.onTouch() –> onTouchEvent() —> OnClickListener.onClick()
注意:若是OnTouchListener.onTouch() 对事件返回了true,则事件不会走后面onTouchEvent和onClick。
down事件永远会从DecorView 一级级传递到被点击的view上,但若是在这个传递过程中,没有DecorView 下的child view对这个down事件消费,则后续的move, up事件都不会传给这些child view了。但是activity自己能拿到这些事件。
down事件传递过程中,如果某个ViewGroup对事件在onInterceptTouchEvent()中进行了拦截,则事件不会传递给它的child view,但是如果此ViewGroup 不在它自己onTouchEvent()对事件进行消费,DecorView 还是会认为没有child view消费触摸事件。
调用super方法或return false 都是不消费事件,只有return true才算消费事件。
如果child view消费了down事件后,在后续的move事件中被它的父类拦截了事件,则这个child view会收到一个cancel事件。后续事件就只会传递给它的父类。
完
更多精彩Android技术可以关注我们的微信公众号,扫一扫下方的二维码或搜索关注公共号:
Android老鸟
- android触摸event传递源码分析二
- Android O: 触摸事件传递流程源码分析(上)
- Android O: 触摸事件传递流程源码分析(下)
- Android探索------触摸事件传递机制分析
- Android的触摸事件传递分析
- Android触摸事件传递机制简要分析
- 源码分析Android触摸事件处理机制
- Android触摸事件派发机制详解与源码分析二(ViewGroup篇)
- Android中Activity触摸事件传递源码学习
- android触摸事件传递
- Android触摸事件传递
- android触摸事件传递
- Android 触摸事件传递
- cocos2d-x 源码分析 : EventDispatcher、EventListener、Event 源码分析 (新触摸机制,新的NotificationCenter机制)
- cocos2d-x 源码分析 : EventDispatcher、EventListener、Event 源码分析 (新触摸机制,新的NotificationCenter机制)
- 多层view触摸传递分析
- Android 从源码分析 Android 触摸事件分发过程
- libevent源码分析-event
- 分布式缓存技术redis学习系列(六)——sentinel哨兵机制
- 网络JQuery引用地址
- MySQL分区表
- 快速搭建LAMP步骤(复习使用)
- 总结2: PHP中的魔术方法及其应用场景
- android触摸event传递源码分析二
- 解决Windows USB被禁
- 【MyEclipse安装配置教程】五、安装MySQL和Navicat可视化数据库
- 带选择头像的用户注册页面
- python3 list、tuple(元组)、str之间的相互转换
- 1012: [JSOI2008]最大数maxnumber
- Ajax小例子
- Android7.0 之 直接启动
- linux 服务器相关