一.概述
事件分发有多种类型, 本文主要介绍Touch相关的事件分发.
-
整个事件分发流程中,会有大量MotionEvent对象,该对象用于记录所有与移动相关的事件,比如手指触摸屏幕事件。
-
一次完整的MotionEvent事件,是从用户触摸屏幕到离开屏幕。整个过程的动作序列:
ACTION_DOWN
(1次) ->ACTION_MOVE
(N次) ->ACTION_UP
(1次), -
多点触摸,每一个触摸点
Pointer
会有一个id和index。对于多指操作,通过pointerindex来获取指定Pointer的触屏位置。比如,对于单点操作时获取x坐标通过getX()
,而多点操作获取x坐标通过getX(pointerindex)
对于View,ViewGroup,Activity都能处理Touch事件, 它们之间处理的先后顺序和方法有所不同.
1.1 View
-
View是所有视图对象的父类,实现了动画相关的接口
Drawable.Callback
, 按键相关的接口KeyEvent.Callback
, 交互相关的接口AccessibilityEventSource
。比如Button继承自View。 -
TouchEvent事件处理相关的方法:
-
dispatchTouchEvent(MotionEvent event)
-
onTouchEvent(MotionEvent event)
-
1.2 ViewGroup
-
ViewGroup,是一个
abstract
类,一组View的集合,可以包含View和ViewGroup,是所有布局的父类或间接父类。继承了View
,实现了ViewParent
(用于与父视图交互的接口),ViewManager
(用于添加、删除、更新子视图到Activity的接口)。比如常用的LinearLayout,RelativeLayout都是继承自ViewGroup。 -
TouchEvent事件处理相关的方法:
-
dispatchTouchEvent(MotionEvent event)
-
onInterceptTouchEvent(MotionEvent ev)
-
onTouchEvent(MotionEvent event)
-
1.3 Activity
-
Activity是Android四大基本组件之一,当手指触摸到屏幕时,屏幕硬件一行行不断地扫描每个像素点,获取到触摸事件后,从底层产生中断上报。再通过native层调用Java层
InputEventReceiver
中的dispatchInputEvent
方法。经过层层调用,交由Activity的dispatchTouchEvent
方法来处理。 -
TouchEvent事件处理相关的方法:
-
dispatchTouchEvent(MotionEvent event)
-
onTouchEvent(MotionEvent event)
-
二. 分发原理
Input系统—进程交互文章的小节[3.3]已介绍事件分发过程的前期工作. 当UI主线程收到底层上报的input事件,便会调用InputEventReceiver.dispachInputEvent方法.
2.1 DecorView.dispatchTouchEvent
[-> PhoneWindow.java ::DecorView]
此处cb是指Window的内部接口Callback. 对于Activity实现了Window.Callback接口. 故接下来调用Activity类.
2.2 Activity.dispatchTouchEvent
[-> Activity.java]
如果重写Activity的该方法,则会在分发事件之前拦截所有的触摸事件. 另外此处getWindow()返回的是Activity的mWindow成员变量, 该变量赋值过程是在Activity.attach()方法, 可知其类型为PhoneWindow.
2.2.1 Activity.onTouchEvent
[-> Activity.java]
2.3 superDispatchTouchEvent
[-> PhoneWindow.java]
PhoneWindow的最顶View是DecorView,再交由DecorView处理。而DecorView的父类的父类是ViewGroup,接着调用 ViewGroup.dispatchTouchEvent()方法。为了精简篇幅,有些中间函数调用不涉及关键逻辑,可能会直接跳过。
2.4 ViewGroup.dispatchTouchEvent
2.4.1 onFilterTouchEventForSecurity
根据隐私策略来过滤触摸事件。当返回true,表示继续分发事件;当返回flase,表示该事件应该被过滤掉,不再进行任何分发。
2.4.2 onInterceptTouchEvent
-
当返回true,表示该事件被当前视图拦截;
-
当返回false,继续执行事件分发。
2.4.3 buildOrderedChildList
获取一个视图组的先序列表,通过虚拟的Z轴来排序。
public float getZ() {
return getElevation() + getTranslationZ();
}
getZ()
用于获取Z轴坐标。屏幕只有x,y坐标,而Z是虚拟的,可通过setElevation()
,setTranslationZ()
或者setZ()
方法来修改Z轴的坐标值。
2.4.4 dispatchTransformedTouchEvent
该方法是ViewGroup真正处理事件的地方,分发子View来处理事件,过滤掉不相干的pointer ids。当子视图为null时,MotionEvent将会发送给该ViewGroup。最终调用View.dispatchTouchEvent方法来分发事件。
2.4.5 addTouchTarget
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
调用该方法,获取了TouchTarget,同时mFirstTouchTarget不再为null。
2.5 View.dispatchTouchEvent
[-> View.java]
-
先由OnTouchListener的OnTouch()来处理事件,当返回True,则消费该事件,否则进入2。
-
onTouchEvent处理事件,的那个返回True时,消费该事件。否则不会处理
2.5.1 View.onTouchEvent
三. 总结
事件分发流程图:
-
onInterceptTouchEvent
返回值true表示事件拦截,onTouch/onTouchEvent
返回值true表示事件消费。 -
触摸事件先交由
Activity.dispatchTouchEvent
。再一层层往下分发,当中间的ViewGroup都不拦截时,进入最底层的View后,开始由最底层的OnTouchEvent
来处理,如果一直不消费,则最后返回到Activity.OnTouchEvent
。 -
ViewGroup才有
onInterceptTouchEvent
拦截方法。在分发过程中,中间任何一层ViewGroup都可以直接拦截,则不再往下分发,而是交由发生拦截操作的ViewGroup的OnTouchEvent
来处理。 -
子View可调用
requestDisallowInterceptTouchEvent
方法,来设置disallowIntercept=true
,从而阻止父ViewGroup的onInterceptTouchEvent
拦截操作。 -
OnTouchEvent由下往上冒泡时,当中间任何一层的OnTouchEvent消费该事件,则不再往上传递,表示事件已处理。
-
如果View没有消费ACTION_DOWN事件,则之后的ACTION_MOVE等事件都不会再接收。
-
只要
View.onTouchEvent
是可点击或可长按,则消费该事件. -
onTouch
优先于onTouchEvent
执行,上面流程图中省略,onTouch
的位置在onTouchEvent
前面。当onTouch
返回true,则不执行onTouchEvent
,否则会执行onTouchEvent。onTouch
只有View设置了OnTouchListener
,且是enable的才执行该方法。