Android 事件分发机制
2015-11-05 17:47
573 查看
Android 事件分发机制
先看一下网上其他人总结的内容:/article/4917556.html
里面的返回值只的是在重写对应函数时,直接返回false,true还是super的意思。
/article/1361183.html
这一篇介绍了事件的传递流程。
通过了以上两篇博客,可以知道Android 事件分发过程主要是和以下几个函数相关:
dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()
涉及到的对象有Activity,ViewGroup,View
对象 | 函数 |
---|---|
Activity | dispatchTouchEvent( MotionEvent ev ) onTouchEvent( MotionEvent ev ) |
ViewGroup | dispatchTouchEvent( MotionEvent ev ) onInterceptTouchEvent( MotionEvent ev ) onTouchEvent( MotionEvent ev ) |
View | dispatchTouchEvent( MotionEvent ev ) onTouchEvent( MotionEvent ev ) |
Activity
/** Activity派发touch事件从这个函数开始,如果重写了这个函数,需用调用super.dispatchTouchEvent(ev)才能保证touch事件会正常的往下传递 **/ public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) {//先调用dispatchTouchEvent让viewGroup处理事件 return true; } return onTouchEvent(ev);//如果ViewGroup没有处理才让Activity的onTouch }
这里调用window.superDispatchTouchEvent(ev)最终调用的就是Android顶层viewDecorView(继承自FrameLayout)的dispatchTouchEvent(ev),其实也就是ViewGroup的dispatchTouchEvent(ev)。
ViewGroup
//去掉了一部分无关的代码 public boolean dispatchTouchEvent(MotionEvent ev) { boolean handled = false;//这一层的ViewGroup或它的子View是否消费了touch事件 final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; if (actionMasked == MotionEvent.ACTION_DOWN) { //收到down事件,重置标志,因为每一个手势都是从ACTION_DOWN开始的,以ACTION_UP结束 cancelAndClearTouchTargets(ev); resetTouchState(); } final boolean intercepted;//是否拦截了事件 //TouchTarget用来记录处理了事件view及其他相关信息。mFirstTouchTarget //记录了由该ViewGroup传递的且被消费了的第一个TouchTarget if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { //mFirstTouchTarget != null表示如果该ViewGroup没有消费ACTION_DOWN //事件则表示其他事件它不过心,也就不用拦截了 //这个标志可以由子view调用父ViewGroup.requestDisallowInterceptTouchEvent //设置,表示是否允许父view拦截事件 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); //默认返回false,当然像ListView,ScrollView会重写这个函数拦截对应事件, ev.setAction(action); } else { intercepted = false; } } else { //不是down事件且之前的没有消费down事件,直接拦截,其它事件不会向下传递给子View intercepted = true; } TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; //以下是向子View传递 down事件 的逻辑 if (!canceled && !intercepted) { if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { //for循环寻找 可能 处理该事件的子View for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = children[childIndex]; //判断child是否可以处理该事件,不符合条件跳出for循环继续寻找 if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } //找到了子View,调用child.dispatchTouchEvent()处理事件,如果子View也是ViewGroup //就又会回到了这个函数里面来了,然后就层层往下,一直到具体控件的dispatchTouchEvent if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { //子view消费了down事件,将处理该事件的TouchTarget放到List头,list保存 //的是处理过由该ViewGroup派发的事件的TouchTarget,这个可能跟多点触控有关。 newTouchTarget = addTouchTarget(child, idBitsToAssign);//这里会为mFirstTouchTarget赋值 alreadyDispatchedToNewTouchTarget = true; break; } } } } //非ACTION_DOWN事件直接走下面逻辑 if (mFirstTouchTarget == null) { //如果该ViewGroup拦截了ACTION_DOWN事件就不会走上面的逻辑,或者子View都不消费ACTION_DOWN, //mFirstTouchTarget就一直为null,以后所有的事件都在这里由自己消费 //dispatchTransformedTouchEvent的第三个参数为null,最终就会调用ViewGroup.onTouchEvent,如果是View就会调用View.dispatchTouchEvent handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { 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; //子View.dispatchTouchEvent处理 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; } } } return handled; }
//默认返回false不拦截,应该ViewGroup一般作为View的容器并不处理touch事件。 //但是想ListView,GridView这些可以滑动的容器肯定是要重装载拦截的了 public boolean onInterceptTouchEvent(MotionEvent ev) { return false; }
ViewGroup.onTouchEvent(MotionEvent ev)就是继承自View的同一个函数。
View
//很简单如果设置了onTouchListener就先调用onTouch,否则调用onTouchEvent public boolean dispatchTouchEvent(MotionEvent event) { if (onFilterTouchEventForSecurity(event)) { ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { return true; } if (onTouchEvent(event)) { return true; } } return false; }
onTouchEvent是事件最终消费的地方和派发过程无关就不分析了,这里主要处理View的press状态,点击事件,长按事件等。
总结
有了以上基础再来看事件的传递路径就更好理解了这张图是相关函数的调用顺序,这里分析了一种路径,ViewGroup那里是调用了DispatchTouchEvent,然后
DispatchTouchEvent调用了onInterceptToucheEvent.
再来看看两张事件传递过程的图片
从Activity的dispatchTouchEvent开始传递给ViewGroup的dispatchTouchEvent,如果所有的View都不拦截也不消费最后就会传递到Activity的onTouchEvent这里。
这一张在最后的View例如Button消费了ACTION_DOWN事件,上层的控件的onTouchEvent就不会收到事件。参照上面的ViewGroup的函数就是
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { //子view消费了down事件 newTouchTarget = addTouchTarget(child, idBitsToAssign);//这里会为mFirstTouchTarget赋值 alreadyDispatchedToNewTouchTarget = true; break; }
这样内层的ViewGroup的
alreadyDispatchedToNewTouchTarget == true
mFirstTouchTarget != null
newTouchTarget != null。
这样也就不会走以下的逻辑,也就不会调用内层ViewGroup的onTouchEvent事件,同时dispatchTouchEvent会返回true
//非ACTION_DOWN事件直接走下面逻辑 if (mFirstTouchTarget == null) { //如果该ViewGroup拦截了ACTION_DOWN事件就不会走上面的逻辑,或者子View都不消费ACTION_DOWN, //mFirstTouchTarget就一直为null,以后所有的事件都在这里由自己消费 //dispatchTransformedTouchEvent的第三个参数为null,最终就会调用ViewGroup.onTouchEvent,如果是View就会调用View.dispatchTouchEvent handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); }
而外层的ViewGroup不会调用onTouchEvent是因为,内层的ViewGroup返回true了,表示我已经消费了这个事件。这里的逻辑就和内层ViewGroup不调用onTouchEvent一样。
这一张是消费了ACTION_DOWN后,后续的事件的传递过程。
为什么这里的事件也不会被传到上层去呢?
看dispatchTouchEvent最下面的逻辑
//非ACTION_DOWN事件直接走下面逻辑 if (mFirstTouchTarget == null) { //因为之前已经拦截了ACTION_DOWN了,所以mFirstTouchTarget != null,这里的逻辑就不会执行。 //也就是子View消费了ACTION_DOWN事件,以后所有的事件都传给子View去处理,自身的onTouchEvent不处理 handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); }
如果在中间有一个事件被拦截了,底层的View就会收到一个ACTION_CANCEL事件
也是看最后的逻辑
final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; //cancelChild 为true,所以就传给下面的控件一个ACTION_CANCEL事件 if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; //这里mFirstTouchTarget变为null,以后的事件自己处理 } else { predecessor.next = next; } target.recycle(); target = next; continue; } }
相关文章推荐
- Android菜单详解——子菜单(SubMenu)
- Android-05 Android的MVC设计框架浅析
- Android中SQLite应用详解
- Android下拉刷新ListView设计
- 【Android】近日问题小计
- Android编程判断横屏、竖屏及设置横竖屏的方法
- Android是如何改变了嵌入式格局
- Android菜单详解(四)——使用上下文菜单ContextMenu
- 【Android学习之道】 四大组件之ContentProvider内容提供器
- AsyncTask的执行流程及其细节点
- 综述种类:Android组件之间 数据传递方法
- 在我工作中,我想让RelativeLayout设置按钮的效果,但是有时候没有效果,下面的方法可以解决问题
- Android使用Glide加载Gif.解决Glide加载Gif非常慢问题
- Android Studio 文件类型图标
- 关于android广播优先级的理解
- 自定义倒计时按钮
- android 通知栏
- Android中用textview展示doc文档保存格式
- 倒计时按钮
- Android开发&Dialog