美文网首页Android开发
源码分析onTouch、onTouchEvent、onClick

源码分析onTouch、onTouchEvent、onClick

作者: 青叶小小 | 来源:发表于2020-12-24 11:33 被阅读0次

在一个Activity中,给一个控件(比如:Button)添加一个点击事件,有两种方式:
// 1. 设置 onClick 事件

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Log.e("chris", "button.onClick");
    }
});

// 2. 设置 onTouch 事件

button.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        Log.e("chris", "button.onTouch = " + MotionEvent.actionToString(event.getAction()) + "[" + event.getX() + "," + event.getY() + "]");
        return false;
    }
});

如果同时添加了这两种事件监听,结果会如何呢?

2017-12-23 16:43:09.539 E/chris: button.onTouch = ACTION_DOWN[108.468994,61.447388]
2017-12-23 16:43:09.571 E/chris: button.onTouch = ACTION_UP[108.468994,61.447388]
2017-12-23 16:43:09.573 E/chris: button.onClick

我们可以看到:

  1. onTouch比onClick优先执行;
  2. 默认情况下,onTouch会执行DOWN、0或多个MOVE、以及UP事件;
  3. UP事件之后才会执行onClick事件;

在上面第2点中提到,默认情况,我们看onTouch是有一个返回值(默认返回false),如果改为返回true呢?

2017-12-23 16:45:29.669 E/chris: button.onTouch = ACTION_DOWN[108.468994,61.447388]
2017-12-23 16:45:29.671 E/chris: button.onTouch = ACTION_UP[108.468994,61.447388]

这时,就只有onTouch事件,没有onClick事件。
因为大家的理解是:返回true,表明onTouch就消费了这些事件,这些事件就不会再后续传递下去。

从源码层面来理解

任何触摸产生的事件,都会从硬件(屏幕)传至Android系统,再传递给Window,然后从根视图开始传递事件,直到该控件,然后调用该控件的事件分发(dispatchTouchEvent),该方法在父类View中可查到:

/**
 * Pass the touch screen motion event down to the target view, or this
 * view if it is the target.
 *
 * @param event The motion event to be dispatched.
 * @return True if the event was handled by the view, false otherwise.
 */
public boolean dispatchTouchEvent(MotionEvent event) {
     ......
    boolean result = false;
    ......
    if (onFilterTouchEventForSecurity(event)) {
        ......
        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;
}

以上代码为事件处理的核心代码:

  1. 如果mOnTouchListener不为null,且该view支持touch,就会进入onTouch方法中(也就是我们注册的listener);
  2. 如果onTouch返回false,才会进入onTouchEvent(onTouch返回true,即 result = true,就不会进入onTouchEvent了);
  3. 后面没有什么重要代码,因此 onClick 一定是在 onTouchEvent 方法中被调用;
onTouchEvent源码分析:
public boolean onTouchEvent(MotionEvent event) {
    ......
    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:
                ......
                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 = requestFocus();
                    }
                    ......
                    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();
                            }
                        }
                    }
                    ......
                }
                ......
                break;
        }
        return true;
    }
    return false;
}

前面的打印日志也表明,在ACTION_UP之后,才会响应 onClick ,因此,我们只需要关注 onTouchEvent 中的 UP 事件。
上面的代码最终就会走到 PerformClick / performClickInternal:

private final class PerformClick implements Runnable {
    @Override
    public void run() {
        performClickInternal();
    }
}
private boolean performClickInternal() {
    ......
    return performClick();
}
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;
    }
    ......
    return result;
}

最终我们看到了我们想要的 onClick !

相关文章

网友评论

    本文标题:源码分析onTouch、onTouchEvent、onClick

    本文链接:https://www.haomeiwen.com/subject/uxqmnktx.html