写给小白的android 事件分发机制

2023-11-15

介绍

当我们手指在屏幕上进行操作时,系统根据我们的操作进行反应,系统为了更好的记录我们的操作,将我们的手指的所有操作分为三类操作,统一封装在MotionEvent类中。

把大象装进冰箱只要三部:1.打开冰箱 2.把大象扔进去 3.关闭冰箱

操作分类只有三类: 1.手指按下屏幕 2. 按下之后在屏幕各种滑动 3. 手指从屏幕离开

将我们日常生活中所能进行的所有操作进行抽象化,执行抽象成两种结果
1. 手指按下屏幕—–>手指离开屏幕 (相当于点击点击,没有任何滑动)
2. 手指按下屏幕—–>无数个手指滑动——>手指离开屏幕

如果滑动1cm是一个滑动,那么滑动10cm就是十个滑动,这也是为什么 抽象结果2中是 无数个滑动

记录手指操作

上面说到过我们的所有手指操作都被记录在MotionEvent这个类中,除了记录我们是按下屏幕还是滑动屏幕还是离开屏幕,还记录了
触摸点坐标 ——-通过getX()和getRawX() 可以活动手指的触摸坐标

重要函数介绍

  1. dispatchTouchEvent(MotionEvent event)
    如果一个View能够接收到我们的触摸事件,那么这个方法就会被执行
  2. onInterceptTouchEvent(MotionEvent event)
    是否拦截这个触摸事件(ViewGroup有这个函数,单一View没有)
  3. onTouchEvent(MotionEvent event)
    对系统记录的触摸实践进行处理的逻辑写在这里面

例子

MyLayout ——-最外层的布局 继承自LinearLayout
MyLayoutB ——-第二次布局 在最外层布局的包裹内,继承自LinearLayout
MyView ——-一个具有点击能力的控件继承自Button
这里写图片描述

布局如下
这里写图片描述

重写MyLayout和MyLayoutB 的三个函数

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d("TAG","MyLayoutB"+"------dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d("TAG","MyLayoutB"+"------onInterceptTouchEvent");
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("TAG","MyLayoutB"+"------onTouchEvent");
        return super.onTouchEvent(event);
    }

对于MyView只用重写两个函数

 @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d("TAG","MyView----dispatchTouchEvent");
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("TAG","MyView----onTouchEvent");
        return super.onTouchEvent(event);
    }

接下来我们点击一下MyView看一下Log输出
这里写图片描述

从Log 可以看出 最先处理我们触摸的最外层的控件,从MyLayout—-MyLayoutB—-MyView。
但是呢,对于每个ViewGroup中又都是先调用dispatchTouchEvent,然后调用onInterceptTouchEvent
对于每个单一View而言是 先调用dispatchTouchEvent然后调用onTouchEvent

总结:
1. 对于ViewGroup(拥有子控件的ViewGroup)他们只用考虑,自己是否接收这个触摸事件(dispatchTouchEvent)和自己是否要拦截这个触摸事件,不让自己的子控件接触这个触摸事件(onInterceptTouchEvent)
2. 对于单一View(也就是没有子控件的View)他们只能考虑,自己接收接收这个触摸事件(dispatchTouchEvent),自己是否要处理这个触摸事件(onTouchEvent)
3. 从Log可以看出从第七行开始Log把上面输出的Log又输出的一遍,也就是流程走了两遍。这是因为按下屏幕是一个触摸事件,离开屏幕又是一个触摸事件,所以Log打印了两遍

onInterceptTouchEvent()返回值探究

我们再看onInterceptTouchEvent这个函数,这个函数拥有一个boolean的返回值。
如果返回true :这个控件要拦截这个触摸事件,不让子控件获得触摸事件
如果返回false:把这个触摸事件传给子控件,让他先进行处理

我们现在就让MyLayoutB 拦截我们的触摸事件,看看MyView能不能进行对触摸事件的响应(能不能打印出MyView的Log)

