在Android开发中,事件的分发机制是一块比较重要的知识体系,了解并熟悉Android中的事件分发机制有助于分析各种点击滑动失效问题,同时也能更好的去扩展控件的事件功能和开发自定义控件。这篇博客主要就是分析Android中的事件分发。

首先,来一张图说明Android中事件是怎样从Activity怎样一步一步传递到View的:

Android中的事件分发机制_第1张图片

针对上图需要说明几点:

1.对于图中的dispatchTouchEvent()方法,走return true/false线路时,表示是开发者重写的该方法;走super时,表示是开发者没有重写,或者是重写时调用super.dispatchTouchEvent()方法,表示是调用父类的处理方式;

2.图中的事件分发流程只是针对DOWN(按下)事件,MOVE(移动)和UP(抬起)事件在后面做分析;

针对上图做一个简单的分析:

① 从图中可以看到,事件的分发过程可以简单的分为三层,Activity层、ViewGroup层和View层,在Activity和View层当中与事件相关的方法主要是dispatchTouchEvent()方法和onTouchEvent()方法,而在ViewGroup中还多了一个onInterceptTouchevent()方法,表示事件拦截的方法;

② Activity获取到事件之后,如果开发者重写了Activity中的dispatchTouchEvent()方法并返回了true或false,事件就会被消费,否则就会调用系统的处理方式,走super,也就是走到了ViewGroup的dispatchTouchEvent()方法,至于为什么会从Activity的dispatchTouchEvent()走到ViewGroup中的方法,后面会有分析;

③ 事件到了ViewGroup组件上,同样,如果开发者重写了ViewGroup的dispatchTouchEvent()方法并返回了true,事件就会被消费;如果返回的false,那么事件就会直接返回给Activity的onTouchEvent()方法,让Activity的onTouchEvent()方法来处理;如果开发者没有重写,就会走super,那么就会调用ViewGroup的onInterceptTouchevent()方法(这个方法表示事件拦截的方法,返回true表示拦截事件,返回false表示不拦截事件,系统默认是返回false,不拦截事件);

④ 到了ViewGroup的onInterceptTouchevent()方法,如果开发者重写了ViewGroup的onInterceptTouchevent()方法并返回true,那么就会调用ViewGroup的onTouchEvent()方法,让ViewGroup的onTouchEvent()方法来处理事件;

⑤ 在ViewGroup的onTouchEvent()方法中如果返回true,表示消费事件,返回false,表示不处理事件,事件将会传递到Activity的onTouchEvent()方法处理;如果在ViewGroup的onInterceptTouchevent()方法中返回false,不拦截事件,那么事件就会传递到View中;

⑥ 事件到了View中,View就会调用自己的dispatchTouchEvent()方法,同样,如果开发者重写了dispatchTouchEvent()方法并返回了true,事件就会被消费;如果返回的false,那么事件就会直接返回给ViewGroup的onTouchEvent()方法,让ViewGroup的onTouchEvent()方法来处理;

⑦ 如果开发者在View中没有重写dispatchTouchEvent()方法,就会走super,系统会调用View的onTouchEvent()方法处理事件,如果在onTouchEvent()方法中返回了true,表示消费了事件,如果返回了false,那么事件就会返回到上一层,也就是ViewGroup的onTouchEvent()方法,让ViewGroup中的onTouchEvent()方法处理事件。

 

在上面的说明中,不仅说到了事件从Activity分发到View的过程,其实,事件在ViewGroup和View中的分发传递过程也已经说完了,下面这用两张更加直观的表示出了ViewGroup和View的事件分发过程:

Android中的事件分发机制_第2张图片

Android中的事件分发机制_第3张图片

对于这两张图就不做更多的说明了,在图中已经表示的非常清楚了。同时在对第一张图片进行说明的部分也已经涉及到了。

下面对于Android中DOWN事件的分发过程做一个简单的总结:

① 对于ViewGroup和View的disatchTouchEvent()和onTouchEvent()方法,return true表示处理事件,事件终结;return false表示不处理事件,让事件回传到上一层的onTouchEvent()方法中,也就是父控件中的onTouchEvent()方法中;

