解析Android点击事件分发机制
作者:Silly_Monkey
开头说说初衷
网上关于点击事件分发的文章一搜一大堆,标题一看,不是“30分钟让你弄明白XXX”就是“这是讲解XXX最好的文章”,满怀憧憬与信心,忍不住兴奋的点进去一看,发现不是代码就全是图,我基本上看完了所有相关的文章,结果硬是看了三个小时也没搞懂。所以最后还是决定自己去试一试,看一看点击事件分发到底是怎么个流程,我写的肯定不会比其他文章好多少,但是呢,带着一个初学者的心,去分析这个东西,自己能弄明白的同时,也让想学习这个的人看了之后有些许收获,那就足够了。
运行的环境
所有的源码都基于API 26,也就是Android8.0奥利奥,Android Studio 3.0.1,想要自己敲代码试试的同学可以参考一下
进入正题
分析点击事件分发流程,是想弄明白当我们用手指去点击屏幕的时候,分为三个动作,按下,移动和抬起,屏幕上的东西是怎么知道我们点了它的,在这中间到底经历了什么。所以要先来模拟一下这个点击的过程,看看到底调用了哪些方法。
搭建最简单的结构
新建Activity,重写dispatchTouchEvent和onTouchEvent,前面的方法负责点击事件的分发,后面的方法负责点击事件的消耗,然后打印三种触摸事件的触发
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); }
新建一个类继承自LinearLayout,同样也重写dispatchTouchEvent和onTouchEvent,还有因为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; }
(这里有个重点).
这里有个需要注意的地方就是Android的控件有些是默认可以点击的(如Button),还有一些是默认不可点击的(如TextView)他们的分发是有一些不同的,这里我们先看不可点击的,新建一个类继承android.support.v7.widget.AppCompatTextView,兼容的TextView,同时跟Activity一样重写dispatchTouchEvent和onTouchEvent,代码不贴了,跟上面一样,它是普通控件,没有拦截的方法。
开始点击,移动手指后抬起
然后来点一点屏幕上的控件看看打印的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,这样来看, 组合起来真是情况太多了,写下来挨个分析看代码的话怕是要看晕,所以这里用一张图来看看所有的情况:
普通不可点击View的事件分发流程
默认可点击控件的事件分发
比如Button这种默认可以点击的控件,或者设置android:clickable=”true”的控件,在分发流程中有一些不同,主要是onTouchEvent的默认方法不同,它直接消耗点击事件,不再往上传递。
可点击View的事件分发流程
结语
事件的分发流程到此就结束了,目的已经达到了,找到了我们想要点击的那个按钮或者其他控件,总结下来就是从Activity经过ViewGroup然后到View依次分发,然后又从底向上确认自己是否消耗该事件,如果某个对象消耗了,动作的后续事件都由他来处理。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。