在安卓事件分发学习之onInterceptTouchEvent方法一文中,我记录了事件分发流程里第二个方法onInterceptTouchEvent()的源码阅读过程,现在记录一下最后一个方法onTouchEvent()的阅读
在文章安卓事件分发学习之dispatchTouchEvent方法中,可以看到onTouchEvent()方法的执行优先级是View->ViewGroup->Activity,那我就按着这个顺序来阅读此方法
其实在阅读的时候发现,ViewGroup并没有覆写onTouchEvent,所以它执行的,还是View类的onTouchEvent。
所以只看View.onTouchEvent()和Activity.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();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
// 是不是可点击的
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// 如果当前view是disable状态
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false); // action_up事件,并且pressed标志位不是0,就设置pressed为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是disable但可点击,就仍然消费这个事件流,但不响应
}
// 设置touch代理的话,调用touch代理的onTouchEvent(),并且返回true,确定消费事件流
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
// 只考虑clickable的情况,不是clickable,绝对返回false
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
// 移除pressed标志位和mPendingCheckForTap回调,但是一般情况这个!clickable判断不成立
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
// 是否设置了prePressed或是否按下pressed
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()) {
// 当前view或子view是否有焦点,有的话,不会执行onClick()
focusTaken = requestFocus();
}
if (prepressed) {
setPressed(true, x, y);
// action_up的时候才会setPressed()
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// 没有执行onLongClick()并且不是多点触摸的话(mIgnoreNextUpEvent似乎在多点触摸的情况下,才可能为true)
// This is a tap, so remove the longpress check
removeLongPressCallback(); // 移除监听
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// 当前view或子view没有焦点,才会执行onClick()
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
// action_up的时候才会调用onClick()
performClick();
}
}
}
// UnsetPressedState类唯一的方法--run()的唯一操作就是setPressed(false)
// 这里的目的当然就是设置pressed为false
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();
}
// 移除pressed标志位和mPendingCheckForTap回调
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
// 当前view不是clickable,就尝试执行onLongClick()
checkForLongClick(0, x, y); // 但一般情况下,这个判断不会成立
break;
}
if (performButtonActionOnTouchDown(event)) {
// 这个方法一般返回false
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// 三大布局这里都是false
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
// 根据父view能不能scroll决定是不是尝试执行onLongClick
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// 一般是走这儿
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) {
// 当前view可点击的话,重新画热区
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
// 如果移到了view外面,进行回调的移除和标志位的回置,setPressed为false
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
return true;
}
return false;
}
代码虽长,逻辑却算不得复杂
得出来的最重要的几个结论就是:
1、onClick、onLongClick优先级是小于onTouchEvent()的,同时结合前一篇文章,可以得到优先级如下:onTouch()、onTouchEvent()、onLongClick()、onClick()
2、onClick是在action_up里执行的,onLongClick是在action_down里执行的。而且如果onLongClick返回true,onClick不会执行
3、有焦点的时候是不会执行onClick的
可以参加下面的源码记录
在action_down里面,调用了一个重要方法:checkForLongClick,源码如下
private void checkForLongClick(int delayOffset, float x, float y) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
mHasPerformedLongPress = false; // 这个标志位决定了onClick是否在action_up时调用
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.setAnchor(x, y);
mPendingCheckForLongPress.rememberWindowAttachCount();
mPendingCheckForLongPress.rememberPressedState();
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
}
如果是long_clickable的话,就post一个runnable类对象--CheckForLongPress类,此类源码如下
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) {
if (performLongClick(mX, mY)) {
mHasPerformedLongPress = true;
}
}
}
public void setAnchor(float x, float y) {
mX = x;
mY = y;
}
public void rememberWindowAttachCount() {
mOriginalWindowAttachCount = mWindowAttachCount;
}
public void rememberPressedState() {
mOriginalPressedState = isPressed();
}
}
最主要的是run()方法,正常情况下,run()方法里第一个判断会成立,那就会执行performLongClick(),它的返回值为true,mHasPerformLongPress就为true,反之为false
performLongClick()方法直接调用了performLongClickInternal()方法,源码如下
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);
}
if (!handled) {
final boolean isAnchored = !Float.isNaN(x) && !Float.isNaN(y);
handled = isAnchored ? showContextMenu(x, y) : showContextMenu();
}
if ((mViewFlags & TOOLTIP) == TOOLTIP) {
if (!handled) {
handled = showLongClickTooltip((int) x, (int) y);
}
}
if (handled) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
return handled;
}
可见,最主要的就是我们的监听器的onLongClick()方法,它为true,就铁定返回true,onClick()就不会执行,它为false,一般情况下(没考虑上下文菜单),onClick才会执行
如果一个事件流没有一个view处理,那就会走到Activity.onTouchEvent()中,这个方法代码如下
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
/*
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
&& isOutOfBounds(context, event) && peekDecorView() != null) {
return true; // 超过了边界而且是down事件,返回true
}
return false;
}
*/
finish();
return true;
}
return false;
}
这里返回true或false已经无所谓了,反正事件流也到头了。
安卓事件分发源码大体就这样了,源码来自sdk-26