② 对于Activity的disatchTouchEvent()方法,如果没有重写,就会通过调用ViewGroup的disatchTouchEvent()方法开始分发事件,如果重写了,那么不管返回true还是false都会消费事件,不在将事件往下分发;

③ 对于dispatchTouchEvent()方法,如果开发者不重写,就会走系统中的默认实现,在ViewGroup中会调用ViewGroup的onInterceptTouchEvent()方法,而在View中会直接把事件分发给View的onTouchEvent()方法处理;

④ 对于ViewGroup而言,如果ViewGroup要自己处理事件,需要重写onInterceptTouchEvent()方法并且返回true,这样才会终止事件的传递,并调用ViewGroup的onTouchEvent()方法处理事件,否则调用系统onInterceptTouchEvent()方法,系统默认的返回值是false,不处理事件将事件传递下去;

⑤ 对于View而言,在View中是没有onInterceptTouchEvent()方法的,因为他没有孩子控件,不需要拦截,系统在dispatchTouchEvent()方法中默认会把事件分发给View的onTouchEvent()方法处理;

⑥ 在Android中,最开始获取到事件的是Activity,然后由Activity的dispatchTouchEvent()方法开始分发事件;

⑦ 如果一个事件由Activity开始下发,但是所有的控件都不处理事件,最终就会回到Activity的onTouchEvent()方法,如果Activity也不消费事件,那么这个事件就丢失了。

 

上面说到了DOWN事件的分发、传递以及处理过程,下面就的说一下MOVE和UP事件的传递过程:

在Android的事件中,最重要也是最复杂的一个事件就是DOWN事件,对于MOVE和UP事件,如果一个控件不能处理DOWN事件,那么在MOVW和UP的时候Android系统是不会将事件分发给这个控件的,也就是说,如果一个控件处理不了DOWN事件,那么他就接收不到了MOVE和UP事件了,更别说处理;相反,如果一个控件能处理DOWN事件,那么系统就会把MOVE和UP事件也交给他处理。下面这张表示了DOWN事件和MOVE、UP事件的一个传递过程:

Android中的事件分发机制_第4张图片

在上图中,事件由容器控件ViewGroup1传递下来,如果是DOWN事件,那么就会按照上面所说的过程一步一步传递,从容器控件ViewGroup1中传递到容器控件ViewGroup2在到View中,然后View也不消费事件,又回传到容器控件ViewGroup2中,容器控件ViewGroup2的onTouchEvent()方法消费了事件,DOWN事件终结;但是如果再次从容器控件ViewGroup1分发下拉了MONE或者UP事件时,容器控件ViewGroup2就会直接将事件交给自己的onTouchEvent()方法处理,而不会将事件在次向DWON事件一样传递给View了。

到这里,Android中的事件分发就基本上说完了。只是在这里还有2点要简单的进行说明一下:

① 在第一张图中有2个方法(dispatchTouchEvent()方法和onTouchEvent()方法)都可以消费事件,

如果一个DOWN事件是在某个控件的dispatchTouchEvent()方法中消费掉的,那么MOVE和UP事件传到这里就会终止,不会再往下传了;

如果一个DOWN事件是在某个控件的onTouchEvent()方法中消费的,那么MOVE和UP事件就依然会被传递到该控件的onTouchEvent()方法中处理。

② Android中的控件还有一个setOnTouchListener()方法

view.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {return false;}});

需要注意:

 

1、onTouch()方法优越于onTouchEvent()方法先执行;

2、如果onTouch()返回true,那么就不会执行onTouchEvent()方法。

 

下面就从源码的角度来了解一下Android下的事件分发机制

同时也回答上文中留下的一个问题:Activity在做事件分发时为什么会调用ViewGroup中的dispatchTouchEvent()方法?

首先看到Activity中的dispatchTouchEvent()方法源码:

 

