这篇事件分发机制里的东西你都懂的话,我赔钱!

1.前言

事件分发这个东西嘛,大家一直都在讲,但总有人觉得吃不透。为什么呢?因为事件分发是多维的,有好多条思维分岔路口,而文章基本上只能用一维的方式从左到右,从上到下进行表达,所以基本不可能让普通智力的人从入门到精通。我们所要做的,就是踏踏实实打开源码,自己多琢磨,多整理。才能彻底理解这些多维的知识点。
下面内容请配合源码食用!不然基本上索然无味!

2.Touch与Click的前生今世

首先,我们先来做点前戏,搞清楚setOnTouchListenersetOnClickListener以及onTouchEvent之间的关系。

2.1 setOnTouchListener

因为这一系列操作都是针对View的,所以我们直接看其源码,精准定位到dispatchTouchEvent()方法。

 public boolean dispatchTouchEvent(MotionEvent event) {
        ...
        boolean result = false;
        ...
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        ...
        return result;
    }

这段代码非常简单,直接将一切都暴露了出来。

result变量十分关键,它是用来控制Touch与Click执行流程的。最开始result为false,如果我们通过setOnTouchListener()为某个View设置了touch监听,并且在监听的onTouch()方法中返回true,那么result变量就会被赋值为true,此时dispatchTouchEvent()执行完毕,就不会执行接下来View本身的onTouchEvent()方法。

2.2 onTouchEvent

相反,如果我们没有为View设置touch监听,或者设置了touch监听但是在监听的onTouch()方法中返回false,那么result依旧为false,就会执行View本身的onTouchEvent()方法。我们来看看onTouchEvent()做了什么,由于只是热身运动,所以只贴出了与其有关的部分代码。

public boolean onTouchEvent(MotionEvent event) {
      ...
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                   ...
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }
      ...
    }

可以看到,在View本身的onTouchEvent()方法中,先去判断了该View是否可以被点击,接着判断触摸事件的类型,如果是ACTION_UP类型,则执行performClick()方法。

2.3 setOnClickListener

performClick()这个方法比较短,直接展示出来。

public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

显而易见,如果通过setOnClickListener为当前View设置了Click监听,此时就会去执行监听中onClick()方法。

2.4 小结

到此为止,前戏就算结束了。我们总结下,setOnTouchListenersetOnClickListener是程序员可以设置的,而onTouchEvent是View本身的方法,在onTouchEvent中会去执行setOnClickListener中设置的OnClick方法。而在View的dispatchTouchEvent()中,首先会去判断是否设置了OnTouchListener并且其OnTouch方法返回为true,如果是,则不会执行View本身的onTouchEvent方法,如果不是,则会执行onTouchEvent进而执行OnClick方法。

我个人是这样记住他们的关系的:Touch是触摸,Click是点击,从逻辑上来说,触摸包含了点击。所以如果设置了触摸的监听,那么其必定包含点击,于是点击的监听也就没什么必要了。

3.事件分发

3.1 事件分发的开始

下面进入正题,在使用安卓手机时,我们用手指触摸了屏幕,物理设备就会一层层将触摸事件传递出来,这是底层的活儿,我们暂且不去了解。属于Android开发的故事,从Activity的dispatchTouchEvent()方法开始。请注意,这是Activity的dispatchTouchEvent(),不要和View的dispatchTouchEvent()混淆起来。

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

分析一波,首先判断触摸事件,如果是ACTION_DOWN,则调用onUserInteraction(),这是一个空方法,专门用来让用户重写的,可以用于在事件发生前做一些操作。
接着getWindow().superDispatchTouchEvent(ev)就比较重要了。一路跟来的同学肯定知道Activity中的Window就是PhoneWindw,不知道的传送门在这里。我们直接看其superDispatchTouchEvent方法。

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

显而易见,这里调用了DecorView中的superDispatchTouchEvent方法

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

DecorView是PhoneWindow的内部类,我们去看看他的父类是谁。

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker

可见,FrameLayout 是DecorView的父类,所以就会调用FrameLayout的dispatchTouchEvent方法,遗憾的是,FrameLayout并没有这个方法,所以还要去找FrameLayout 的父类ViewGroup。ViewGroup中的dispatchTouchEvent是本篇最大的高潮,我们下一节专门来讲。在此,我们回到Activity的dispatchTouchEvent方法中

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

看最后一行代码——年轻的程序员啊,请记住,只要外层ViewGroup的dispatchTouchEvent返回为true,那么就代表事件被消耗了,此时连Activity中的onTouchEvent

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值