Android 关于View事件分发(onTouch、onTouchEvent、onClick、onLongClick的关系及原理)(一)

一、MotionEvent——手指触摸屏幕时产生的事件

事件含义
ACTION_DOWN手指初次碰到屏幕时触发
ACTION_MOVE手指在屏幕上滑动时触发(ps:会多次触发,看源码时同一块代码应该看多次去理解)
ACTION_UP手指离开屏幕时触发
ACTION_CANCEL事件被上层拦截时触发

  关于ACTION_MOVE事件,手指在屏幕上滑动时会触发多次,对于这个点,看源码时同一块代码应该看多次结合去理解。

  关于ACTION_CANCEL,这个事件并不是由用户手指触发的,而是在事件分发过程中,MOVE事件和UP事件被上层拦截而产生的。(关于ACTION_CANCEL是如何产生的,又有什么作用,需要在后面了解事件分发流程及源码之后~

二、View的事件处理

  当手指点击屏幕时,一般来说,事件都会经过Activity,然后由Activity往下传递到ViewGroup,然后ViewGroup再分发给它的子view,

Activity——ViewGroup1——ViewGroup2——....—— View

我们通常会对一个View做事件监听,那么其中有什么机制呢?

【View的onTouch、onTouchEvent、onClick、onLongClick之间的关系】

事件优先级: onTouch  > onLongClick > onClick

当事件一层层分发下来时,最后会分发给目标View,执行目标View的dispatchTouchEvent方法(ps:View的dispatchTouchEvent方法 与ViewGroup中的是不同的)

View的dispatchTouchEvent方法

    public boolean dispatchTouchEvent(MotionEvent event) {
        // TODO: 2023/7/22 看主要代码,无关省略

        // ... 省略

        // 【1】result ,dispatchTouchEvent最后的返回值,true代表消费,false代表未消费
        boolean result = false;

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        // ... 省略
        
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement

            // 【2】if判断
            // && 短路与, 当前面条件成立时,不会执行后面的条件
            // 条件1 li != null —— 当View有设置事件监听时,mListenerInfo不为空
            // 条件2 li.mOnTouchListener != null —— 当view设置了setOnTouchListener时li.mOnTouchListener不为空
            // 条件3 (mViewFlags & ENABLED_MASK) == ENABLED —— 当前点击的控件是否为 enable
            // 条件4 li.mOnTouchListener.onTouch(this, event) —— view设置的touch监听事件回调
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }


            // 【3】当result为false,才会执行onTouchEvent
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        // ... 省略
        return result;
    }

  (1)由以上代码分析可知,一般情况下,若要事件被消费,即result 为ture,要么onTouch返回true,要么onTouchEvent返回true。

  (2)当我们对View设置了setOnTouchListener监听,onTouch方法返回true时,那么当MotionEvent事件分发到此处时,代码【3】处 if判断中,reslut为true,!result条件不成立,那么onTouchEvent不会执行,setOnClickListener无效(ps:onClick方法的调用在onTouchEvent里面)。同理,当我们对VIew设置了 setEnable(false)时,代码【2】处(mViewFlags & ENABLED_MASK) == ENABLED,li.mOnTouchListener.onTouch(this, event)不会执行,所以setOnTouchListener监听无效。

  由此可得出,onTouch优先级高于onTouchEvent,接下来看看onTouchEvent中做了什么。

View的onTouchEvent方法

    public boolean onTouchEvent(MotionEvent event) {
        // ...省略

        // 重点
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            // 【1】处理Up、Down、Move、Cancel事件
            switch (action) {
                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        handleTooltipUp();
                    }
                    // 【2】clickable,可通过setClickable方法控制,为false时,跳出
                    if (!clickable) {
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                      // ...省略

                        // 【3】mHasPerformedLongPress跟长按有关,默认为false,只有onLongClick回调方法返回true时,该值才为true

                        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();
                                }
                                    // TODO: 2023/7/22 【4】onCLick在这里


                                if (!post(mPerformClick)) {

                                    performClickInternal();
                                }
                            }
                        }

                    break;

                case MotionEvent.ACTION_DOWN:
                    // ...省略