将MyLayoutB的onInterceptTouchEvent函数进行修改,然后进行点击

 @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d("TAG","MyLayoutB"+"------onInterceptTouchEvent");
//        return super.onInterceptTouchEvent(ev);
        return true;
    }

MyLayoutB拦截触摸事件

可以看到这里完全没有MyView的事了,也就是MyView没有收到点击事件,即使我们是在MyView上进行点击

总结:
如果onInterceptTouchEvent如果返回了true就代表当前控件要拦截触摸事件,那么这个控件的所有的子控件都没有机会对当前的触摸事件进行响应

dispatchTouchEvent()返回值探究

dispatchTouchEvent()当控件一接收到触摸事件,这个方法就会被调用
这里比较麻烦,我们先上结论
这里写图片描述

验证子View的dispatchTV返回ture,子控件处理触摸事件

  1. 将上面对MyLayoutView 的onInterceptTouchEvent方法返回子改回false
  2. 对MyView进行如下修改
@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d("TAG","MyView----dispatchTouchEvent");
        //super不能省略,因为如果该控件具有点击能力便会调用onTouchEvent函数
        //否则直接返回false
        super.dispatchTouchEvent(event);
        return true;
    }

结果如下这仅仅只是按下时的Log

可以看到这里值调用了MyView.onTouchEvent,并没有调用MyLayout、MyLayoutB的OnTouchEvent方法,证明了上图右下角的结论:当child.dispatchTouchEvent返回true是事件传递结束。下面为了更加深入的证实这个结论,我们将阅读源码

源码验证dispatchTouchEvent

我们最上面的结论得知
1.触摸事件是从顶层View传递到底层View
2.当触摸时间传递到一个View时,就会调用当前View的dispatchTouchEvent方法。
所以在最底层的视图是单一View,单一View的父级视图大多都是ViewGroup,所以这里我们查看ViewGroup的dispatchTouchEvent的源码

public boolean dispatchTouchEvent(MotionEvent ev) {  

    ...

/*
disallowIntercept  子View可以通过调用requestDisallowInterceptTouchEvent进行赋值
onInterceptTouchEvent(ev)  返回值默认为false

所以如果我们没有对onInterceptTouchEvent(ev)为false也就是不拦截事件,就会遍历当前控件的所有子视图,并根据点击位置判断点击的是哪一个子视图,然后调用被点击的子视图的dispatchTouchEvent

 */

        if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
            ev.setAction(MotionEvent.ACTION_DOWN);  
            final int scrolledXInt = (int) scrolledXFloat;  
            final int scrolledYInt = (int) scrolledYFloat;  
            final View[] children = mChildren;  
            final int count = mChildrenCount;  

          //通过for循环,遍历了当前ViewGroup下的所有子View
            for (int i = count - 1; i >= 0; i--) {  
                final View child = children[i];  
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                        || child.getAnimation() != null) {  
                    child.getHitRect(frame);  

                    //判断当前遍历的View是不是正在点击的View
                    //如果是,则进入条件判断内部
                    if (frame.contains(scrolledXInt, scrolledYInt)) {  
                        final float xc = scrolledXFloat - child.mLeft;  
                        final float yc = scrolledYFloat - child.mTop;  
                        ev.setLocation(xc, yc);  
                        child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  

                    //条件判断的内部调用了该View的dispatchTouchEvent()方法

                        if (child.dispatchTouchEvent(ev))  { 

        //调用子View的dispatchTouchEvent返回值如果为true那么当前控件的dispatchTouchEvent就会返回true,并且不执行任何操作
                            return true;  
                        }  
                    }  
                }  
            }  
        }  
    }  
    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
            (action == MotionEvent.ACTION_CANCEL);  
    if (isUpOrCancel) {  
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
    }  
    final View target = mMotionTarget;  

