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

这篇具有很好参考价值的文章主要介绍了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博客文章来源地址https://www.toymoban.com/news/detail-819807.html

到了这里,关于Android 关于View事件分发(onTouch、onTouchEvent、onClick、onLongClick的关系及原理)(一)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android 事件分发

    本篇文章主要简单介绍下Android中的事件分发,和大家一起学习,进步,有问题也希望大家及时指证修改. 1: onClick和OnTouch 首先我们在单独的activity中添加个按钮button.增加点击事件setOnClickListener: 接着添加OnTouch: 我们执行下点击事件.可以看到输出如下: 2024-04-09 20:54:11.219 17770-17770/?

    2024年04月11日
    浏览(49)
  • Android触摸事件分发机制(一)

    本文主要分享事件分发中的基本概念。 介绍负责参与分发事件的主要方法。 从这些方法的核心逻辑中,总结事件分发的规律。 被分发的对象是那些?被分发的对象是用户触摸屏幕而产生的点击事件,事件主要包括:按 下、滑动、抬起与取消。这些事件被封装成 MotionEvent 对

    2024年02月15日
    浏览(33)
  • Android事件分发-基础原理和场景分析

    作者:京东零售 郭旭锋 和其他平台类似,Android 中 View 的布局是一个树形结构,各个 ViewGroup 和 View 是按树形结构嵌套布局的,从而会出现用户触摸的位置坐标可能会落在多个 View 的范围内,这样就不知道哪个 View 来响应这个事件,为了解决这一问题,就出现了事件分发机制

    2023年04月21日
    浏览(41)
  • 带你深入了解Android的事件分发机制

    Android的事件分发机制是指在Android系统中,如何将用户的触摸事件、按键事件等传递给正确的View进行处理的一套机制。它是Android应用程序中实现交互的重要部分,确保用户的操作能够被正确地捕获和处理。 Android的事件分发机制涉及到以下几个核心概念:事件源、事件分发、

    2024年02月16日
    浏览(33)
  • Android事件分发机制五:面试官你坐啊

    学过事件分发吗,聊聊什么是事件分发 事件分发是将屏幕触控信息分发给控件树的一个套机制。 当我们触摸屏幕时,会产生一些列的MotionEvent事件对象,经过控件树的管理者ViewRootImpl,调用view的dispatchPointerEvnet方法进行分发。 那主要的分发流程是什么: 在程序的主界面情况

    2024年04月15日
    浏览(43)
  • 高级UI之Android事件分发机制原理及源码分析

    在 Android 中, 事件分发机制是一块很重要的知识点, 掌握这个机制能帮你在平时的开发中解决掉很多的 View 事件冲突问题,这个问题也是面试中问的比较多的一个问题了, 本篇就来总结下这个知识点。 Android 中页面上的 View 是以树型结构显示的,View 会重叠在一起,当我们

    2024年02月08日
    浏览(41)
  • View 的四种 OnClick 方式

    嗨喽,大家好!今天呢,我跟大家聊一聊Android 的View 的点击事件onClick 。额,有点拗口(^_^) 。 看过我的文章的人可能会好奇,你怎么写Android的文章了啊?说起这啊,就是我的血泪史了,此处省略一万字.................... 废话不多说,让我们代码走起,风里来,雨里去,唯有代

    2023年04月15日
    浏览(42)
  • [Android 13]Input系列--触摸事件在应用进程的分发和处理

    hongxi.zhu 2023-7-21 Android 13 前面我们已经梳理了input事件在native层的传递,这一篇我们接着探索input事件在应用中的传递与处理,我们将按键事件和触摸事件分开梳理,这一篇就只涉及触摸事件。 一、事件的接收 从前面的篇幅我们知道,framework native层 InputDispatcher 向应用通过s

    2024年02月15日
    浏览(37)
  • Flutter中为控件添加交互,带你一起探究Android事件分发机制

    ), ); } } 代码运行效果如图: 2.父widget管理widget的状态 对于父widget来说,管理状态并告诉其子widget何时更新通常是最有意义的。 例如,IconButton允许您将图标视为可点按的按钮。 IconButton是一个无状态的小部件,因为我们认为父widget需要知道该按钮是否被点击来采取相应的处理

    2024年04月11日
    浏览(42)
  • 【Android】Touch 事件分发逻辑梳理和避坑逻辑(上层设置了setOnTouchListener的事件监听但是没有起作用的原因)

    在项目中发现我明明在最上层的 activity 中的一个 DrawerLayout 对象设置了如下代码: 该代码的作用的在点击的时候显示一下 TopBar 一个自定义的UI组件。 但是发现点击超级白板(你可以理解为一个画板组件)部分上述代码就没有触发。这是怎么回事呢? 首先针对Touch事件的分发机

    2024年02月11日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包