深入源码理解Android Touch事件分发机制(下篇)
来源:互联网 发布:淘宝旺旺客服 编辑:程序博客网 时间:2024/05/21 08:52
上文我们彻底弄清楚了onTouch、onTouchEvent、onClick这三者的区别和联系,也弄清楚Touch事件的传递原则以及事件在Activity、DecorView中的分发和传递。也给大家初步介绍了跟Touch事件分发息息相关的三个最重要的方法dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent,并给大家留下了一个疑惑:Touch事件在ViewGroup和View中是怎么分发和传递的? 那么,本篇我们将为大家解决这个疑惑,重点介绍Touch事件在ViewGroup和View中的分发机制。
首先要带大家探究的就是ViewGroup对Touch事件的分发过程,其主要实现是在ViewGroup的dispatchTouchEvent方法中实现的,该方法可以说是相当长、相当复杂。我们一段段来分析,我们先来看一下第一段源码:
// 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); 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; }
很明显这一段是对对当前ViewGroup是否该拦截Touch事件进行判断。当当前的是DOWN事件或者当mFirstTouchTarget != null时会判断是否拦截当前事件。那么,这里读者可能有一个疑惑:mFirstTouchTarget是个什么东东??? 看官别急,从后面的逻辑咱们可以看到,当ViewGroup的子View处理了Touch事件后,mFirstTouchTarget就会被赋值并指向该子View,换言之就是当ViewGroup不拦截事件并将其交给子View来处理是mFirstTouchTarget != null,一旦ViewGroup拦截事件那么mFirstTouchTarget != null就不成立了,那么当MOVE和UP事件到来的时候就不会走到ViewGroup的onInterceptTouchEvent方法中去了,整个一系列的事件将全部由该ViewGroup来处理。
当然也有一种特殊情况,那就是FLAG_DISALLOW_INTERCEPT这个标记位,它是由requestDisallowInterceptTouchEvent方法来设置的,一般是在子View中调用。一旦FLAG_DISALLOW_INTERCEPT设置后,ViewGroup将无法拦截除了DOWN事件外的其它事件。为什么会是除了DOWN事件外的其它事件呢?因为如果是DOWN事件,那么就会重置FLAG_DISALLOW_INTERCEPT标记位,从而使子View设置的该标记位失效。所以如果是DOWN事件的话,总会调用onInterceptTouchEvent方法来询问是否拦截,这点从上述源码也能看出。通过下面的这段源码,我们能对ViewGroup处理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); resetTouchState(); }如果是DOWN事件,会停止和清除所有的TouchTarget也就是说此时mFirstTouchTarget肯定为空,并调用resetTouchState方法对FLAG_DISALLOW_INTERCEPT进行重置。至此,我们对ViewGroup的dispatchTouchEvent方法基本上已经了解清楚了,当然这还是纯粹从源码的角度来分析的,我们还得写个小demo来进行辅助验证:同样的,我们还是写一个MainActivity,里面有一个继承LinearLayout的自定义ViewGroup——TestLayout,该ViewGroup中放一个自定义的子View——TestView。
/** * Created by leevi on 16/9/1. */public class TestLayout extends LinearLayout{ private Context mContext; public TestLayout(Context context) { this(context,null); mContext = context; } public TestLayout(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.d("TestLayout", "dispatchTouchEvent!!!!!!"); return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.d("TestLayout", "onInterceptTouchEvent!!!!!!"); return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { Log.d("TestLayout", "onTouchEvent!!!!!!"); return super.onTouchEvent(event); }}先只关注ViewGroup的情况,这种情况下,我们点击TestView,能得到如下log:
很明显可以看出能走到onInterceptTouchEvent方法,那我们再将dispatchTouchEvent方法的返回值改成true和false看看是什么结果:
返回值改成true的情况:
很明显可以看出,走了两次dispatchTouchEvent方法,说明DOWN 和 UP事件都只走到ViewGroup的dispatchTouchEvent方法就不能往下走了,会父上传递给父控件的onTouchEvent方法(后面会聊到。)
那再来看看返回值是false的情况吧:
纳尼!!!!!!!居然只打印了一次dispatchTouchEvent,这大大出乎我们的意料吧,意思是当我们DOWN事件到来后碰到这种情况整个Touch事件就结束了,后面的MOVE和UP事件都不会处理了。
所以我们可以得出结论,只有当ViewGroup的dispatchTouchEvent返回super.dispatchTouchEvent(event)的时候才能将事件传递下去。
那接下来我们就再以这个例子来探讨一下ViewGroup的onInterceptTouchEvent方法。
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; } return false; }
以上是ViewGroup的onInterceptTouchEvent的源码,我们可以理解成默认是返回false的,表示不拦截Touch事件。那么默认情况下,Touch事件是怎么传递的呢?在这里我要强调一句:View是没有onInterceptTouchEvent方法的,只有dispatchTouchEvent、onTouchEvent。
当ViewGroup的onInterceptTouchEvent返回super或者false的时候,我们会得到以下log:
从以上log我们可以看出,事件由ViewGroup的dispatchTouchEvent方法传递给onInterceptTouchEvent方法,再传递给子View的dispatchTouchEvent方法,最后传递给子View的onTouchEvent方法来处理。
那如果我们将TestLayout的onInterceptTouchEvent方法的返回值改成true来拦截该事件呢?我们又会得到什么情况呢?
我们可以发现,该事件不会传递到子View,并且会由onInterceptTouchEvent传递给onTouchEvent,由于没有任何部分能处理DOWN事件,所以MOVE和UP事件也不复存在了。这就是我们看到上述log并没有打印两次的原因。
至此,Touch事件在ViewGroup中的传递我们基本上已经完全弄清楚了。那接下来我们看看当事件传递给子View后是怎么分发和传递的呢?首先我们也是来研究一下View的dispatchTouchEvent方法。同样还是从源码入手:
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(); } if (onFilterTouchEventForSecurity(event)) { if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true; } //noinspection SimplifiableIfStatement 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; } } 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; }
相比较ViewGroup的dispatchTouchEvent方法,View的dispatchTouchEvent方法要简便得多,主要是判断了有没有注册onTouchListener,如果onTouchListener中的onTouch方法返回true则不会走onTouchEvent方法。这也解释了我们在前面一篇所得到的onTouch方法的优先级高于onTouchEvent。从上面的log我们也能看出,当View 的dispatchTouchEvent返回super.dispatchTouchEvent时,事件是会传递给View的onTouchEvent的。那我们将该返回值改成true和false再来看看log的情况:
先将返回值改为true。
改成false的情况如下:
我们可以看出,跟ViewGroup的情况非常相似,无论是返回true还是false,事件都没办法传递到View的TouchEvent中来。而这里我们会发现一个特别有意思的地方,当返回值为false的时候,事件会向上传递给父容器的onTouchEvent方法。这就是Touch事件的传递规则,先由外至内,如果内部不消化的话再由内传递至外。
我们还是可以得出一个结论,只有当dispatchTouchEvent返回值是super.dispatchTouchEvent(ev)时,Touch事件才能向下传递。
那最后我们再来看看View的onTouchEvent方法,默认返回值是false,代表不处理,不处理的话会传递给父控件的onTouchEvent方法。那我们如果将onTouchEvent的返回值改成true呢?
我们可以看到,onTouchEvent方法返回true就将该事件消费了,事件不会再由内向外传递。那我们就通过一张流程图来总结一下Touch事件在ViewGroup和View中的分发和传递。
通过上图,我们已经非常清楚地了解到了事件的分发机制。那最后我们就来了解一下,解决实际开发中冲突的两类方法:
(1)、外部拦截法。所谓外部拦截法,就是Touch事件先传递给父容器,由父容器来决定拦截处理。如果父容器需要该事件,那就在onInterceptTouchEvent中返回true进行拦截,如果不需要该事件那就在onInterceptTouchEvent中返回false,将事件传递给子View。
(2)、内部拦截法。内部拦截法顾名思义就是父控件默认不拦截任何事件,所有事件全部传递给子View,如果子View需要事件则在onTouchEvent中返回true消费,否则返回false交给父容器处理。这里需要配合requestDisallowInterceptTouchEvent方法使用,才能达到想要的效果。我们要重写子View的dispatchTouchEvent方法,根据具体情况调用requestDisallowInterceptTouchEvent来对请求父控件对事件进行拦截和不拦截。这里需要特别注意的是,父控件一定不能拦截DOWN事件,要不然所有事件都不会传递到子View了(前面已经验证了),内部拦截法也起不了作用了。
至此,Touch事件分发机制基本上全部讲完了,相信大家结合上下两篇文章,对事件分发机制都有较为深入的了解了。后续还会带来更多高质量的博客,如果大家有什么需要深入了解的知识点,也可以再留言里跟我说,我可以根据你们的留言写相应的博客。
- 深入源码理解Android Touch事件分发机制(下篇)
- 深入源码理解Android Touch事件分发机制(上篇)
- android Touch事件分发深入理解
- 深入理解Android事件分发机制
- 深入理解Android事件分发机制
- android Touch()事件分发机制
- 深入解析Android事件分发机制源码(1)
- Android Touch事件分发机制
- Android Touch事件分发机制
- android touch事件分发机制
- android Touch事件分发机制
- Android Touch事件分发机制
- Android Touch事件分发机制
- Android:Touch事件分发机制
- Android Touch事件分发机制
- Android,Touch事件分发机制。
- Android Touch事件分发机制
- Android Touch 事件分发机制
- 2.观察者模式(Observer Pattern)
- 关于CSR8670按键处理
- 【整理】JQ样式篇——属性与样式
- linux kernel (proc文件系统)参数
- SQL Server Browser 与动态端口
- 深入源码理解Android Touch事件分发机制(下篇)
- 阅读 ANDROID 源码的一些姿势
- Linux下配置网络
- 【JAVA基础小问题】如何跳出当前的多重嵌套循环?
- 容器云平台使用体验:DaoCloud
- 考试题目“部落卫队”
- 3.装饰者模式(Decorator Pattern)
- Linux基本操作命令
- 虚拟空间iis环境下如何隐藏index.php