View事件分发机制的源码解析
ViewGroup不论是拦截事件或者将事件分发给子View,其最终都是调用了View的dispatchTouchEvent方法,毕竟ViewGroup也是继承自View。所以下面分析View#dispatchTouchEvent源码来了解View事件分发机制。
public boolean dispatchTouchEvent(MotionEvent event) {
...
boolean result = false;
...
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;
}
}
...
return result;
}
View对点击事件的处理过程就比较简单了,因为View中没有子元素无法向下传递事件,所以View是所有事件传递的最终端,只能自己处理事件。
从上面的源码中可以看出View对点击事件的处理过程,首先会判断是否设置了OnTouchListener,如果OnTouchListener中的onTouch方法返回true,那么onTouchEvent就不会被调用,可见OnTouchListener的优先级高于onTouchEvent,这样做的好处是方便在外界处理点击事件。
接着再分析onTouchEvent的实现。先看当View处于不可用状态下点击事件的处理过程,如下所示。不可用状态下的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;
}
接着,如果View设置有代理,那么当前View的点击事件会交给代理的View来处理,调用代理View的onTouchEvent方法,如果代理View消耗了事件,那么相当于当前View消耗了事件。
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
接下来便是onTouchEvent中对点击事件的具体处理
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
...
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
...
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)) {
performClick();
}
}
}
...
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
...
break;
case MotionEvent.ACTION_CANCEL:
...
break;
case MotionEvent.ACTION_MOVE:
...
break;
}
return true;
}
可以看出当CLICKABLE或者LONG_CLICKABLE以及CONTEXT_CLICKABLE中一个或多个标志被设置时,onTouchEvent返回true,表示消耗这个事件,不论当前View是否是DISABLE状态。当ACTION_UP事件发生时,会触发 performClick 方法,如果 View 设置了 OnClickListener,那么 performClick 方法内部会调用它的onClick方法,如下所示:
public boolean performClick() {
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;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
View的LONG_CLICKABLE属性默认为false, 而CLICKABLE属性是否为false和具体的View有关,确切来说是可点击的View其CLICKABLE为true, 不可点击的View其
CLICKABLE为 false, 比如Button是可点击的,TextView是不可点击的。通过setClickable和setLongClickable以及setContextClickable可以分别改变View 的CLICKABLE和 LONG_CLICKABLE以及属性。另外,setOnClickListener 会自动将 View的CLICKABLE设为 true, setOnLongClickListener则会自动将View的LONG_CLICKABLE设为true,setOnContextClickListener会自动将 View的CONTEXT_CLICKABLE设为 true,这一点从源码中可以看出来,如下所示。
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
public void setOnLongClickListener(@Nullable OnLongClickListener l) {
if (!isLongClickable()) {
setLongClickable(true);
}
getListenerInfo().mOnLongClickListener = l;
}
public void setOnContextClickListener(@Nullable OnContextClickListener l) {
if (!isContextClickable()) {
setContextClickable(true);
}
getListenerInfo().mOnContextClickListener = l;
}
View的事件分发主要是对事件的具体处理,上面主要分析ACTION_UP点击事件的处理流程,其它点击事件可参照源码具体分析。同样对View的事件分发总结为一张流程图,如下:
Android事件分发机制深度分析(一)
Android事件分发机制深度分析(二)
Android事件分发机制深度分析(三)