android 事件分发机制(源码解析)

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()

@Override
public 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()方法,这个方法非常长,我们看关键的部分:

@Override
public 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()

@Override
public 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事件的调用:

@Override
public 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()里。

@Override
public 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()方法。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值