/*
一下两种情况会执行下面的代码
1. onInterceotTouchEvent 返回值为true
2.  onInterceotTouchEvent 返回值为false,但是子视图的dispatchTouchEvent返回值为false
如果是上面两种情况就会调用ViewGroup的父类View的dispatchTouchEvent(),从而调用当前控件的onTouch()、onTouchEvent()
*/
    if (target == null) {  
        ev.setLocation(xf, yf);  
        if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
            ev.setAction(MotionEvent.ACTION_CANCEL);  
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        }  
        return super.dispatchTouchEvent(ev);  
    }

验证单一视图View的dispatchTouchEvent

因为View的dispatchTouchEvent源码比较简单,所以这里以源码进行解释

public boolean dispatchTouchEvent(MotionEvent event) {  
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
            mOnTouchListener.onTouch(this, event)) {  
        return true;  
    }  
    return onTouchEvent(event);  
}

1.如果我们对当前的控件设置了Touch事件监听,那么mOnTouchListener != null就成立为true
2. (mViewFlags & ENABLED_MASK) == ENABLED 一般都为true
3.mOnTouchListener.onTouch(this, event) 如果监听器的onTouch方法返回true的话,View的dispatchTouchEvent就返回true然后执行结束。
*如果上面是哪个条件有一个不满足就会执行当前View自身的onTouchEvent方法

结论

将总结论用一个伪代码来表示就是

