开头说说初衷

网上关于点击事件分发的文章一搜一大堆,标题一看,不是“30分钟让你弄明白XXX"就是“这是讲解XXX最好的文章”,满怀憧憬与信心,忍不住兴奋的点进去一看,发现不是代码就全是图,我基本上看完了所有相关的文章,结果硬是看了三个小时也没搞懂。所以最后还是决定自己去试一试,看一看点击事件分发到底是怎么个流程,我写的肯定不会比其他文章好多少,但是呢,带着一个初学者的心,去分析这个东西,自己能弄明白的同时,也让想学习这个的人看了之后有些许收获,那就足够了。

运行的环境

所有的源码都基于API 26,也就是Android8.0奥利奥,Android Studio 3.0.1,想要自己敲代码试试的同学可以参考一下

进入正题

分析点击事件分发流程,是想弄明白当我们用手指去点击屏幕的时候,分为三个动作,按下,移动和抬起,屏幕上的东西是怎么知道我们点了它的,在这中间到底经历了什么。所以要先来模拟一下这个点击的过程,看看到底调用了哪些方法。

搭建最简单的结构

  1. 新建Activity,重写dispatchTouchEventonTouchEvent,前面的方法负责点击事件的分发,后面的方法负责点击事件的消耗,然后打印三种触摸事件的触发
private static final String TAG = MainActivity.class.getSimpleName();
@Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG, "Activity onTouchEvent ACTION_DOWN");//按下
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG, "Activity onTouchEvent ACTION_MOVE");//移动
                break;
            case MotionEvent.ACTION_UP:
                Log.d(TAG, "Activity onTouchEvent ACTION_UP");//抬起
                break;
        }
        return super.onTouchEvent(event);
    }
@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG, "Activity dispatchTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG, "Activity dispatchTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.d(TAG, "Activity dispatchTouchEvent ACTION_UP");
                break;
        }
        return super.dispatchTouchEvent(ev);
    }
  1. 新建一个类继承自LinearLayout,同样也重写dispatchTouchEventonTouchEvent,还有因为LinearLayout继承自ViewGroup,ViewGroup是可以拦截点击事件的,这个很好理解,因为控件都是放在他里面的嘛。所以还要重写onInterceptTouchEvent方法
@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG, "onInterceptTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG, "onInterceptTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.d(TAG, "onInterceptTouchEvent ACTION_UP");
                break;
            default:
                break;
        }
        return false;
    }
3(这里有个重点).

这里有个需要注意的地方就是Android的控件有些是默认可以点击的(如Button),还有一些是默认不可点击的(如TextView)他们的分发是有一些不同的,这里我们先看不可点击的,新建一个类继承android.support.v7.widget.AppCompatTextView,兼容的TextView,同时跟Activity一样重写dispatchTouchEventonTouchEvent,代码不贴了,跟上面一样,它是普通控件,没有拦截的方法。

开始点击,移动手指后抬起

然后来点一点屏幕上的控件看看打印的log,dispatchTouchEvent和onTouchEvent都返回默认的实现super,onInterceptTouchEvent默认返回false,表示不拦截,默认的情况打印:

1.--------------------------------------
D/MainActivity: Activity dispatchTouchEvent ACTION_DOWN
D/MyLayout: dispatchTouchEvent ACTION_DOWN
D/MyLayout: onInterceptTouchEvent ACTION_DOWN
D/MyTextView: dispatchTouchEvent ACTION_DOWN
2.--------------------------------------
D/MyTextView: onTouchEvent ACTION_DOWN
D/MyLayout: onTouchEvent ACTION_DOWN
D/MainActivity: Activity onTouchEvent ACTION_DOWN
3.--------------------------------------
D/MainActivity: Activity dispatchTouchEvent ACTION_MOVE
D/MainActivity: Activity onTouchEvent ACTION_MOVE
D/MainActivity: Activity dispatchTouchEvent ACTION_MOVE
D/MainActivity: Activity onTouchEvent ACTION_MOVE
D/MainActivity: Activity dispatchTouchEvent ACTION_UP
D/MainActivity: Activity onTouchEvent ACTION_UP

这是默认的情况,把它分为三个阶段:

  1. 事件的分发,从上到下,从Activity到Layout到Text的dispatchTouchEvent结束
  2. 事件的消耗,从下到上,从Text到Layout到Activity的onTouchEvent结束
  3. 这套动作的后续事件交给上个事件的最后消耗者,不经过其他控件的分发
三个函数的其他返回值

dispatchTouchEvent和onTouchEvent都有三种返回情况

  • true
  • false
  • super.dispatchTouchEvent(ev)和super.onTouchEvent(event)

onInterceptTouchEvent有两种返回,true和false,这样来看,
组合起来真是情况太多了,写下来挨个分析看代码的话怕是要看晕,所以这里用一张图来看看所有的情况:

androidapp点击事件按压效果去掉 android点击事件处理_ide

默认可点击控件的事件分发

比如Button这种默认可以点击的控件,或者设置android:clickable="true"的控件,在分发流程中有一些不同,主要是onTouchEvent的默认方法不同,它直接消耗点击事件,不再往上传递。

androidapp点击事件按压效果去掉 android点击事件处理_点击事件_02

结语

事件的分发流程到此就结束了,目的已经达到了,找到了我们想要点击的那个按钮或者其他控件,总结下来就是从Activity经过ViewGroup然后到View依次分发,然后又从底向上确认自己是否消耗该事件,如果某个对象消耗了,动作的后续事件都由他来处理。