Android view事件处理

Android view 事件处理

1、事件处理

事件通过ViewGroup分发完后,就到了事件处理,事件处的方法也是dispatchTouchEvent,只不过这里的dispatchTouchEvent是在View里面的。下面我们来看一下源码。

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);
    }
    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
        stopNestedScroll();
    }

    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;
        }
    }

    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();
    }

    return result;
}

源码看似很长,但是涉及到事件的也就一小段代码而已,下面这段代码涉及两个事件处理li.mOnTouchListener.onTouch(this, event),这个事件是通过setOnTouchListener(OnTouchListener l)设置进来的 和 onTouchEvent(event)事件处理 , 这个处理时View本身自带的,继承View可以重写它。看这段代码是先执行li.mOnTouchListener.onTouch(this, event),返回true就不再执行onTouchEvent(event)了,返回false才继续执行onTouchEvent(event)。所以通过setOnTouchListener(OnTouchListener l)设置的事件监听的优先级要大于View本身自带的onTouchEvent(event)事件监听。想要li.mOnTouchListener.onTouch(this, event)与onTouchEvent(event)事件处理同时执行,必须让li.mOnTouchListener.onTouch(this, event)返回结果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;
    }
}

在这里插入图片描述

2、onOnClickListener事件

上面讲了OnTouchListener事件和onTouchEvent事件处理。哪onOnClickListener事件在哪里执行呢?通过查看onTouchEvent源码,发现在onTouchEvent方法里的MotionEvent.ACTION_UP事件处理是调用了performClickInternal();

 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();
    }
}

查看performClickInternal()方法的代码,最后调用了performClick()方法

private boolean performClickInternal() {
    // Must notify autofill manager before performing the click actions to avoid scenarios where
    // the app has a click listener that changes the state of views the autofill service might
    // be interested on.
    notifyAutofillManagerOnClick();

    return performClick();
}

查看performClick()方法的代码,在一系列的判断后,调用li.mOnClickListener.onClick(this);返回true,到此onOnClickListener监听结束了

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;
    }

    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

    notifyEnterOrExitForAutoFillIfNeeded(true);

    return result;
}

3、onLongClickListener事件

onOnClickListener事件是在onTouchEvent方法里执行的,那onLongClickListener肯定也是在onTouchEvent方法里执行的执行的,只不过执行的地方不一样,onLongClickListener事件执行是在MotionEvent.ACTION_DOWN事件中执行的checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);。

case MotionEvent.ACTION_DOWN:
    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
    }
    mHasPerformedLongPress = false;

    if (!clickable) {
        checkForLongClick(
            ViewConfiguration.getLongPressTimeout(),
            x,
            y,
            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
        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
        setPressed(true, x, y);
        checkForLongClick(
            ViewConfiguration.getLongPressTimeout(),
            x,
            y,
            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
    }
    break;

查看checkForLongClick方法代码,初始化CheckForLongPress线程对象,postDelayed(mPendingCheckForLongPress, delay)固定时间运行线程。

private void checkForLongClick(long delay, float x, float y, int classification) {
    if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
        mHasPerformedLongPress = false;

        if (mPendingCheckForLongPress == null) {
            mPendingCheckForLongPress = new CheckForLongPress();
        }
        mPendingCheckForLongPress.setAnchor(x, y);
        mPendingCheckForLongPress.rememberWindowAttachCount();
        mPendingCheckForLongPress.rememberPressedState();
        mPendingCheckForLongPress.setClassification(classification);
        postDelayed(mPendingCheckForLongPress, delay);
    }
}

在这里我们看一下CheckForLongPress线程运行了哪里代码,看下面代码最后调用的是performLongClick(mX, mY)方法。

private final class CheckForLongPress implements Runnable {
    private int mOriginalWindowAttachCount;
    private float mX;
    private float mY;
    private boolean mOriginalPressedState;
    /**
     * The classification of the long click being checked: one of the
     * FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__* constants.
     */
    private int mClassification;

    @UnsupportedAppUsage
    private CheckForLongPress() {
    }

    @Override
    public void run() {
        if ((mOriginalPressedState == isPressed()) && (mParent != null)
                && mOriginalWindowAttachCount == mWindowAttachCount) {
            recordGestureClassification(mClassification);
            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();
    }

    public void setClassification(int classification) {
        mClassification = classification;
    }
}

查看performLongClick方法代码,最后调用的是performLongClick()方法

public boolean performLongClick(float x, float y) {
    mLongClickX = x;
    mLongClickY = y;
    final boolean handled = performLongClick();
    mLongClickX = Float.NaN;
    mLongClickY = Float.NaN;
    return handled;
}

查看performLongClick()方法,最后调用的是performLongClickInternal方法。

public boolean performLongClick() {
    return performLongClickInternal(mLongClickX, mLongClickY);
}

查看performLongClickInternal方法,到了这里看到了调用了li.mOnLongClickListener.onLongClick(View.this);到此onLongClickListener事件监听就结束了。

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;
}

4、总结

1)一个事件只能消费一次,返回true代表事件已消费,返回false代表事件没有被消费

