事件分发概述
常用View
(1) 常用组件
用户界面的基本组件为View类及其子类, Android里面预定义很多基本的界面组件,比如Button、CheckBox、TextView等,它们一般称作组件(components)或是部件(widgets)。
(2) ViewGroup
一个View也可以由多个其他的View组件构成,即android.view.ViewGroup类及其子类,它本身也是继承自View类,它可以充当一个父视图(parent view),容纳其他的子视图(child views)。
(3) Layout
另外一种常用的ViewGroup就是布局,是一种容器视图,比如RelativeLayout,TableLayout等。
监听器
在监听器中写执行代码,当触发某类事件时就会执行,相当于回调。
| 常用监听器 | 响应事件 |
|---|---|
| onClickListener | 用于响应点击事件 |
| onLongClickListener | 用于响应长按事件 |
| onTouchListener | 用于响应触摸事件,比如多手势 |
| onFocusChangeListener | 用语响应焦点转移事件 |
| onKeyListener | 用于响应键盘按键事件 |
事件处理基本规则
同一个事件序列是指从手指接触屏幕起,到离开屏幕结束,以down开始,包含数量不定的move事件,以up结束;
- 正常情况下,一个事件序列只能被一个View拦截且消耗;
- 某个View决定拦截,那么这一个事件序列都只能由它来处理;
- 某个View一旦开始处理事件,如果不消耗ACTION_DOWN事件(onTouchEvent方法返回false),那么同一事件序列中的其他事件都不会再交给他来处理;
- 如果View不消除ACTION_DOWN以外的事件,那么这个点击事件会消失;
- ViewGroup默认不拦截任何事件,onInterceptTouchEvent方法默认返回false;
- View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,onTouchEvent方法会被调用;
- View的onTouchEvent方法默认返回true,除非是不可点击的;
- View的enable属性不影响onTouchEvent方法的默认返回值;
- onClick会发生的前提是View是可点击的,并且收到了down和up事件;
- 事件传递过程是由外向内的,即总是先传递给父元素,然后由父元素分发给子View。
APP事件分发过程
View的事件分发概述
(1) 窗口层级
一个界面的窗口层级如下, 事件传递从 Activity 逐层向下传递的 View 组件上:
(2) 事件分发方法
MotionEvent即点击事件,当一个MotionEvent产生后,系统需要把这个事件传递给一个具体的View,这个过程就是分发过程。
点击事件的分发过程由dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent三个方法共同完成。
| 方法 | 说明 |
|---|---|
| public boolean dispatchTouchEvent(MotionEvent event) | 用来进行事件分发,如果事件能传递给当前View,那么此方法被调用 |
| public boolean onInterceptTouchEvent(MotionEvent event) | 是ViewGroup提供的方法,在dispatchTouchEvent()方法内部调用,用来判断是否拦截某个事件。 默认返回false,返回true表示拦截,于是不再将事件分发给View。 View中不提供onInterceptTouchEvent()。view中默认返回true,表示消费了这个事件 |
| public boolean onTouchEvent(MotionEvent event) | 在dispatchTouchEvent方法内部调用,用来处理点击事件。返回结果为true表示消耗当前事件 |
注:ViewGroup是容纳这些View组件的容器,如LinearLayout、RelativeLayout等 (extends ViewGroup),其本身也是从View派生出来的,处理事件机制与View一样。
(3) 分发大致流程
当一个点击事件产生以后,它的传递顺序为:Activity —> Window —> ViewGroup —> View。
即一个事件总是先传递给Activity,Activity再传递给Window,最后Window再传递给顶级View(即ViewGroup),顶级View接收到事件后会选择是否分发事件给View。
如果一个View的onTouchEvent方法返回false,那么它的父容器的onTouchEvent方法会被调用,即当所有元素都不处理这个事件,那么这个事件最终传递给Activity处理。
以ViewGroup为例,其分发事件的伪代码如下:
//当一个事件产生以后,会传递给根ViewGroup,并调用根ViewGroup的dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
//如果这个ViewGroup要拦截当前事件,那么调用onInterceptTouchEvent后返回true
//并调用onTouchEvent方法
if (onInterceptTouchEvent(ev)) {
consume = onTouchEvent(ev);
} else {
//如果这个ViewGroup不拦截当前事件,那么调用onInterceptTouchEvent后返回false
//并将当前事件传递给它的子元素,调用子元素的dispatchTouchEvent方法,如此反复
consume = child.dispatchTouchEvent(ev);
}
//返回false表示该事件未被消费,
return consume;
}
Activity
Activity.java
//首先事件交给Activity.dispatchTouchEvent()进行分发
public boolean dispatchTouchEvent(MotionEvent ev) {
//onUserInteraction()是空方法,用户可重载。有事件分发到当前Activity就会被调用
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//事件开始交给Activity所拥有的Window进行分发,向下传递
//superDispatchTouchEvent()返回true表示事件循环结束
//返回false意味着事件没人处理
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//如果所有View的onTouchEvent都返回了false,那么Activity的onTouchEvent就会被调用
return onTouchEvent(ev);
}
PhoneWindow
PhoneWindow.java
Window是个抽象类,而Window的superDispatchTouchEvent方法也是个抽象方法,其唯一的实现类是PhoneWindow。
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
//PhoneWindow将事件传递给了DecorView
return mDecor.superDispatchTouchEvent(event);
}
//DecorView将事件传递给其父类FrameLayout,FrameLayout没有重写dispatchTouchEvent,
//所以直接调用到其父类ViewGroup.dispatchTouchEvent(event)
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
}
ViewGroup
ViewGroup作为一个parent是可以截获传向它的child的touch事件的。
如果一个ViewGroup的onInterceptTouchEvent()方法返回true,说明Touch事件被截获,子View不再接收到Touch事件,而是转向本ViewGroup的 onTouchEvent()方法处理。
从Down开始,之后的Move,Up都会直接在onTouchEvent()方法中处理。先前还在处理touch event的child view将会接收到一个 ACTION_CANCEL。如果onInterceptTouchEvent()返回false,则事件会交给child view处理。
其dispatchTouchEvent()中代码较多,简化为伪代码进行分析。
//当一个事件产生以后,会传递给根ViewGroup,并调用根ViewGroup的dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
//标识一个消息是否被处理(消费)
boolean handled = false;
// Check for interception.
final boolean intercepted;
//标识是否拦截事件
final boolean intercepted;
//当事件类型为ACTION_DOWN或者mFirstTouchTarget != null,不拦截事件
//当ViewGroup的子元素成功处理事件时,mFirstTouchTarget会被赋值并指向子元素
//当ViewGroup不拦截事件并将事件交由子元素处理时mFirstTouchTarget != null
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;
}
}
public boolean dispatchTouchEvent(MotionEvent ev) {
//标识一个消息是否被处理(消费)
boolean handled = false;
//如果这个ViewGroup要拦截当前事件,那么调用onInterceptTouchEvent后返回true
//并调用onTouchEvent方法
if (onInterceptTouchEvent(ev)) {
handled = onTouchEvent(ev);
} else {
//如果这个ViewGroup不拦截当前事件,那么调用onInterceptTouchEvent后返回false
//并将当前事件传递给它的子元素,调用子元素的dispatchTouchEvent方法,如此反复
handled = child.dispatchTouchEvent(ev);
}
//返回false表示该事件未被消费,
return handled;
}
View
如果ACTION_DOWN事件发生在某个View的范围之内,则后续的ACTION_MOVE,ACTION_UP和ACTION_CANCEL等事件都将被发往该View,即使事件已经出界了。
若Down事件一直未被消费,则该View不会执行move和up事件。
View.java
public boolean dispatchTouchEvent(MotionEvent event) {
ListenerInfo li = mListenerInfo;
if (li != null
&& li.mOnTouchListener != null //用户设置了OnTouchListener则为true
&& (mViewFlags & ENABLED_MASK) == ENABLED //View是否可用
//调用自定义的OnTouchListener.onTouch(),返回true,表示消费事件
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//返回true标识在执行完onTouch里面的代码之后,onTouch事件并没有结束
//result=true,表示消费事件
//false则执行onTouchEvent()
if (!result && onTouchEvent(event)) {
result = true;
}
//...
return result;
}
onTouch()是View提供给用户,让用户自己处理触摸事件的接口。而onTouchEvent()是Android系统自己实现的接口。(02),onTouch()的优先级比onTouchEvent()的优先级更高。dispatchTouchEvent()中分发事件的时候,会先将事件分配给onTouch()进行处理,然后才分配给onTouchEvent()进行处理。 如果onTouch()对触摸事件进行了处理,并且返回true;那么,该触摸事件就不会分配在分配给onTouchEvent()进行处理了。只有当onTouch()没有处理,或者处理了但返回false时,才会分配给onTouchEvent()进行处理。
然后再看一下View.onTouchEvent()
只要View的CLICKABLE和LONG_CLICKABLE有一个为true,那么它就会消耗这个事件。