public boolean dispatchTouchEvent(MontionEvent){
    boolean consume = false ;
    if(onInterceptTouchEvent(ev)){
        consume = onTouchEvent(ev);
    }else {
        consume = child.dispatchTouchEvent(ev);
    }

    return consume ;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

写给小白的android 事件分发机制 的相关文章

  • 如何快速自动发送FCM或APNS消息?

    我正在开发一项后端服务 通过 FCM 或 APNS 向移动应用程序发送推送通知 我想创建一个可以在一分钟内运行的自动化测试 并验证服务器是否可以成功发送通知 请注意 我不一定需要检查通知是否已送达 只需检查 FCM 或 APNS 是否已成功
  • 在 ViewPager Fragments 中使用 Master/Detail 模板(下载链接)

    工作代码 https github com lukeallison ViewPagerMasterDetail https github com lukeallison ViewPagerMasterDetail Android 主 详细流
  • Android 30+ 中的视频捕获意图 - 只有所有者才能与待处理项目交互

    我正在尝试在我的应用程序上捕获视频 它可以在 android API 30 以下运行 但不能在 30 以上运行 似乎在 sdk 30 之后 android 不允许完全读取外部存储 作用域存储 我目前遇到这个错误 java lang Ille
  • java.lang.NoClassDefFoundError:org.apache.batik.dom.svg.SVGDOMImplementation

    我在链接到我的 Android LibGDX 项目的 Apache Batik 库时遇到了奇怪的问题 但让我们从头开始 在 IntelliJ Idea 中我有一个项目 其中包含三个模块 Main Android 和 Desktop 我强调的
  • 使用 Android 发送 HTTP Post 请求

    我一直在尝试从 SO 和其他网站上的大量示例中学习 但我无法弄清楚为什么我编写的示例不起作用 我正在构建一个小型概念验证应用程序 它可以识别语音并将其 文本 作为 POST 请求发送到 node js 服务器 我已确认语音识别有效 并且服务
  • 无法访问 com.google.android.gms.internal.zzbfm 的 zzbfm 类文件未找到

    我正在将我的 Android 应用程序项目从GCM to FCM 为此 我使用 Android Studio 中的 Firebase 助手工具 并遵循 Google 开发人员指南中的说明 一切都很顺利 并将我的应用程序代码更改为FCM根据助
  • 是否有 ADB 命令来检查媒体是否正在播放

    我想使用 ADB 命令检查根植于终端的外部设备中是否正在播放音频 视频 我无法找到任何 ADB 命令 如果有 我尝试过 adb shell dumpsys media player 我想要一个命令来指定视频是否正在运行 您可以使用以下命令查
  • 原色(有时)变得透明

    我正在使用最新的 SDK 版本 API 21 和支持库 21 0 2 进行开发 并且在尝试实施新的材料设计指南时遇到了麻烦 材料设计说我需要有我的primary color and my accent color并将它们应用到我的应用程序上
  • 你的CPU不支持NX

    我刚刚下载了 android studio 但是我遇到了一个问题 当我运行它时 它说你的 cpu 不支持 NX 我应该怎么办 NX 或实际上是 NX 处理器位 是处理器的一项功能 有助于保护您的 PC 免受恶意软件的攻击 当此功能未启用并且
  • 如何使用 IF 检查 TextView 可见性

    我有一个 onCheckedChangeListener 来根据选择的单选按钮显示文本视图 我有 1 个疑问和 1 个难题 想知道是否有人可以帮助我 问题 您能否将单选组默认检查值设置为 否 单选按钮 以便一开始就不会检查任何内容 问题 如
  • Ubuntu 16.04 - Genymotion:找不到 /dev/hw_random

    I install Genymotion on the Ubuntu 16 04 64Bit I created a virtual emulator for Android 6 0 then I run this emulator but
  • 在 SQLite 中搜索时排除 HTML 标签和一些 UNICODE 字符

    更新 4 我已经成功运行了firstchar例如 但现在的问题是使用regex 即使包含头文件 它也无法识别regex操作员 有什么线索可以解决这个问题吗 更新 2 我已经编译了sqlite3我的项目中的库 我现在正在寻找任何人帮助我为我的
  • 如何发布Android .aar源以使Android Studio自动找到它们?

    我正在将库发布到内部 Sonatype Nexus 存储库 Android Studio 有一个功能 可以自动查找通过 gradle 引用的库的正确源 我将 aar 的源代码作为单独的 jar 发布到 Nexus 但 Android Stu
  • 如何使用InputConnectionWrapper?

    我有一个EditText 现在我想获取用户对此所做的所有更改EditText并在手动将它们插入之前使用它们EditText 我不希望用户直接更改中的文本EditText 这只能由我的代码完成 例如通过使用replace or setText
  • Android Studio - Windows 7 上的 Android SDK 问题

    我对 Google i o 2013 上发布的最新开发工具 Android Studio 有疑问 我已经成功安装了该程序并且能够正常启动 我可以导入现有项目并对其进行编辑 但是 当我尝试单击 SDK 管理器图标或 AVD 管理器图标时 或者
  • Android向menuItem添加子菜单,addSubMenu()在哪里?

    我想根据我的参数以编程方式将 OptionsMenu 内的子菜单添加到 menuItem 中 我检查了android sdk中的 MenuItem 没有addSubMenu 方法 尽管你可以找到 hasSubMenu 和 getSubMen
  • 如何根据 gradle 风格设置变量

    我想传递一个变量test我为每种风格设置了不同的值作为 NDK 的定义 但出于某种原因 他总是忽略了最后味道的价值 这是 build gradle apply plugin com android library def test andr
  • 一次显示两条Toast消息?

    我希望在一个位置显示一条 Toast 消息 并在另一位置同时显示另一条 Toast 消息 多个 Toast 消息似乎总是按顺序排队和显示 是否可以同时显示两条消息 是否有一种解决方法至少可以提供这种外观并且不涉及扰乱活动布局 Edit 看来
  • android sdk 的位置尚未在 Windows 操作系统的首选项中设置

    在 Eclipse 上 我转到 windows gt Android SDK 和 AVD Manager 然后弹出此消息 Android sdk 的位置尚未在首选项中设置 进入首选项 在侧边栏找到 Android 然后会出现一个 SDK 位
  • 强制 Listview 不重复使用视图(复选框)

    我做了一个定制Listview 没有覆盖getView 方法 Listview 中的每个项目都具有以下布局 联系布局 xml

随机推荐