之前看过很多的事件分发博客,特别是看分析源码,一看到源码就头大,太菜没办法,就只能死记别人最后的结论。还自我安慰事件分发明白的差不多了,但一到项目中,很多时候还是要靠搜索引擎。痛定思痛,决定还是看源码,并写博客记录。
View的dispatchTouchEvent():
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
//默认返回值为false
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
//down事件停止嵌套滚动
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//关键代码1:设置了onTouchListener,并且其onTouch返回true,dispatchTouchEvent直接return true,
//不走下面if语句;如果onTouch()返回false,条件不成立,接着往下走
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//关键代码2:如果上面的if条件不成立,走到这里,onTouchEvent()返回值决定了dispatchTouchEvent()的返回值
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
//上面2处关键的if语句都没有走进去,返回默认值false
return result;
}
view的dispatchTouchEvent()代码不多,只有两处关键代码,在这里有个思考:为什么onTouchListener的onTouch()方法比onTouchEvent()方法先执行?因为onTouchListener()是暴露给开发者处理Touch事件的接口,而onTouchEvent()是系统默认的处理Touch事件的函数,所以如果开发者想自己定义Touch事件处理方式,就必须在系统的默认处理方式之前执行。
View的onTouchEvent():
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//如果当前view的Clickable或者LongClickable为true,那么clickable标记为true
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//如果当前view是disable,直接返回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.
//直接返回clickable的值
return clickable;
}
//TouchDelegate是一个代理类,可以用来扩展某个view的触摸范围,需要手动设置
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//如果clickable为true(手动设置了setClickable(true)或者setLongClickable(true), 或者设置了onClickListener(),
//longClickListener(),clickable都为true)
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
//是否是预按下状态,只有在滚动的容器类View按下的时候才会添加这个标记
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
//如果有按下标记,或者是预按下状态
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
//判断是否获取过焦点
boolean focusTaken = false;
//如果能获取焦点,并且没有过获取焦点
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
//focusTaken设为true
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
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
//按下状态执行点击action
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();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_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();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
//设置按下状态,会添加PFLAG_PRESSED按下标记
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
//如果移出了当前view区域
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
//如果有按下标记,down的时候会添加这个标记
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
//移除按下标记
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
return true;
}
return false;
}
通过源码可以看到:一个view的clickable决定了onTouchEvent()的返回值,如果一个view的clickable为false,down事件传递进来后,什么都不会执行,最后直接就返回false了,而且这一个Touch事件系列中的后续move,up事件都不会传递到该view,直到下一个Touch事件系列到来(一个Touch事件系列会一般经历down---->move----->up)。
在这里还想补充:在实际开发中,view的dispatchTouchEvent()和onTouchEvent()的返回值并不是只有true或false。还有super.dispatchTouEvent()和super.onTouchEvent()。调用super.xxx就是调用父类的相关方法,父类再调用父类的,最终就是调用上面分析的相关方法,所以如果直接返回true或false,不调用super.xxx,那么Touch事件就不会通过系统默认的分发方式去分发,所以如果我们只想处理某些特殊情况,而一般情况还是需要正常分发事件,super.xxx不能去掉。