android 事件分发机制(看完即懂)

android事件分发机制很多人都写,但我看的感觉不是很明白。讲解的时候经常略过一大段,有的还是用的老版代码,弄的我似懂非懂的。不如我自己研究下源码,完整的把流程捋一遍。算是巩固一下吧!

Android事件分发机制,有点基础的人都知道。事件的分发是由ActivityViewGroupView传递的。PhoneWindowDecorView只是起到中转的作用。下面我们就来分析这一流程是怎么样走的.

一、ViewGroup拦截事件的执行流程

首先,当触摸屏幕事件发生时,是底层传感器最先接收到的。然后传感器会调用ActivitydispatchTouchEvent()开始进行分发。所以也可

以说,Activity是最先接收到事件的。打开Activity查找dispatchTouchEvent()

public boolean dispatchTouchEvent(MotionEvent ev){    if(ev.getAction()==MotionEvent.ACTION_DOWN){        onUserInteraction();    }    if(getWindow().superDispatchTouchEvent(ev)){// 关键语句        returntrue;    }    return onTouchEvent(ev);}

看关键代码,getWindow().superDispatchTouchEvent(ev),我们都知道,Window是个抽象类,其具体实现是PhoneWindow。再来查

PhoneWindowsuperDispatchTouchEvent()

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

而PhoneWindow又调用了内部类DecorView的superDispatchTouchEvent方法。

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

DecorView又调用super父类,DecorView是继承自FrameLayout,但是FrameLayout并没有实现这个方法。其实最终是在ViewGroup里

实现的。 我们来看看ViewGroup里的dispatchTouchEvent()方法,这个方法非常长,我们看关键的部分:

@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {        ...        intercepted = onInterceptTouchEvent(ev); // 接收是否被拦截,这里假设被拦截了,也就是intercepted为true。        if (!canceled && !intercepted) { // 如果拦截了这里是不会执行的            ...        }        // 第一次执行时,这里肯定是为null的,所以这里肯定是执行的。那么来看看dispatchTransformedTouchEvent方法。        if (mFirstTouchTarget == null) {            handled = dispatchTransformedTouchEvent(ev, canceled, null,                    TouchTarget.ALL_POINTER_IDS);        } else {            ...        }       ...    return handled;}

这里注意,第三个参数传的是null,也就是没有子View。

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,        View child, int desiredPointerIdBits) {    ...     if (child == null) { // 没有子View那么就走的这里            handled = super.dispatchTouchEvent(event);     } else {            final float offsetX = mScrollX - child.mLeft;            final float offsetY = mScrollY - child.mTop;            event.offsetLocation(offsetX, offsetY);            handled = child.dispatchTouchEvent(event);            event.offsetLocation(-offsetX, -offsetY);     }        return handled;    ...    return handled;}

ViewGroupsuper.dispatchTouchEvent 。注意,ViewGroup的父类是View。所以就调到了View里去了。

public boolean dispatchTouchEvent(MotionEvent event) { // View的dispatchTouchEvent方法    ...    if (onFilterTouchEventForSecurity(event)) {        ListenerInfo li = mListenerInfo;        if (li != null && li.mOnTouchListener != null                           && (mViewFlags & ENABLED_MASK) == ENABLED                && li.mOnTouchListener.onTouch(this, event)) { // 这里的判断说明,我们调用了setOnTouchListener。            result = true;  // 然后将这个标志置为true        }        if (!result && onTouchEvent(event)) { // 短路与操作符,第一个判断为假,第二个onTouchEvent就不会执行了            result = true;        }    }    ...    return result;}

从这里也可以知道,为什么我们给自定义View设置OnTouchListener后就不会再调用onTouchEvent方法了。因为短路与的关系

onTouchEvent()调用不到。以上是ViewGroup里的onInterceptTouchEventtrue,也就是被拦截了。然后,如果ViewGroup设置了onTouchEvent,那么就交给

ViewGrouponTouchEvent处理。但由于ViewGroup没有实现onTouchEvent,它最终会调用父类ViewonTouchEvent处理。LinearLayoutRelativeLayoutonTouchEvent都是调的ViewonTouchEvent而如果返回false,那么就按照方法的传递顺序反向传递。最终传到:

public boolean dispatchTouchEvent(MotionEvent ev) {    if (ev.getAction() == MotionEvent.ACTION_DOWN) {        onUserInteraction();    }    if (getWindow().superDispatchTouchEvent(ev)) { // 关键语句        return true;    }    return onTouchEvent(ev);}

是的,最终传到这里。getWindow().superDispatchTouchEvent(ev)false,那么调用ActivityonTouchEvent()

二、ViewGroup不拦截,正常的执行流程

(1)View的onTouchEvent()返回true

现在来看ViewGroup里的onInterceptTouchEventfalse,也就是没有被拦截。 (以下是重复上面的代码,只不过走的逻辑不一样)

public boolean dispatchTouchEvent(MotionEvent ev) {    ...        if (!canceled && !intercepted) {  // 因为没有拦截,也没有取消。所以走这块逻辑            ...                    final View[] children = mChildren;                    for (int i = childrenCount - 1; i >= 0; i--) { // 这里是遍历所有的子View                        final int childIndex = customOrder                                ? getChildDrawingOrder(childrenCount, i) : i;                        final View child = (preorderedList == null)                                ? children[childIndex] : preorderedList.get(childIndex);                        if (childWithAccessibilityFocus != null) {                            if (childWithAccessibilityFocus != child) {                                continue;                            }                            childWithAccessibilityFocus = null;                            i = childrenCount - 1;                        }                        if (!canViewReceivePointerEvents(child) // 这里是判断子View是否处在触摸边界范围内                                || !isTransformedTouchPointInView(x, y, child, null)) {                            ev.setTargetAccessibilityFocus(false);                            continue;                        }                        // 这里是关键的一步,第一次流程时,调用这个方法传的第三个参数传的是null,而这里                        // 因为遍历,所以每次传的都是有值的.                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {                             ...                        }                        ...                }        ...    return handled;}

找到ViewGroupdispatchTransformedTouchEvent这个方法

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,        View child, int desiredPointerIdBits) {    ...     if (child == null) {                handled = super.dispatchTouchEvent(event); // 不走这里            } else {                final float offsetX = mScrollX - child.mLeft;                final float offsetY = mScrollY - child.mTop;                event.offsetLocation(offsetX, offsetY);                handled = child.dispatchTouchEvent(event); // 有子View,所以这次走的是这里                event.offsetLocation(-offsetX, -offsetY);            }            return handled;    ...    return handled;}

可以看到,这次走的是child.dispatchTouchEvent(event).顺便一提,offsetXoffsetY是相对于手机左上角原点的偏移量,也就是相对于绝对坐标的偏移量。 event.offsetLocation(offsetX, offsetY),将这两个偏移量传进去。子View获取event.getX()getY()时才会获取的是相对于ViewGroup的值。

event.offsetLocation(-offsetX, -offsetY),这么做取反再设置会原来的样子。是因为后面还会有使用到 的,责任链模式传递中间不能修改传递的东西。

关于偏移量计算不懂的看下面这张图:

好了,言归正传。现在走的是child.dispatchTouchEvent(event)。我们来看看ViewdispatchTouchEvent()

public boolean dispatchTouchEvent(MotionEvent event) {    ...    boolean result = false;    ...    if (onFilterTouchEventForSecurity(event)) {        ...        if (!result && onTouchEvent(event)) { // 第一个判断肯定能过去,第二个判断就是调用我们自定义的onTouchEvent方法            result = true; // 而如果我们自定义View的onTouchEvent返回true,那么result就为true了。        }    }    ...    return result; // 然后就直接把result返回。}

现在,我们自定义ViewonTouchEvent()返回true,那么ViewdispatchTouchEvent也返回trueViewGroup里的dispatchTransformedTouchEvent也会返回true。返回到ViewGroupdispatchTouchEvent()方法里。

public boolean dispatchTouchEvent(MotionEvent ev) {    ...    if (!canceled && !intercepted) {        ...        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // 进到这个方法里            ...            newTouchTarget = addTouchTarget(child, idBitsToAssign); // 这里是关键的地方            ...            break;        }        ...    }    ...    return handled;}

这个addTouchTarget()非常重要。当我们自定义ViewonTouchEvent()返回true时,就代表所有触摸事件由这个自定义View来处理。

那么后续的触摸事件,系统是不会在经过一大堆遍历和判断,而是直接将事件传给自定义View。原因就是addTouchTarget()

private TouchTarget addTouchTarget(View child, int pointerIdBits) {    TouchTarget target = TouchTarget.obtain(child, pointerIdBits);    target.next = mFirstTouchTarget;    mFirstTouchTarget = target;    return target;}

它是个链表,android中应用责任链模式的地方非常多。注意,只有调了这个addTouchTarget()方法后mFirstTouchTarget才被赋值,

只有这一个地方。addTouchTarget()添加完子View之后,就直接break了,所以其它子View是没有机会接触到触摸事件的。再回到ViewGroupdispatchTouchEvent()

@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {    ...    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {        ...        newTouchTarget = addTouchTarget(child, idBitsToAssign);        ...        break;    }    ...    if (mFirstTouchTarget == null) { // 我们看到mFirstTouchTarget刚刚已经赋值了,所以走的是else语句块        ...    } else {        TouchTarget predecessor = null;        TouchTarget target = mFirstTouchTarget; // 这是头节点        while (target != null) {            final TouchTarget next = target.next; // 不断的while循环,找下一个节点            if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {                handled = true;            } else {                final boolean cancelChild = resetCancelNextUpFlag(target.child)                        || intercepted;                if (dispatchTransformedTouchEvent(ev, cancelChild,                        target.child, target.pointerIdBits)) { // 当MOVE事件不断发生时,这里也是不断的调用                    handled = true;                }                if (cancelChild) {                    if (predecessor == null) {                        mFirstTouchTarget = next;                    } else {                        predecessor.next = next;                    }                    target.recycle();                    target = next;                    continue;                }            }            predecessor = target;            target = next;        }    }    ...    return handled;}

好,关于这个ViewGroupdispatchTouchEvent(),我们再来梳理一下关于DOWN事件和MOVE事件的调用:

@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {    ...    if (!canceled && !intercepted) { // 如果没有被取消也没有被拦截        ...        if (actionMasked == MotionEvent.ACTION_DOWN // 从这里的判断看出,如果是DOWN事件就走这个语句块                || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {            ...        }    }    if (mFirstTouchTarget == null) {        ...    } else { // 那么MOVE事件只有从这里走了        ...    }    ...    return handled;}

现在我们可以知道,如果是DOWN事件,就会按部就班的一步一步dispatchinterceptontouchevent这样来。 如果是MOVE事件,就会直接到自定义ViewonTouchEvent()。因为addTrouchTarget()了,mFirstTouchTarget就指向被点击的View了。

(2)View的onTouchEvent()返回false

现在是第二种情况,ViewonTouchEvent()返回false,表示子View不处理了。 回到ViewGroupdispatchTransformedTouchEvent调用child.dispatchTouchEvent(event),也就是ViewdispatchTouchEvent()的时候。

public boolean dispatchTouchEvent(MotionEvent event) {    ...    boolean result = false;    ...    if (onFilterTouchEventForSecurity(event)) {        ...        if (!result && onTouchEvent(event)) { // 第一种情况判断的是自定义View的onTouchEvent()返回true,代表处理。而现在返回false,代表不处理。            result = true;        }    }    ...    return result; // 然后就直接把result返回。返回false.}

ViewdispatchTouchEvent返回false。回到ViewGroupdispatchTouchEvent()里。

@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {    ...    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // 返回false代表这里不走        ...    }    ...    if (mFirstTouchTarget == null) { // 就会走到这里            handled = dispatchTransformedTouchEvent(ev, canceled, null,                    TouchTarget.ALL_POINTER_IDS);    } else {        ...    }    ...}

而走到if (mFirstTouchTarget == null)这个语句块就意味着,轮到ViewGroup来判断了,然后会一直返回到Activity里去。调用ActivityonTouchEvent()方法。

以上是在Android 5.x版本源码分析,如果没有源码可以从这个网址保存或下载,里面从2.x - 7.x版本的源码都有:

Android源码下载

无密码,注意压缩格式是7z格式,如果用普通解压工具解压出错。安装这个7-Zip试试!

解压源码非常耗时间,因为里面大概有40多万个文件,大概需要1个小时的时间。

如果嫌下载和解压太麻烦,可以到打开这个网址。在线android 5.x源码查看网站:

Android源码在线浏览网站

总结

事件分发从ActivityViewGroup再到View。这一流程,如果一开始Activity就拦截就没什么好说的了。ViewGroup拦截后对于是否处理做了源码分析。处理就处理,不处理就一直返回falseActivitydispatchTouchEvent()里去,最终调用ActivityonTouchEvent()方法。View也是类似的,处理就处理,不处理最终也是调用ActivityonTouchEvent()方法。


更多相关文章

  1. Android分享功能
  2. cocos2dx在Android下如何接入91SDK
  3. Android横竖屏切换总结(Android资料)
  4. Android(安卓)通过WebView和js的交互
  5. Android调用内置的无线信息隐藏菜单[转]【待验证】
  6. Android与JS的交互简单实现
  7. android之调用webservice 实现图片上传
  8. android应用程序基本原理
  9. Android中通过WebView控件实现与JavaScript方法相互调用的地图应

随机推荐

  1. Android Xml文件生成,Xml数据格式写入
  2. Android官方DataBinding(三):RecyclerView
  3. Android常见错误处理
  4. Android Studio 导入 GreenDao
  5. 使用PHP开发Android应用程序技术介绍
  6. Android:Designing for Performance
  7. Android(一)Android Eclipse环境搭建
  8. Android_RelativeLayout属性大全
  9. Error:(1, 0) The android gradle plugin
  10. 使用AudioTrack播放PCM音频数据(android)