从源码层分析和学习事件传递机制

前言

学习Android,事件传递机制是必须要了解的,之前在项目中遇到一些诸如ScrollView嵌套ListView,RecycleView等,就需要用到这一块的知识,市面上有很多书籍,资料等都讲了这一块的知识,可是只按照别人那里得来的通用性结论死记硬背,感觉也不能深刻的理解,所以我觉得这一块最好的理解方式还是看源码,所以今天决定按照源码的角度来学习和分析事件传递机制。

事件传递机制

首先我们得了解,什么是事件传递机制,举个简单的例子,一个简单的activity,外层是LinearLayout布局,里面有个按钮,点击可以触发相应的事件,那么事件是直接通过按钮的监听来执行的么,显然当然不是,这里的事件是从activity到LinearLayout再到Button一层一层传递进来的,而且上层有权利决定是否分发、是否拦截和消费此事件。我们也都知道,事件传递有三个阶段:分发,拦截和消费,下面分别介绍这三个阶段:

事件分发

事件分发对应着dispatchTouchEvent方法,在Android系统中,所有的触摸事件都是通过这个方法来分发的,Activity,View和ViewGroup都有这个方法,首先可以去看Activity对应的这个方法,这个代码很短,我就直接拷进来了:

activity内的dispatchTouchEvent源码

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

可以看到,Activity的分发方法中,先判断事件是否为按下事件,如果是,则会执行onUserInteraction方法,可是点进去发现这个方法是个空方法,也就是子类可以重写这个方法决定当按键为按下时activity的操作内容,这个就先不看了,注意下面这行,看名字可以确定大概就是判断子层viewgroup的dispatchOntouchEvent是否返回的是super方法,如果是,这边返回true,也就是事件继续向下分发。可是这里有一点会有点绕,那也就是如果activity的子类覆盖这个方法,不super而是直接返回true会怎么样呢?大家可以试一下,结果应该是事件不会继续向下分发的,其实这里你只要不super,不论返回true或者false都是不会继续向下分发的。因为参见这里的代码就可以联想到,这里判断子类是否super,其它地方也会判断Activity的这个方法是否super,我们虽然只看Activity、View和ViewGroup,但是Android系统这么大不可能说这些内容不跟其它内容耦合,我们只要把握一个关键点就好了。

ViewGroup的事件分发

先看dispatchOnTouchEvent这个方法,这个源码太长,就不粘上来了,我们从上往下阅读,抓住几个重点(关键字),可以看到,一开始声明了一个boolean类型的handler值

boolean handled = false;

这个就是方法最终的返回值,后面要抓住这个点,继续往下阅读,又遇到一个关键字:

// 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;
            }

可以看到intercepted 关键字,很明显就和另一个方法onInterceptTouchEvent有关,果然,往下几行就发现了这个方法,注意上面的判断: if (!disallowIntercept),其实英文直译就可以知道,不允许拦截,加上!就是允许拦截时候会进入这个方法,
所以我们可以假设,当手机当前无其他操作处于允许拦截时,会调用这个onInterceptTouchEvent方法,给intercepted 赋值onInterceptTouchEvent方法的返回值。

猜测

那么我们就有了一个猜测,既然有了这个字段,他的返回值肯定会在某种情况下影响dispatchTouchEvent的返回值,继续往下看,找到这样一行

  if (!canceled && !intercepted) {

                                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;
                            ......

这里是一个大的判断,也就是当onInterceptTouchEvent的返回值是false且不取消该次点击的时候,也就是没有拦截子view事件的时候,才会进入这个判断

final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }

可以看到,这里有个cancelChild的字段是由intercepted决定的,然后下面一行把这个布尔值传进dispatchTransformedTouchEvent方法内部,判定这个方法的返回值为true时,handler=true,看见了么,onInterceptTouchEvent就是通过这种方式织入dispatchTouchEvent方法中,决定返回值的。那么这个dispatchTouchEvent方法中究竟做了什么呢,我们进去看一下:

dispatchTouchEvent内部代码

这里同样是在开始声明了一个布尔类型的handler值

 final boolean handled;

再看这个handler是怎么赋值的

final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                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);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }

        // Perform any necessary transformations and dispatch.
        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }

            handled = child.dispatchTouchEvent(transformedEvent);
        }

可以看到,在某种情况下当子view的child为空时,方法的返回值为ViewGroup的dispatchTouchEvent方法的返回值,而child不为空时,返回值就取决于child View的dispatchTouchEvent方法的返回值。当然这里很容易想的通,如果没有内层的view,dispatchTouchEvent还是交给自己处理,而如果有子view,就传递给子view处理。那么在返回到刚刚哪里,方法返回true时,也就是当事件被消费时,这里也返回true,表示被事件被消费掉。

结论

那么对ViewGroup,我们可以的出一个结论,上层的Activity的dispatchTouchEvent传入到ViewGroup,ViewGroup在事件可以被拦截的情况下会调用onInterceptTouchEvent方法,其返回值作为一个参数间接的影响到了dispatchTouchEvent方法,从而根据这个返回值决定dispatchTransformedTouchEvent方法内部的事件流,也就是当onInterceptTouchEvent返回true时,会进入这种逻辑,有子层的view则调用子层view的dispatch,无则返回自己的dispatch。、

OnTouchEvent

相信看到这里你一定很奇怪,为什么没有OnTouchEvent方法,其实我也很奇怪,我这里看到是7.0的源码,在souceinsight里搜索也搜索不到这个方法,之前看书上说是有这个方法的。那么它到哪去了?
其实也没看过别的版本的源码,所以不知道以前这里会不会实现OnTouchEvent方法,因为这里ViewGroup是View的子类,所以子类没有重写这个方法也正常,所以这里对ViewGroup的事件分发的探索也就到此为止了。那么关于onTouchEvent方法实际上我看的这个版本并没有重写该方法,那么,这个就放在下次的View的传递机制去探索啦

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值