public boolean dispatchTouchEvent(MotionEvent ev) {// 如果是ACTION_DOWN事件会走这个语句,onUserInteraction()这个方法在系统中是空实现if (ev.getAction() == MotionEvent.ACTION_DOWN) {onUserInteraction();}/** * 主要看一下这行代码 * getWindow()表示获取Window的子类PhoneWindow对象 * 也就是说调用PhoneWindow中的superDispatchTouchEvent(ev)方法,判断是否有控件处理事件 */if (getWindow().superDispatchTouchEvent(ev)) {return true;}// 如果没有控件能处理事件,就走这一行代码,调用Activity的onTouchEvent()方法处理事件return onTouchEvent(ev);}

接着进入到PhoneWindow中的,查看superDispatchTouchEvent(ev)这个方法:

@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {return mDecor.superDispatchTouchEvent(event);}

在PhoneWindow类的superDispatchTouchEvent(ev)方法中,直接调用了mDecor对象的superDispatchTouchEvent(ev)方法,mDecore其实就是继承至FrameLayout的DecorView的对象。在《Android自定义View之Activity页面的组成》这篇博客中贴出了DecorView类的定义源码。

 

接着查看类中的superDispatchTouchEvent(ev)这个方法:

 

public boolean superDispatchTouchEvent(MotionEvent event) {    return super.dispatchTouchEvent(event);}

只有一句代码,super.dispatchTouchEvent(event),调用父类的dispatchTouchEvent(event)方法,也就是FrameLayout的dispatchTouchEvent(event)方法,查看FrameLayout类会发现FrameLayout并没有重写dispatchTouchEvent(event)方法,那么就是使用的ViewGroup中的dispatchTouchEvent(event)方法。

 

到这里也就完全说明了Activity在做事件分发时调用的是ViewGroup中的dispatchTouchEvent()方法。

 

查看ViewGroup中的dispatchTouchEvent()方法:

 

@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {    ...boolean handled = false;if (onFilterTouchEventForSecurity(ev)) {.../** * 如果是DOWN事件就先将mFirstTouchTarget设置为null, * 然后在resetTouchState()方法中重置状态 */if (actionMasked == MotionEvent.ACTION_DOWN) {cancelAndClearTouchTargets(ev);resetTouchState();} // 定义变量intercepted标记ViewGroup是否拦截Touch事件的传递.final boolean intercepted;// 事件为ACTION_DOWN或者mFirstTouchTarget不为null(有控件消费touch事件)if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {//判断disallowIntercept(禁止拦截)标志位final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//当没有禁止拦截时if (!disallowIntercept) {// 调用onInterceptTouchEvent(ev)方法,并将返回值赋给interceptedintercepted = onInterceptTouchEvent(ev);ev.setAction(action);} else { //当禁止拦截时,指定intercepted = false,表示不拦截事件intercepted = false;}} else {//当事件不是ACTION_DOWN并且mFirstTouchTarget为null(没有控件消费touch事件)时//设置 intercepted = true,表示ViewGroup执行Touch事件拦截的操作。intercepted = true;}... // 事件分发final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;TouchTarget newTouchTarget = null;boolean alreadyDispatchedToNewTouchTarget = false;//不是ACTION_CANCEL事件并且intercepted为false(ViewGroup不拦截事件onInterceptTouchEvent()方法返回false)if (!canceled && !intercepted) {//处理ACTION_DOWN事件if (actionMasked == MotionEvent.ACTION_DOWN|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {final int actionIndex = ev.getActionIndex();                 final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex):TouchTarget.ALL_POINTER_IDS;                removePointersFromTouchTargets(idBitsToAssign);final int childrenCount = mChildrenCount;if (childrenCount != 0) {// 依据Touch坐标寻找孩子控件来消费Touch事件final View[] children = mChildren;final float x = ev.getX(actionIndex);final float y = ev.getY(actionIndex);final boolean customOrder = isChildrenDrawingOrderEnabled();// 遍历所有孩子控件,判断哪个消费Touch事件for (int i = childrenCount - 1; i >= 0; i--) {final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;final View child = children[childIndex];if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {continue;}newTouchTarget = getTouchTarget(child);if (newTouchTarget != null) {// 找到消费Touch事件的孩子控件,跳出循环,并用newTouchTarget表示孩子控件newTouchTarget.pointerIdBits |= idBitsToAssign;break;}resetCancelNextUpFlag(child);// 没有跳出循环,走到这一步,就会调用dispatchTransformedTouchEvent()方法,将事件传给孩子控件做递归处理,第三个参数不为nullif (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {   ...}}} /** * 如果在循环中没有孩子控件消费事件并且之前的mFirstTouchTarget不为空 */if (newTouchTarget == null && mFirstTouchTarget != null) {// 将mFirstTouchTarget的赋给newTouchTargetnewTouchTarget = mFirstTouchTarget;while (newTouchTarget.next != null) {newTouchTarget = newTouchTarget.next;}// newTouchTarget指向了最初的TouchTargetnewTouchTarget.pointerIdBits |= idBitsToAssign;}}} /** * 分发Touch事件至目标控件(target),以上过程主要针对ACTION_DOWN, * 如果不是比如ACTION_MOVE和ACTION_UP,就是从此处开始执行 */if (mFirstTouchTarget == null) {/** * mFirstTouchTarget为null表示Touch事件未被消费或Touch事件被拦截了, * 则调用ViewGroup的dispatchTransformedTouchEvent()方法,递归处理,第三个参数为null */handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);} else {/** * mFirstTouchTarget不为null表示找到了可以消费Touch事件的子View * 并且MOVE或UP事件可以传递到该子View */TouchTarget predecessor = null;// 将找到的可以消费事件的mFirstTouchTarget赋给目标控件(target)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;// 否则调用dispatchTransformedTouchEvent()方法进行递归处理,第三个参数不为nullif (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {handled = true;}   ...}predecessor = target;target = next;}}/** * 如果是ACTION_UP和ACTION_CANCEL事件,还原状态 */if (canceled|| actionMasked == MotionEvent.ACTION_UP|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {resetTouchState();} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {...}}...return handled;}

我们可以看到在上面的方法中,调用的onInterceptTouchEvent()判断是否需要拦截事件。

查看ViewGroup中的dispatchTransformedTouchEvent()方法:

 

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,            View child, int desiredPointerIdBits) {final boolean handled;...if (child == null) {// 如果孩子控件为空,就调用View的dispatchTouchEvent()方法,在View的dispatchTouchEvent()方法中会调用onTouchEvent()方法handled = super.dispatchTouchEvent(event);} else {// 如果孩子控件不为空,就调用孩子控件的dispatchTouchEvent()方法// 在此处孩子控件也还有可能是ViewGroup,所以就是继续调用ViewGroup的dispatchTouchEvent()方法handled = child.dispatchTouchEvent(event);}event.setAction(oldAction);return handled;}   ...transformedEvent.recycle();return handled;}

查看ViewGroup中onInterceptTouchEvent()方法:

 

 

public boolean onInterceptTouchEvent(MotionEvent ev) {return false;}

直接返回false,表示不拦截事件。

 

在ViewGroup中,没有重写onTouchEvent()方法,所以调用的是View中的onTouchEvent()方法。

 

在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);}// 如果是DOWN事件,重置状态final int actionMasked = event.getActionMasked();if (actionMasked == MotionEvent.ACTION_DOWN) {stopNestedScroll();}// 过滤触摸安全策略,如果是false,直接跳出触摸事件if (onFilterTouchEventForSecurity(event)) {ListenerInfo li = mListenerInfo;if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) {// 当前控件是可用(enabled)的并且View调用了setOnTouchListener()方法且返回了true,那么就设置result为trueresult = true;}// result为false,表示没有调用setOnTouchListener()方法或该方法返回false,那么就调用// View的onTouchEvent()方法if (!result && onTouchEvent(event)) {result = true;}}if (!result && mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);}// 如果是UP事件或CANCEL事件或者是DOWN事件但是该控件不能消费事件时,重置状态if (actionMasked == MotionEvent.ACTION_UP ||actionMasked == MotionEvent.ACTION_CANCEL ||(actionMasked == MotionEvent.ACTION_DOWN && !result)) {stopNestedScroll();}return result;}

最后查看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;        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;        }        // 如果有代理,调用代理的方法        if (mTouchDelegate != null) {            if (mTouchDelegate.onTouchEvent(event)) {                return true;            }        }        // 对点击事件的具体处理,只要有点击事件,那么onTouchEvent()方法就返回了 true        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {            switch (action) {                case MotionEvent.ACTION_UP:                    // ...                        // mHasPerformedLongPress 表示长按事件的返回值,如果长按事件的的回调方法返回了true,那么 点击事件就不会调用了                        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();                                 }                                if (!post(mPerformClick)) {                                    performClickInternal(); // 会调用 performClick()方法处理单击事件                                }                            }                        }                        if (mUnsetPressedState == null) {                            mUnsetPressedState = new UnsetPressedState();                        }                        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;                    }                    if (performButtonActionOnTouchDown(event)) {                        break;                    }                    // Walk up the hierarchy to determine if we're inside a scrolling container.                    boolean isInScrollingContainer = isInScrollingContainer();                    // 根据是否在滚动容器中使用不同方式调用长按事件的回调                    if (isInScrollingContainer) {                        mPrivateFlags |= PFLAG_PREPRESSED;                        if (mPendingCheckForTap == null) {                            mPendingCheckForTap = new CheckForTap(); // 最终调用长按事件回调                        }                        mPendingCheckForTap.x = event.getX();                        mPendingCheckForTap.y = event.getY();                        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:                    // ...                    break;            }            // 只要有点击事件,那么onTouchEvent()方法就返回了 true            return true;        }        return false;    }

从上面的代码来看,只要View的 CLICLABLE和LONG_CLICKABLE有一个为true,那么onTouchEvent()方法就会返回true,而且不管该View是否为DISABLE状态,其他情况返回false。

true表示该控件可以消费事件,false表示该控件不能消费事件。现在,在回过头来看一下第一张图,是不是更加的清晰了呢。

 

最后,对于View 的  CLICKABLE 和 LONG_CLICKABLE默认值,LONG_CLICKABLE默认值为false,但是对于CLICKABLE就要根据具体的View来看了,确切的说是可点击的View的CLICKABLE值为true,如Button,不可点击的View的CLICKABLE值为false,不如TextView,但是当我们调用了View的 setOnClickListener(@Nullable OnClickListener l) 方法或者 setOnLongClickListener(@Nullable OnLongClickListener l) 方法就会将对应的值改为true。

public void setOnClickListener(@Nullable OnClickListener l) {        if (!isClickable()) {            setClickable(true);        }        getListenerInfo().mOnClickListener = l;    }public void setOnLongClickListener(@Nullable OnLongClickListener l) {        if (!isLongClickable()) {            setLongClickable(true);        }        getListenerInfo().mOnLongClickListener = l;    }

 

 

更多相关文章

  1. Android 事件分发
  2. Chronometer android计时器组件Chronometer的使用,android通话时
  3. [Android Studio系列(五)] Android Studio手动配置Gradle的方法
  4. android点击事件,第一次无效,第二次才响应的问题
  5. 【Android 界面效果29】研究一下Android滑屏的功能的原理,及scrol
  6. Android界面编程——Android基本控件
  7. Android中View的绘制过程 onMeasure方法简述 附有自定义View例子
  8. Android开发学习之基本控件概览

随机推荐

  1. 当尝试安装节点时,会得到一个“DLL”错误
  2. 如何通过ajax将javascript数组传递给YII
  3. 什么库或模式减少了这个cb到1 cb的样板?
  4. 使用yui将新项添加到组合框中
  5. js获取毫秒值以及转换成年月日时分秒等
  6. 使用“使用中值排序基数法”实现树状结构
  7. JQuery 1.4.4和Datatable 1.7不工作
  8. CSS3转换导致非常奇怪的window.scrollY行
  9. 奇怪的字符显示在XML> ASP.NET> Javascri
  10. S1-Javascript基础知识一[Javascript]