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 事件分发介绍

    目录 一、目的 二、环境 三、相关概念 3.1 事件分发 四、详细设计 4.1应用布局 4.1.1 应用布局结构 4.1.2 LayoutInspector 4.2 关键View方法 4.2.1 相关View 4.2.2 相关方法 4.2.3 View与方法关系 4.3 事件分发概念图 4.3.1 事件分发类图 4.3.2 事件分发模型图 4.4 Activity组件 4.4.1 Activity-dispatchTouch

    2024年02月03日
    浏览(40)
  • Android触摸事件分发机制(一)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    2024年02月11日
    浏览(37)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包