2)onTouchListener优先级大于onTouchEvent,当两者同时存在时,如果onTouchListener消费了事件返回true了,则onTouchEvent不执行;只有onTouchListener返回false时,onTouchEvent才能被执行(两者都被执行)。

3)onClickListener事件是在onTouchEvent中的ACTION_UP事件中被执行的,也就是说同时设置了onTouchListener事件和onClickListener事件时,onTouchListener在ACTION_UP事件时必须返回false,onClickListener事件才会被执行。

4)onLongClickListener事件是在onTouchEvent中的ACTION_DOWN事件中被执行的,同时设置了onTouchListener事件和onLongClickListener事件时,在onTouchListener在ACTION_DOWN事件时必须返回false,onLongClickListener事件才会被执行。并且onLongClickListener事件一定得长按的时间大于长按事件固定的事件才会被执行。默认是400毫秒,但是实际的还得根据应用场景来。

/**
 * Defines the default duration in milliseconds before a press turns into
 * a long press
 * @hide
 */
public static final int DEFAULT_LONG_PRESS_TIMEOUT = 400;

onLongClickListener事件监听有一个boolean的返回值,看看返回值对其他事件的影响

if (performLongClick(mX, mY)) {
    mHasPerformedLongPress = true;
}

由上代码得知,onLongClickListener的返回值最后会影响mHasPerformedLongPress的值,返回true的时候,mHasPerformedLongPress = true; 再看看mHasPerformedLongPress对事件的影响,最后找到在ACTION_UP事件执行onClickListener事件之前有判断。mHasPerformedLongPress为true时,onClickListener事件不会执行

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();
        }
    }
}

5)onLongClickListener事件和onClickListener事件同时存在时,若是onLongClickListener事件已经执行了,onLongClickListener返回的结果是true时,onClickListener事件不执行;onLongClickListener返回的结果是false时,onClickListener事件会在ACTION_UP事件中执行。

6)onTouchEvent正常情况下,若是没有设置onClickListener事件和onLongClickListener事件,其返回的值是false,就是子View不消费事件,把事件给回到父View执行。在只设置onTouchListener事件时要注意了,不要返回false,否则后面的事件都处理不了了。

nClickListener事件同时存在时,若是onLongClickListener事件已经执行了,onLongClickListener返回的结果是true时,onClickListener事件不执行;onLongClickListener返回的结果是false时,onClickListener事件会在ACTION_UP事件中执行。

6)onTouchEvent正常情况下,若是没有设置onClickListener事件和onLongClickListener事件,其返回的值是false,就是子View不消费事件,把事件给回到父View执行。在只设置onTouchListener事件时要注意了,不要返回false,否则后面的事件都处理不了了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

szzyjsxyzwy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值