mHasPerformedLongPress = false;

                   // 【5】开始长按处理,包括计时等操作
                    if (!clickable) {
                        checkForLongClick(
                                ViewConfiguration.getLongPressTimeout(),
                                x,
                                y,
                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                        break;
                    }
 // ...省略

                    break;

                case MotionEvent.ACTION_CANCEL:
                    // ...省略

                    break;

                case MotionEvent.ACTION_MOVE:
                    // ...省略
                    break;
            }

            return true;
        }

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

            // 顺便提一嘴,  click的播放音效,public
            playSoundEffect(SoundEffectConstants.CLICK);

            // onCLick找到了 并且result = true, onClick没有返回值,一旦执行到onClick,系统认为事件已经处理了

            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }

    (1)onClick点击事件是在UP事件中,也就是手指离开屏幕时处理的,其完整调用流程是dispatchTouchEvent ——onTouchEvent ——performClickInternal——performClick——li.mOnClickListener.onClick(this)

     (2)若对VIew设置了setClickable(false), 代码【2】处可知,onClick监听将会无效,但切记setClickable(false)要放在setOnClickListener之前,因为该方法会重置clickable参数

public void setOnClickListener(@Nullable OnClickListener l) {
    if (!isClickable()) {
        setClickable(true); // 重置
    }
    getListenerInfo().mOnClickListener = l;
}

 (3)onClick方法没有返回值, onLongClick方法由返回值,当onLongClick方法返回true时, mHasPerformedLongPress会置为true,看代码【3】处的判断,!mHasPerformedLongPress不成立,后面的代码不会执行,onClick无效。

    

(4)对于长按监听的逻辑, 简单说说(ps:本人还没有细看),长按事件是手指长按0.5s以上会触发,不需要松开。看代码【5】,是在onTouchEvent——down事件——checkForLongClick方法开始的长按事件处理,当手指松开屏幕时(UP事件),代码【3】处的if判断中,会移除这个长按事件,若此时长按事件还没有触发,则再也不会触发。

checkForLongClick方法

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) {
                
                // 【1】长按任务
                mPendingCheckForLongPress = new CheckForLongPress();
            }
            mPendingCheckForLongPress.setAnchor(x, y);
            mPendingCheckForLongPress.rememberWindowAttachCount();
            mPendingCheckForLongPress.rememberPressedState();
            mPendingCheckForLongPress.setClassification(classification);
            
            // 【2】延迟执行长按任务
            postDelayed(mPendingCheckForLongPress, delay);
        }
    }

代码【1】处里的mPendingCheckForLongPress 又是什么呢,来看看

CheckForLongPress对象

    private final class CheckForLongPress implements Runnable {
// ...省略其他成员及方法

       @Override
        public void run() {
            if ((mOriginalPressedState == isPressed()) && (mParent != null)
                    && mOriginalWindowAttachCount == mWindowAttachCount) {
                recordGestureClassification(mClassification);
                
                // performLongClick, 是不是很熟悉,跟前面的performClick一样
                if (performLongClick(mX, mY)) {
                    
                    // mHasPerformedLongPress唯一置为true的地方,代表事件由longClick消费了,则优先级在其之后的Click不会收到事件
                    mHasPerformedLongPress = true;
                }
            }
        }

}

在按下屏幕时,就开始长按事件的计时任务了,当长按不松开一段时间后,便会执行该任务,回调onLongClick方法。

到此, View的事件分析先告一段落。再来看看我之前有的一些疑惑,

三、自问自答加深一下理解~

1、当对按钮设置了setOnTouchListener监听之后,点击该按钮(松开),onTouch方法调用几次?

button.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
// 打印一下日志
        return false;
    }
});

点击按钮,会产生DOWN和UP两个事件,所以onTouch会调用两次

2、 当对view设置了setOnTouchListener监听之后,按住该view滑动,onTouch方法调用几次?

当在view上滑动时,会调用多次onTouch,因为会一直产生MOVE事件

3、设置了setOnTouchListener之后,为什么setOnClickListener的onClick没有反应了?

当onTouch方法返回true时,认为该事件已经处理了 ,就不会有onCLick的调用了,修改onTouch返回false即可

4、同时设置点击监听和长按监听,当用户长按时只响应长按事件应该怎么做?

 onLongClick返回true即可

5、setClickable(false)失效的原因?

setClickable应该在setOnClickListener之后调用,因为setOnClickListener方法会重置该参数为true(setLongClickable同理)

6、onTouch、onClick、onLongClick的优先级?

优先级: onTouch  > onLongClick > onClick

四、后面学习整理一下ViewGroup的处理机制

Android 关于View事件分发(ViewGroup的事件分发流程解析,结合Down事件、Move事件各种情况下的分发流程加深对事件分发的理解)(二)~_半摆的博客-CSDN博客

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值