Android事件分发机制原理及源码分析
面试Android事件分发机制原理及源码分析(Andro5.0)
- 概述
- Touch事件的三个方法
- 1:dispatchTouchEvent(ev)事件分发
- 2:onInterceptTouchEvent(MotionEvent ev)事件拦截
- 3:onTouchEvent(MotionEvent ev)事件响应
- Activity的事件分发
- 1:Activity的dispatchTouchEvent方法源码分析
- 2:Activity的onTouchEvent方法源码:
- ViewGroup的事件分发机制
- 1:ViewGroup的dispatchTouchEvent(ev)方法源码分析
- 2: ViewGroup的onInterceptTouchEvent (ev)方法源码分析
- View的事件分发机制
- 1: View的dispatchTouchEvent方法源码:
- 2: View的onTouchEvent方法源码:
首先附上我的几篇其它文章链接感兴趣的可以看看,如果文章有异议的地方欢迎指出,共同进步,顺便点赞谢谢!!!
Android framework 源码分析之Activity启动流程(android 8.0)
Android studio编写第一个NDK工程的过程详解(附Demo下载地址)
面试必备1:HashMap(JDK1.8)原理以及源码分析
面试必备2:JDK1.8LinkedHashMap实现原理及源码分析
View事件的滑动冲突以及解决方案
Handler机制一篇文章深入分析Handler、Message、MessageQueue、Looper流程和源码
Android三级缓存原理及用LruCache、DiskLruCache实现一个三级缓存的ImageLoader
概述
Android中的事件分发机制指的是事件从Activity–>ViewGroup—>View的传递,然后在由View–>ViewGroup–>Activity依次响应的过程,主要与 Touch 事件相关的三个重要方法息息相关:
- dispatchTouchEvent(MotionEvent ev)负责事件的分发
- onInterceptTouchEvent(MotionEvent ev)用来判断是否拦截某个事件
- onTouchEvent(MotionEvent ev)用来判断是否响应响应该事件
本文的源码分析是基于Andro5.0进行的分析,它比之前的逻辑更加复杂,但是原理是一致的,5.0之前的源码分析大家可以参考这篇文章:
https://blog.csdn.net/carson_ho/article/details/54136311
Touch事件的三个方法
1:dispatchTouchEvent(ev)事件分发
Touch 事件发生时 Activity 的 dispatchTouchEvent(MotionEvent ev) 方法会以隧道方式(从根元素依次往下传递直到最内层子元素或在中间某一元素中由于某一条件停止传递)将事件传递给最外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法,并由该 View 的 dispatchTouchEvent(MotionEvent ev) 方法对事件进行分发。dispatchTouchEvent 的事件分发逻辑
- 如果 return true,事件会分发给当前 View 并由 onTouchEvent 方法进行消费,同时事件会停止向下传递;
- 如果 return false,事件分发分为两种情况:如果当前 View 获取的事件直接来自 Activity,则会将事件返回给 Activity 的 onTouchEvent 进行消费;如果当前 View 获取的事件来自外层父控件,则会将事件返回给父 View 的 onTouchEvent 进行消费。但是这两种情况都遵循一个原则就是将事件返回外层控件的onTouchEvent进行消费。
- 如果返回系统默认的 super.dispatchTouchEvent(ev),事件会自动的分发给当前 View 的 onInterceptTouchEvent 方法。
2:onInterceptTouchEvent(MotionEvent ev)事件拦截
onInterceptTouchEvent 的事件拦截逻辑如下:
- 如果 onInterceptTouchEvent 返回 true,则表示将事件进行拦截,并将拦截到的事件交由当前 View 的 onTouchEvent 进行处理;
- 如果 onInterceptTouchEvent 返回 false,则表示将事件放行,当前 View 上的事件会被传递到子 View 上,再由子 View 的 dispatchTouchEvent 来开始这个事件的分发;
- 如果 onInterceptTouchEvent 返回 super.onInterceptTouchEvent(ev),逻辑和返回 false 时相同。
- 需要注意的是此方法存在于ViewGroup中,在View中没有此方法。
3:onTouchEvent(MotionEvent ev)事件响应
onTouchEvent 的事件响应逻辑如下:
- 如 果事件传递到当前 View 的 onTouchEvent 方法,而该方法返回了 false,那么这个事件会从当前 View 向上传递,并且都是由上层 View 的 onTouchEvent 来接收,如果传递到上面的 onTouchEvent 也返回 false,这个事件就会“消失”,而且接收不到下一次事件。
- 如果返回了 true 则会接收并消费该事件。
- 如果返回 super.onTouchEvent(ev) 默认处理事件的逻辑和返回 false 时相同。
事件的分发机制主要和这三个方法有关,具体的验证,自己可一区写一个demo,自定义VIewGroup和View复写Activity和自定义ViewGroup和View的三个方法,修改其返回值去测试,在这里我就不贴上自己的Demo了。注意事件机制内部的一个优化,第一次传递到这个view时没有进行处理,则讲接收不到下个事件。
Activity的事件分发
1:Activity的dispatchTouchEvent方法源码分析
当一个事件发生时首先传递到Activity的dispatchTouchEvent方法,其源码如下:
/** * 源码分析:Activity的dispatchTouchEvent() */ public boolean dispatchTouchEvent(MotionEvent ev) { // 一般事件列开始都是DOWN事件 = 按下事件,故此处基本是true if (ev.getAction() == MotionEvent.ACTION_DOWN) { //onUserInteraction() 在Activity中为空方法。留给用户来重写的:一般用来实现屏保功能 onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) {//getWindow().superDispatchTouchEvent(ev) return true; //1:Activity中对事件的分发是通过getWindow().superDispatchTouchEvent(ev)将事件交给PhoneWindow // 2:若getWindow().superDispatchTouchEvent(ev)的返回true //:3:则Activity.dispatchTouchEvent()就返回true,则方法结束。即 :该点击事件停止往下传递 & 事件传递过程结束 } //:4:superDispatchTouchEvent返回false时:继续往下调用Activity.onTouchEvent return onTouchEvent(ev); }
PhoneWindow的dispatchTouchEvent方法源码:
@Override/*** a. DecorView类是PhoneWindow类的一个内部类 * b. DecorView继承自FrameLayout,是所有界面的父类 * c. FrameLayout是ViewGroup的子类,故DecorView的间接父类 = ViewGroup */public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event);}
接下来看一下DecorView.superDispatchTouchEvent(event)
/** * DecorView.superDispatchTouchEvent(event) * 定义:属于顶层View(DecorView) * 说明: * a. DecorView类是PhoneWindow类的一个内部类 * b. DecorView继承自FrameLayout,是所有界面的父类 * c. FrameLayout是ViewGroup的子类,故DecorView的间接父类 = ViewGroup */ public boolean superDispatchTouchEvent(MotionEvent event) { // 调用父类的方法 : ViewGroup的dispatchTouchEvent() 即 将事件传递到ViewGroup去处理 return super.dispatchTouchEvent(event); }
2:Activity的onTouchEvent方法源码:
/***当一个点击事件未被Activity内任何一个View接收 / 处理时,就会回到Activity 的onTouchEvent * public boolean onTouchEvent(MotionEvent event) {//是否要关闭事件: 只有在点击事件在Window边界外才会返回true,一般情况都返回false if (mWindow.shouldCloseOnTouch(this, event)) { //消费事件 finish(); return true; } return false; } /** @hide */ public boolean shouldCloseOnTouch(Context context, MotionEvent event) { // // 主要是对于处理边界外点击事件的判断:是否是DOWN事件,event的坐标是否在边界内等 if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event) && peekDecorView() != null) { return true;//消费事件 } return false; }
经以上源码分析:
- Activity事件分发方法getWindow().superDispatchTouchEvent(ev)返回值表示事件在Activity的view中事件是否分发成功,如果为true则事件分发成功(既被Activity中的view所消耗),如果为false,就调用Activity自己的onTouchEvent()方法来处理事件。
- 事件由Activity传递到Activity经历了:Activity–>PhoneWindow----->DecorView---->ViewGroup的过程
ViewGroup的事件分发机制
1:ViewGroup的dispatchTouchEvent(ev)方法源码分析
@Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(ev, 1); } // If the event targets the accessibility focused view and this is it, start // normal event dispatch. Maybe a descendant is what will handle the click. if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) { ev.setTargetAccessibilityFocus(false); } boolean handled = false; // onFilterTouchEventForSecurity()用安全机制来过滤触摸事件,true为不过滤分发下去,false则销毁掉该事件。 // 方法具体实现是去判断是否被其它窗口遮挡住了,如果遮挡住就要过滤掉该事件。onFilterTouchEventForSecurity的源码不是重点就不在这里分析了 if (onFilterTouchEventForSecurity(ev)) { // // 没有被其它窗口遮住 final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; // 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. //重点分析1: //上面这一块注释说的很清楚了,就是在`Down事件的时候把所有的状态都重置,作为一个新事件的开始。因为事件是从DOWN开始UP结束 // 如果是Down,那么mFirstTouchTarget到这里肯定是`null`.因为是新一系列手势的开始。 // mFirstTouchTarget是处理第一个事件的目标。 cancelAndClearTouchTargets(ev);/把所有的状态都重置 resetTouchState(); } // // 检查是否拦截该事件(如果onInterceptTouchEvent()返回true就拦截该事件) final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; // disallowIntercept:表示是否禁用事件拦截功能, 默认是`false`,可以通过`requestDisallowInterceptTouchEvent(true)`方法来设置,通知父`View`不要拦截该`View`上的事件。 if (!disallowIntercept) { //每次都要通过onInterceptTouchEvent 判断该`ViewGroup`是否要拦截该事件。`onInterceptTouchEvent()`方法默认返回`false`即不拦截。 intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { // 子`View`通知父`View`不要拦截。这样就不会走到上面`onInterceptTouchEvent()`方法中了, // 所以父`View`就不会拦截该事件。 intercepted = false; } } else { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. // 就是没有目标来处理该事件,而且也不是一个新的事件`Down`事件(新事件的开始), 拦截该事件。即点击的空白处没有子控件拦截改事件 intercepted = true; } // If intercepted, start normal event dispatch. Also if there is already // a view that is handling the gesture, do normal event dispatch. if (intercepted || mFirstTouchTarget != null) { ev.setTargetAccessibilityFocus(false); } //.检查当前是否是Cancel事件或者是有Cancel标记。 final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; // 这行代码为是否需要将当前的触摸事件分发给多个子`View`,默认为`true`,分发给多个`View`(比如几个子`View`位置重叠)。默认是true final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; // 用于记录当前要分发给的哪个View TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; //没取消并且没有拦截 if (!canceled && !intercepted) { // 下面这部分代码的意思其实就是找到该事件位置下的`View`(可见或者是在动画中的View), 并且与`pointID`关联 View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? findChildWithAccessibilityFocus() : null; if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int actionIndex = ev.getActionIndex(); // always 0 for down final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS; // Clean up earlier touch targets for this pointer id in case they // have become out of sync. removePointersFromTouchTargets(idBitsToAssign); final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); // Find a child that can receive the event. // Scan children from front to back. final ArrayList preorderedList = buildTouchDispatchChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); // for循环遍历找子View进行分发了。 final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } // canViewReceivePointerEvents()`方法会去判断这个View是否可见或者在播放动画,只有这两种情况下可以接受事件的分发 // isTransformedTouchPointInView判断这个事件的坐标值是否在该`View`内。 //此处就不在此深入分析这两个方法的源码了: if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; }//可见&&事件在该View内 往下执行 // 找到该`View`对应的在`mFristTouchTarget`中的存储的目标, 判断这个`View`可能已经不是之前`mFristTouchTarget`中的`View`了。 // 如果找不到就返回null, 这种情况是用于多点触摸, 比如在同一个`View`上按下了多跟手指。 newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { //该View已经接受了这个事件了 , 找到该View了,不用再循环找了 newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); // 如果上面没有break,只有newTouchTarget为null,说明上面我们找到的Child View和之前的肯定不是同一个了, // 是新增的, 比如多点触摸的时候,一个手指按在了这个`View`上,另一个手指按在了另一个`View`上。 // 这时候我们就看child是否分发该事件。dispatchTransformedTouchEvent如果child为null,就直接该ViewGroup处理事件 // 如果child不为null,就调用child.dispatchTouchEvent分发给子View if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // 如果这个Child View能分发,那我们就要把之前存储的值改变成现在的Child View。 mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { // childIndex points into presorted list, find original index for (int j = 0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); // 赋值成现在的Child View对应的值,并且同步更新`mFirstTouchTarget`也改成该值 (即mFristTouchTarget`与`newTouchTarget`是一样的) newTouchTarget = addTouchTarget(child, idBitsToAssign); //分发给子View了跳出循环 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); } if (preorderedList != null) preorderedList.clear(); } // newTouchTarget == null就是没有找到新的可以分发该事件的子View,那我们用上一次的分发对象了。 if (newTouchTarget == null && mFirstTouchTarget != null) { // Did not find a child to receive the event. // Assign the pointer to the least recently added target. newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } } // DOWN事件在上面代码中会去找touch target if (mFirstTouchTarget == null) { // dispatchTransformedTouchEvent方法中如果child为null,那么就调用super.dispatchTouchEvent(transformedEvent);否则调用child.dispatchTouchEvent(transformedEvent) // No touch targets so treat this as an ordinary view.没有子View接收事件 自己处理 handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { //进行分发事件操作 // Dispatch to touch targets, excluding the new touch target if we already // dispatched to it. Cancel touch targets if necessary. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; // 找到了新的子View,并且这个是新加的对象,上面已经处理过了 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { // 否则都调用dispatchTransformedTouchEvent处理,传递给child final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; //分发给子View if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } // 如果是onInterceptTouchEvent返回true就会遍历mFirstTouchTarget全部给销毁, //这就是为什么onInterceptTouchEvent返回true,之后所有的事件都不会再继续分发的了的原因 if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } // Update list of touch targets for pointer up or cancel, if needed. if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { resetTouchState(); } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { // 抬起UP事件发生的时候,清除它相关的数据。 final int actionIndex = ev.getActionIndex(); final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); removePointersFromTouchTargets(idBitsToRemove); } } if (!handled && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); } return handled; }
接下来看一看dispatchTransformedTouchEvent()方法的源码:
/** * 此方法主要是用来处理事件的,此处指展示核心代码,以便理解 * @param event * @param cancel * @param child 不为null时则将事件交给自 View的dispatchTouchEvent去处理 * @param desiredPointerIdBits * @return */ private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; final int oldAction = event.getAction(); //被拦截之后,之前处理过该事件的View会收到CANCEL的原因是在此处进行了设置 if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { //重点 //自己处理,交给自己的父类View的dispatchTouchEvent处理,View的super.dispatchTouchEvent方法将在下面进行分析 handled = super.dispatchTouchEvent(event); } else { //子View去处理,如果子View仍然是ViewGroup还是执行同样的处理, //如果子View是普通View,普通View的dispatchTouchEveent()会调用onTouchEvent() handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } ...................//省略部分代码 return handled; }
2: ViewGroup的onInterceptTouchEvent (ev)方法源码分析
public boolean onInterceptTouchEvent(MotionEvent ev) {//此方法比较简单判断是否拦截,默认情况下返回false 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的 onTouchEvent方法调用的是父类View的 onTouchEvent方法,所以在这里不进行分析,将在下面View的事件分发机制中进行分析。
View的事件分发机制
1: 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(); }// 判断该View是否被其它View遮盖住。 if (onFilterTouchEventForSecurity(event)) { if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true; } /** *重点:当这三个条件都成立是View的dispatchTouchEvent方法才返回true ,即事件结束,否则会掉View的onTouchEvent方法 * 条件一mListenerInfo!=null: 看源码可知无论设置什么监听都会出初始化mListenerInfo,顾mListenerInfo!=null成立 * 条件二 (mViewFlags & ENABLED_MASK) == ENABLED:即View的enable属性为true时 * 条件三 mListenerInfo.mOnTouchListener.onTouch(this, event)返回true需要我们手动复写onTouch方法 */ ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) {//先调用onTouch方法 result = true; } if (!result && onTouchEvent(event)) {//后调用onTouchEvent方法,onTouchEvent的源码将在下面进行详细分析, //在此需要注意:onClick是在onTouchEvent的DOWN事件中触发的,所以比onTouch执行的晚 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; }
2: View的onTouchEvent方法源码:
public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; final int action = event.getAction(); final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;// 对disable按钮的处理,一个disable但是clickable的view仍然会消耗事件,只是不响应而已。 if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return clickable; } * * * 关于TouchDelegate,文档中是这样说的The delegate to handle touch events that are physically in this view * but should be handled by another view. 就是说如果两个View, View2在View1中,View1比较大,如果我们想点击 * View1的时候,让View2去响应点击事件,这时候就需要使用TouchDelegate来设置。 * 简单的理解就是如果这个View有自己的事件委托处理人,就交给委托人处理。 */ if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { switch (action) { case MotionEvent.ACTION_UP: mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; if ((viewFlags & TOOLTIP) == TOOLTIP) { handleTooltipUp(); } if (!clickable) { removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; break; } // 最好先看DOWN后再看MOVE最后看UP。 // PFLAG_PREPRESSED 表示在一个可滚动的容器中,要稍后才能确定是按下还是滚动. // PFLAG_PRESSED 表示不是在一个可滚动的容器中,已经可以确定按下这一操作 boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in // touch mode. boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { //如果现在还没获取到焦点,就再获取一次焦点 focusTaken = requestFocus(); } // 在前面DOWN事件的时候会延迟显示View的pressed状态,用户可能在我们还没有显示按下状态效果时就不按了.我们还是得在进行实际的点击操作时,让用户看到效果。 if (prepressed) { // The button is being released before we actually // showed it as pressed. Make it show the pressed // state now (before scheduling the click) to ensure // the user sees it. setPressed(true, x, y); }// /判断不是长按 if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } // PerformClick就是个Runnable,里面执行performClick()方法,在onClick中调用OnCliclListener的onclick方法,其源码就不在这里分析了 if (!post(mPerformClick)) { performClick(); } if (!post(mPerformClick)) { performClick(); } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); }// 取消按下状态,UnsetPressedState也是个Runnable,里面执行setPressed(false) if (prepressed) { postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } mIgnoreNextUpEvent = false; break; case MotionEvent.ACTION_DOWN: if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) { mPrivateFlags3 |= PFLAG3_FINGER_DOWN; } mHasPerformedLongPress = false; if (!clickable) { checkForLongClick(0, x, y); break; }//处理鼠标右键菜单,有些View显示右键菜单就直接弹菜单.一般设备用不到鼠标,所以返回false。 if (performButtonActionOnTouchDown(event)) { break; } // Walk up the hierarchy to determine if we're inside a scrolling container. boolean isInScrollingContainer = isInScrollingContainer(); // For views inside a scrolling container, delay the pressed feedback for // a short period in case this is a scroll. //遍历下View层级,判断这个View是不是一个能scroll,如果可以滚动则需要判断是滚动还是点击监听 if (isInScrollingContainer) { // 因为用户可能是点击或者是滚动,所以我们不能立马判断,先给用户设置一个要点击的事件。 mPrivateFlags |= PFLAG_PREPRESSED; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPendingCheckForTap.x = event.getX(); mPendingCheckForTap.y = event.getY(); //发送一个延时的操作,用于判断用户到底是点击还是滚动。其实就是在tapTimeout中如果用户没有滚动,那就是点击了。 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // Not inside a scrolling container, so show the feedback right away //设置成点击状态 setPressed(true, x, y); // 检查是否是长按,就是过一段时间后如果还在按住,那就是长按了。 checkForLongClick(0, x, y); } break; case MotionEvent.ACTION_CANCEL: // 取消按下状态,移除个监听,回复初始值 if (clickable) { setPressed(false); } removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; break; case MotionEvent.ACTION_MOVE: if (clickable) { drawableHotspotChanged(x, y); } // Be lenient about moving outside of buttons判断是否移动到区域外面 if (!pointInView(x, y, mTouchSlop)) { // Outside button ///移动到区域外面去了,就要取消点击。 // Remove any future long press/tap checks removeTapCallback(); removeLongPressCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; } break; } return true; } return false; }
事件分发机制一个应用场景就是事件的滑动冲突,大家可以参考一下我的另一篇博客View事件的滑动冲突以及解决方案
到此为止事件分发机制的原理和源码基本分析完,里面不对的地方请大家留言指正,共同进步!!!!!!
更多相关文章
- Android事件分发-总结
- Android ListView 事件监听 || 关于ListView选中时显示的效果。
- Android Retrofit 源码系列(一)~ 原理剖析
- [置顶] Android系统安全之旅 第1章 编译Android程序的方法
- Android使背景灯(Brightness)高亮的方法
- android spinner自动弹出列表,设置title,TtextView不换行自动截取
- 为android-support-v4.jar打包源码