Android事件分发机制
主要方法:
1.dispatchTouchEvent(MotionEvent ev):用来进行事件分发
2.onInterceptTouchEvent(MotionEvent ev):判断是否拦截事件(只存在于ViewGroup中)
3.onTouchEvent(MotionEvent ev):处理点击事件
一、事件分发:Activity
Activity.java
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//如果返回false,就走下边的onTouchEvent()
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
/**
* Called when a touch screen event was not handled by any of the views
* under it. This is most useful to process touch events that happen
* outside of your window bounds, where there is no view to receive it.
*
* @param event The touch screen event being processed.
*
* @return Return true if you have consumed the event, false if you haven't.
* The default implementation always returns false.
*/
public boolean onTouchEvent(MotionEvent event) {
//超出可触摸边界就直接finish掉
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
传递流程:
Activity:dispatchTouchEvent--->PhoneWindow:superDispatchTouchEvent--->DecorView:superDispatchTouchEvent--->ViewGroup:dispatchTouchEvent
如果子view没有消费触摸事件,最终就是走了Activity的onTouchEvent()的事件分发
二、事件分发:ViewGroup:dispatchTouchEvent()
step1: 在DOWN的时候,先调用了cancelAndClearTouchTargets(ev);和resetTouchState();清楚掉之前的点击状态并重置标记位;
ViewGroup--line:2559
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
step2:检测是否需要拦截。这里定义了一个boolean intercepted来记录是否需要拦截
line:2567
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//用来禁止viewGroup拦截除了down以外的事件,
//通过子view调用requestDisallowInterceptTouchEvent()方法来控制
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;
}
step3:判断不是DOWN事件后,通过一个for循环,拿到子view,又进行了一个判断canViewReceivePointerEvents(),判断能否接收到点击事件。
line:2647
//如果不可见或在执行动画或不在可触摸区域范伟之内,就不可响应触摸事件,continue
if (!canViewReceivePointerEvents(child) ||
!isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
line:2940
/**
* Returns true if a child view can receive pointer events.
* 如果是可见的且没有在执行动画,就可以接收点击事件
* @hide
*/
private static boolean canViewReceivePointerEvents(@NonNull View child) {
return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null;
}
当可以接收点击事件,调用该方法,传入子view,当child不为空的时候,调用子view的dispatchTouchEvent(),交给子view处理。
line:2662
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {...}
line:2988
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
...
}
走完step1—step3,就完成了viewGroup到view的事件传递,并返回了一个handled的布尔值,表示子view消耗了点击事件。
小结:dispatchTouchEvent()是用来分发的,最终的处理都是在onTouchEvrent()
三 、View:dispatchTouchEvent()
View--line:12509
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
...
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
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;
}
由上面源码可以得出,onTouchEvent()和onTouch()方法优先级及控制关系:
①如果onTouch()方法返回值是true(事件被消费)时,则onTouchEvent()方法将不会被执行;
②只有当onTouch()方法返回值是false(事件未被消费,向下传递)时,onTouchEvent方法才被执行。
View---line:13718
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//clickable标记view是否可以被消费
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
...
}
clickable标记view可否消费事件,哪怕view是disable得,但只要它可点击或长按,就仍然消费事件,只是说没有消费他们。
View--onTouchEvent--ACTION_UP line:13775
//如果设置了长按事件,且长按事件返回true,则不会执行onClick事件
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
View的长按事件在ACTION_DOWN中处理,UP中处理点击事件,如果500ms内没有触发UP事件,则长按事件会执行(设置了长按监听器)。如果设置了长按事件,且长按事件返回true,则不会执行onClick事件,返回的false,则长按点击事件都会执行。
由此可见,给View设置监听OnTouchListener时,重写的onTouch()方法,其优先级比onTouchEvent()要高,假如onTouch方法返回false,会接着触发onTouchEvent,反之onTouchEvent方法不会被调用。内置诸如click事件的实现等等都基于onTouchEvent,假如onTouch返回true,这些事件将不会被触发。OnClickListener,其优先级最低,即处于事件传递的尾端。
_
_
_
_
总结
类似领导向下属分配任务,逐级下传,中途遇到处理不了就报告上级。
事件分发流程 .png
- 一个事件序列从手指接触屏幕到手指离开屏幕,在这个过程中产生一系列事件,以DOWN事件开始,中间含有n个MOVE事件,以UP事件结束;
- 正常情况下,一个事件序列只能被一个View拦截并消耗;
- 某个View一旦决定拦截,那么这个事件序列都将由它的onTouchEvent处理,并且它的onInterceptTouchEvent不会再调用;
- 某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回false),那么同一事件序列中其他事件都不会再交给它处理。并且重新交由它的父元素处理(父元素onTouchEvent被调用);
- 事件的传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent反法可以在子View中干预父元素的事件分发过程,但ACTION_DOWN除外;
- ViewGroup默认不拦截任何事件,即onInterceptTouchEvent默认返回false。View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就被调用;
- VIew的onTouchEvent默认会消耗事件(返回true),除非它是不可点击的(clickable和longClickable同时为false)。View的longClickable默认都为false,clickable要分情况,比如Button的clickable默认为true,TextView的clickable默认为false;
- View的enable属性不影响onTouchEvent的默认返回值。哪怕一个View是disable状态,只要它的clickable或者longClickable有一个为true,那么它的onTouchEvent就返回true;
- onClick会响应的前提是当前View是可点击的,并且收到了ACTION_DOWN和ACTION_UP事件,并且受长按事件影响,当长按事件返回true,onClick不会响应;
- onLongClick在ACTION_DOWN里判断是否进行响应,要想执行长按事件该View必须是longClickable的并且设置了OnLongClickListener;
onInterceptTouchEvent()方法返回值
一般重载这个函数地方就是你自定义了一个布局,extends LinearLayout等等布局,一般除了自己的业务处理外,返回值只有两种。
第一种:返回值为false
作用:让自定义布局上面的所有子view 例如button imageview 等可以被点击
第二种:直接返回true
作用:让自定义布局上面的所有子view不可以被点击
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;
}
分分钟明白onTouchEvent事件分发
首先确定有三种,由内向外依次为:
1、View自己的onTouchEvent
2、ViewGroup的onTouchEvent,由于要管理它的子View的onTouchEvent,所以多了个onInterceptTouchEvent(鼓励重载这个而不是dispatchTouchEvent,因为后者是对ViewGroup共性的提取,前者才是针对个例)
3、Activity的onTouchEvent
三者都是先处理setOnTouchEvent的onTouch事件,返回true表示不想下传递,就不进入到onTouchEvent中了
先说ViewGroup的onInterceptTouchEvent,两种情况:
1.返回值为True,代表拦截这次事件,直接进入到ViewGroup的onTouchEvent中,就不会进入到View的onTouchEvent了
2.返回值为False,代表不拦截这次事件,不进入到ViewGroup的onTouchEvent中,直接进入到View的onTouchEvent中
再说三者的onTouchEvent:
1.View的onTouchEvent返回为false
表示view处理完onTouchEvent后不消费这次事件,那么这个事件就会继续传递到他的上一层ViewGroup的onTouchEvent事件中,返回true
的话就传递完毕,进入不到ViewGroup的onTouchEvent中了
2.ViewGrop的onTouchEvent返回为false表示这个ViewGroup处理完onTouchEvent后不消费这次事件,这个事件就会继续传递到activity的onTouchEvent中,返回为true的话就传递完毕,进入不到activity的onTouchEvent中了
3.activity的onTouchEvent,就这样吧 `(∩_∩)′
总之,如果最里层的返回false就会交给他的上一层处理,否则就会消费这次事件,停止传递,over!