一、事件的流程
1.down----up
2.down---move-----up
3.down---move---cancel
二、事件分发的API
1.dispatchTouchEvent 分发事件
2.onIntercepTouchEvent 是否拦截拦截事件
3.onTouchEvent 是否处理事件
三、不同控件的事件分发
3.1 Activity
事件开启会调用Activity的dispatchEvent
1.首先会判断是否是Down事件,如果是预先调用用户自定义的处理
2.判断PhoneWindow是否处理事件
3.PhoneWindow实际上是调用的DecorView的dispatchEvent事件,如果DecorView(ViewGroup)拦截,那么事件结束
4.如果没有拦截那么总终会调用Activity的onTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
PhoneWindow的方法
@Override
public boolean
superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
3.2 ViewGroup的事件分发
ViewGroup的dispatchEvent方法是真正执行事件分发的,ViewGroup没有onTouchEvent方法
1.首先判断是否是Down事件或者是否可以接收Move和Up事件,事件是否接收根据FLAG_DISALLOW_INTERCEPT来判断,这个值由requestDisallowInterceptTouchEvent方法来设置
2.如果接收事件则会调用onInterceptTouchEvent方法判断是否对这个事件做处理,这个方法默认返回false,也就是不对事件做处理
3.如果onInterceptTouchEvent返回false,将分发给子view,分发的条件是子View可见,没有执行动画,并且这个点击的范围在这个子View的内
4.如果有子View可以拦截事件,那么就会调用dispatchTransformedTouchEvent将事件交给子View<BR>
5.如果子View接收到事件但是没有消费(dispatchTransformedTouchEvent),那么事件将会回传给父容器的dispatchTouchEvent方法
final boolean intercepted;
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;
}
是否处理该事件
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
ViewGroup是否消费这次事件
if (!canceled && !intercepted) {
// If the event is targeting accessiiblity focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
for (int i = childrenCount - 1; i >= 0; i--) {
.........
// 如果子view可以接收到事件
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
}
}
将事件分发给子View
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 (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
.....
}
3.3View的事件分发机制
ViewGroup将事件交给View后会调用View的dispatchTouchEvent方法。
1.判断是否通过setOnTouchEvent方法设置了touchListener,如果设置了直接将时间交给touchListener
2.如果没有设置listener,事件会交给onTouchEvent方法
dispatchTouchEvent核心代码
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//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;
}
}
1.TouchEvent处理UP,DOWN,MOVE等事件
TouchEvent中的核心代码
一个Disable的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;
}
2.在Down事件中,主要是发起对长按事件的检测
1.如果在一个滑动的容器中,会延时100ms去检测是否有长按事件,通过checkForLongClick方法
2.如果没有在滑动容器中,直接发起长按事件的检测
3.是否存在长按事件通过mHasPerformedLongPress标志位来判断,这个标志位在UP事件中被使用到,Down事件主要是设置它的值。
TouchEvent中的ActionDown事件
case MotionEvent.ACTION_DOWN:
......
// 是否处理了常按事件
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
// 是否在一个可滑动的容器
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
// 延时100ms,检测是否有长按事件
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
// 检测是否有长按事件
checkForLongClick(0, x, y);
}
break;
3.UP事件主要判断长按事件是否存在,以及是否执行短按事件
1.当事件走到UP时,如果mHasPerformedLongPress仍然为false,那么将移除这个长按事件。
2.如果在到达UP事件前,延时任务已经被触发,那么将执行长按事件。
3.没有执行长按事件,将会通过performClickInternal执行点击事件
TouchEvent的Up事件
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
//如果 mHasPerformedLongPress 仍然为false,那么移除这个长按事件
// 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)) {
// 执行click点击事件
performClickInternal();
}
}
}
performClick 点击事件
public boolean performClick() {
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
notifyAutofillManagerOnClick();
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
// 执行点击事件
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
return result;
}
4.CheckLongPress任务
checkLongPress是一个线程,在滑动容器中会延时500ms检查,在普通容器中会立即检查。一旦checkLongPress被执行,就将通过performLongClick调用我们设置的longClickListener
如果performLongClick方法返回true改变了mHasPerformedLongPress的值,那么点击事件将不执行。反之如果处理了longClickLisener事件,但是返回false,即不改变标志位的值,那么click事件仍然可以执行
private final class CheckForLongPress implements Runnable {
private int mOriginalWindowAttachCount;
private float mX;
private float mY;
private boolean mOriginalPressedState;
@Override
public void run() {
if ((mOriginalPressedState == isPressed()) && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
// 如果performLongClick方法返回true改变了mHasPerformedLongPress的值,那么点击事件将不执行,
if (performLongClick(mX, mY)) {
mHasPerformedLongPress = true;
}
}
}
5.performLongClickInternal
performLongClick最终会调用到performLongClickInternal,它是真正执行longClick事件的地方
public boolean performLongClick(float x, float y) {
mLongClickX = x;
mLongClickY = y;
final boolean handled = performLongClick();
mLongClickX = Float.NaN;
mLongClickY = Float.NaN;
return handled;
}
private boolean performLongClickInternal(float x, float y) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLongClickListener != null) {
handled = li.mOnLongClickListener.onLongClick(View.this);
}
......
return handled;
}
四、总结
1.事件从手指按下屏幕的Down事件,中间一系列的Move事件,最终到手指抬起的UP事件为一个正常的事件序列<BR>2.某一个View一旦决定拦截一个事件,那么这个事件的所有后续事件都由它来处理
3.某个View如果不拦截down事件(TouchEvent返回false),那么后续的事件都不会再给它。<BR>4.事件总是由父容器传递给子容器
5.ViewGroup的onIntercept方法默认是false的,所以默认不拦截事件
6.View的enable方法,不会影响事件的分发,如果View不可用,仍然可以拦截事件,只不过不会做出响应。
7.点击事件的前提是View可以被点击
8.长按事件必须要通过setOnLongClickListener设置监听才被触发
注意:
1.在滑动容器中Down---Up少于500ms,被认为不是长按事件
2.点击事件在UP事件触发
3.在longClickListener中返回false,那么事件即可以被长按事件处理也可以被点击事件处理。
4.如果设置了onTouchListener方法,那么不会执行onTouchEvent方法。