Android 事件机制探讨(1)

news2024/11/28 23:06:08

事件的传递主要有三个方法:dispatchTouchEvent(事件分发)、onInterceptTouchEvent(事件拦截)、onTouchEvent(事件消费)。如下图:

  • 仔细看的话,图分为3层,从上往下依次是Activity、ViewGroup、View
  • 事件从左上角那个白色箭头开始,由Activity的dispatchTouchEvent做分发
  • 箭头的上面字代表方法返回值,(return true、return false、return super.xxxxx(),super 的意思是调用父类实现。
  • dispatchTouchEvent和 onTouchEvent的框里有个【true---->消费】的字,表示的意思是如果方法返回true,那么代表事件就此消费,不会继续往别的地方传了,事件终止。
  •  事件分成好几种类型,我们常用的就三种,从手指按下移动到抬起依次为:ACTION_DOWN(按下)、ACTION_MOVE(移动)、ACTION_UP(抬起)。
  • 之前图中的Activity 的dispatchTouchEvent 有误(图已修复),只有return super.dispatchTouchEvent(ev) 才是往下走,返回true 或者 false 事件就被消费了(终止传递)

事件的传递过程

        事件的传递在我们手指按下(ACTION_DOWN)的瞬间发生了,如果手指有移动会触发若干个移动事件(ACTION_MOVE),当你手指抬起时会触发ACTION_UP事件,这样为一个事件序列。我们先来看看单个事件时怎么传递的。

Activity代码如下: 

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("MainActivity","onTouchEvent:");
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: //按下的动作
                Log.d("MainActivity", "View      onTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE: //滑动的动作
                Log.d("MainActivity", "View      onTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP: //离开的动作
                Log.d("MainActivity", "View      onTouchEvent ACTION_UP");
                break;
        }
        return super.onTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d("MainActivity","dispatchTouchEvent:");
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: //按下的动作
                Log.d("MainActivity", "View      dispatchTouchEvent ACTION_DOWN");
                break;
                case MotionEvent.ACTION_MOVE: //滑动的动作
                    Log.d("MainActivity", "View      dispatchTouchEvent ACTION_MOVE");
                    break;
                    case MotionEvent.ACTION_UP: //离开的动作
                        Log.d("MainActivity", "View      dispatchTouchEvent ACTION_UP");
                        break;
        }
        return super.dispatchTouchEvent(event);
    }

布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <androidx.drawerlayout.widget.DrawerLayout xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        tools:context=".ui.MainActivity">

        <include
            android:id="@+id/include"
            layout="@layout/app_bar_main"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

           ........

        <!--app:headerLayout="@layout/nav_header_main"-->
        <!--app:menu="@menu/activity_main_drawer"-->
    </androidx.drawerlayout.widget.DrawerLayout>
</layout>

app_bar_main.xml 布局如下:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <LinearLayout xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        android:orientation="vertical"
        tools:context="com.example.jingbin.cloudreader.ui.MainActivity">


        <!--状态栏-->
        <View
            android:id="@+id/view_status"
            android:layout_width="match_parent"
            android:layout_height="24dp"
            android:background="@color/colorHomeToolBar"
            android:visibility="gone" />

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="@color/colorHomeToolBar"
            app:contentInsetStart="0.0dp"
            app:popupTheme="@style/ThemeOverlay.AppCompat.ActionBar"
            app:theme="@style/ToolbarStyle">

            <FrameLayout
                android:id="@+id/ll_title_menu"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:background="?attr/selectableItemBackgroundBorderless"
                android:paddingLeft="15dp"
                android:paddingRight="15dp">

                <ImageView
                    android:id="@+id/iv_title_menu"
                    android:layout_width="23dp"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:src="@drawable/titlebar_menu" />
            </FrameLayout>

            <HorizontalScrollView
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_gravity="center">

                <LinearLayout
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:background="?attr/selectableItemBackground"
                    app:theme="@style/AppTheme">

                    <ImageView
                        android:id="@+id/iv_title_one"
                        android:layout_width="55dp"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center"
                        android:background="?attr/selectableItemBackgroundBorderless"
                        android:src="@drawable/titlebar_music" />

                    <ImageView
                        android:id="@+id/iv_title_two"
                        android:layout_width="55dp"
                        android:layout_height="match_parent"
                        android:background="?attr/selectableItemBackgroundBorderless"
                        android:src="@drawable/titlebar_disco" />

                    <ImageView
                        android:id="@+id/iv_title_three"
                        android:layout_width="55dp"
                        android:layout_height="match_parent"
                        android:background="?attr/selectableItemBackgroundBorderless"
                        android:src="@drawable/titlebar_friends" />

                </LinearLayout>

            </HorizontalScrollView>

        </androidx.appcompat.widget.Toolbar>

        <com.example.jingbin.cloudreader.view.ViewPagerFixed
            android:id="@+id/vp_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:descendantFocusability="blocksDescendants" />

    </LinearLayout>
</layout>

控件 ViewPagerFixed 中代码如下:

    override fun onTouchEvent(ev: MotionEvent): Boolean {
        try {
            Log.d("ViewPagerFixed","onTouchEvent:")
            when (ev?.action) {
                MotionEvent.ACTION_DOWN -> Log.d("ViewPagerFixed", "View      onTouchEvent ACTION_DOWN")
                MotionEvent.ACTION_MOVE -> Log.d("ViewPagerFixed", "View      onTouchEvent ACTION_MOVE")
                MotionEvent.ACTION_UP -> Log.d("ViewPagerFixed", "View      onTouchEvent ACTION_UP")
            }
            return super.onTouchEvent(ev)
        } catch (ex: IllegalArgumentException) {
            ex.printStackTrace()
        }
        return false
    }

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        try {
            Log.d("ViewPagerFixed","onInterceptTouchEvent:")
            when (ev?.action) {
                MotionEvent.ACTION_DOWN -> Log.d("ViewPagerFixed", "View      onInterceptTouchEvent ACTION_DOWN")
                MotionEvent.ACTION_MOVE -> Log.d("ViewPagerFixed", "View      onInterceptTouchEvent ACTION_MOVE")
                MotionEvent.ACTION_UP -> Log.d("ViewPagerFixed", "View      onInterceptTouchEvent ACTION_UP")
            }
            return super.onInterceptTouchEvent(ev)
        } catch (ex: IllegalArgumentException) {
            ex.printStackTrace()
        }
        return false
    }


    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        Log.d("ViewPagerFixed","dispatchTouchEvent:")
        when (ev?.action) {
            MotionEvent.ACTION_DOWN -> Log.d("ViewPagerFixed", "View dispatchTouchEvent ACTION_DOWN")
            MotionEvent.ACTION_MOVE -> Log.d("ViewPagerFixed", "View dispatchTouchEvent ACTION_MOVE")
            MotionEvent.ACTION_UP -> Log.d("ViewPagerFixed", "View dispatchTouchEvent ACTION_UP")
        }
        return super.dispatchTouchEvent(ev)
    }

效果图如下:

我们运行app,点击红色状态栏中 剧中的三个白色图标,看到日志如下:

        首先我们知道一次点击,会触发一次ACTION_DOWN、若干个ACTION_MOVE、一次ACTION_UP事件,而一次事件的传递是由上往下传递的,也就是依次通过Activity、ViewGroup、View。按下的瞬间ACTION_DOWN触发,Activity的dispatchTouchEvent(事件分发)会先调用,这个跟我们的第一行的日志不谋而合。

        Activity中的dispatchTouchEvent会调用ViewGroup或者View的dispatchTouchEvent的方法,而当ViewGroup或者View返回false时才会调用本身的onTouchEvent方法。第一行日就容易理解了,是执行Activity的dispatchTouchEvent打印出来的,后面执行了多个ACTION_MOVE,一次ACTION_UP事件,最后Activity中这个事件被onClick2消费了。

这里有个知识点,就是ViewGroup的dispatchTouchEvent方法会调用自身的onInterceptTouchEvent(事件拦截)方法,这一点跟Activity中的有点不一样,因为Activity中并没有事件拦截方法,如果ViewGroup的onInterceptTouchEvent事件拦截方法返回true,那么View中的dispatchTouchEvent方法不会被调用,反而会执行ViewGroup的onTouchEvent方法,那么事件传递结束。

如果返回的是false(默认就是返回false),那么View 中的dispatchTouchEvent方法会被调用,因为View是最底层的控件,事件无法继续再往下传递,只能自身消费,所以dispatchTouchEvent又会调用onTouchEvent方法。在我们这个例子中,点击的是iv_title_two

这里我们会想,onClick 和 onTouchEvent 到底啥关系呢,下一篇我会具体的分析。

我们知道事件的传递是从上往下传递的,那么当事件传递到最底层的View并且该事件没有被消费,又该如何呢?其实上面的日志已经告诉我们了,当最底层的View并没有消费该事件时,该事件会一层层往上抛,接下来会执行ViewGroup的onTouchEvent方法,如果返回true的话,事件传递停止,如果还是一样返回默认值false的话,Activity的onTouchEvent方法会被调用,到此ACTION_DOWN事件的传递结束,这就是一次完整的事件传递过程。

细心同学又会问了,为什么Activity的dispatchTouchEvent被执行了多次呢(日志打印出来的)?

没错,确实是执行了,刚才我们说过了:手指按下移动到抬起,会执行一次ACTION_DOWN(按下)、若干次ACTION_MOVE(移动)和一次ACTION_UP(抬起),被执行了多次是因为执行了一次ACTION_MOVE(一次是偶然的,如果你手指多滑动,会执行多次的)和一次ACTION_UP事件,也就是还有多次完整的事件传递过程,但是我们发现后面这两次跟ACTION_DOWN不一样,只调用两次Activity的dispatchTouchEvent、onTouchEvent方法,这是为什么呢?

因为Android本身的事件传递机制就是这样的,我们把手指按下抬起所发生的事件传递称为一个事件序列,看似3个或3个以上独立的事件组成,其实不然,它们还是有联系的,因为当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发下一个action,什么意思呢?刚才的例子Activity的dispatchTouchEvent的方法中viewGroup或者view.dispatchTouchEvent(ev)返回的是默认值false,接下来ACTION_MOVE、ACTION_UP两个事件并不会触发ViewGroup的dispatchTouchEvent方法(因为你前一个action【ACTION_UDOWN】返回的false),反而是直接执行自身的onTouchEvent的方法。所以打印出来的日志就是这样的。这告诉我们如果一个事件序列的ACTION_DOWN事件你没消费掉,那么该事件序列的ACTION_MOVE、ACTION_UP并不会在被执行了。

接下来我们滑动下面的viewpager,三个tab "玩安卓","广场","问答"的那个

看下日志截图:

viewpager 拦截了 ACTION_UDOWN 事件。

viewpager拦截了 ACTION_MOVE 事件

ACTION_UP事件被消费了

至此一个完整的事件流程结束了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1392185.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring Data JPA 踩过的坑实录

前言 游戏中台一直在使用spring 全家桶&#xff0c; 本文会左右使用Spring Data JPA的坑点记录总结 主要给大家总结介绍了关于使用Spring JPA注意事项及踩过的坑。 案例1&#xff1a; 为什么只调用了 org.springframework.data.repository.CrudRepository#findById(ID id) 却…

HCIA——11计算机网络分层结构——OSI/ISO、TCP/IP

学习目标&#xff1a; 参考模型 计算机网络 1.掌握计算机网络的基本概念、基本原理和基本方法。 2.掌握计算机网络的体系结构和典型网络协议&#xff0c;了解典型网络设备的组成和特点&#xff0c;理解典型网络设备的工作原理。 3.能够运用计算机网络的基本概念、基本原理和基本…

在 ASP.NET Core Web API 中使用异常筛选器捕获和统一处理异常

前言 在 ASP.NET Core Web API 中&#xff0c;异常筛选器&#xff08;Exception Filter&#xff09;是一种用于处理发生在 Web API 控制器或管道中的异常的机制。 异常筛选器可以捕获和处理应用程序中发生的异常&#xff0c;当系统中出现未经处理的异常的时候&#xff0c;异常…

leetocode 15 三数之和

题目 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元组。 示例…

Vant4在Vue3.3中如何按需导入组件和样式

前言 最近我在Vue 3.3的项目中对Vant4做按需导入时&#xff0c;尽管按照Vant4的官方指南进行操作&#xff0c;但样式仍然无法正确加载。经过深入研究和多篇文章的比较&#xff0c;我终于找到了在Vue3中如何正确的按需导入Vant 4组件和样式的方法。由于Vue3.3和Vant4相对较新&am…

python代码练习:链表——分隔链表

参考知识&#xff1a; 什么是链表Optional有什么用 题目&#xff1a; 题目来源&#xff1a;力扣 代码&#xff1a; from typing import Optionalclass ListNode: 链表结点的数据类型 def __init__(self, val0,nextNone):self.val valself.next nextdef convert_to_linked…

Linux环境之Centos安装Docker流程

Linux环境之Centos/Rocky安装Docker流程我们今天详细分享一下&#xff1a;直接采用阿里云镜像安装 docker-ce镜像_docker-ce下载地址_docker-ce安装教程-阿里巴巴开源镜像站 1、进来之后看到 2、先查看是否已经安装了Docker yum list dockerdocker version 3、如果没有安装…

写点东西《什么是网络抓取?》

写点东西《什么是网络抓取&#xff1f;》 什么是网络抓取&#xff1f; 网络抓取合法吗&#xff1f; 什么是网络爬虫&#xff0c;它是如何工作的&#xff1f; 网络爬虫示例 网络抓取工具 结论 您是否曾经想同时比较多个网站上同一件商品的价格&#xff1f;或者自动提取您最喜欢的…

聚焦行业翘楚~2024武汉国际氢能源及燃料电池产业展览会

聚焦行业翘楚~2024武汉国际氢能源及燃料电池产业展览会 2024武汉国际氢能源及燃料电池产业博览会 同期举办&#xff1a;2024世界汽车制造技术暨智能装备博览会 时间&#xff1a;2024.8.14-16 地点&#xff1a;武汉国际博览中心 邀请函 主办单位&#xff1a; 湖北省汽车行…

vscode安装和基本设置

目录 vscode安装和基本设置1.HTML标签2.标签属性3.HTML基本结构4.安装vscode5.安装Live Server插件6.HTML注释7.文档说明8.HTML字符编码9.HTML设置语言10.HTML标准结构 vscode安装和基本设置 1.HTML标签 标签 又称 元素&#xff0c;是HTML的基本组成单位。标签分为&#xff1…

推荐一款低成本半桥驱动器集成电路 SIC631CD-T1-GE3

SIC631CD-T1-GE3 是经过优化的集成功率级解决方案用于同步降压应用&#xff0c;提供大电流、高电压效率高&#xff0c;功率密度高。使电压调节器设计能够提供高达50 A的电流每相持续电流。内部功率MOSFET利用Vishay的最先进的第四代TrenchFET技术行业基准绩效将显著降低开关和传…

Qt单个字符判断

1.相关说明 字符的Unicode编码、单个字符的判断 2.界面绘制 3.相关主要代码 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui;…

Windows 下 PYQT开发环境的搭建:

(1)环境搭建&#xff1a; PYQT 安装包&#xff1a; Anaconda&#xff1a; Anaconda3-2023.09-0-Windows-x86_64 pycharm&#xff1a; pycharm 2019.3 下载包&#xff1a; Anaconda:下载成功 |蟒蛇 (anaconda.com) pycharm: pycharm安装包_pycharm用copilotchat资源-CSD…

如何提高问卷填写率:有效策略与技巧分享

解决了调查问卷制作这个难题&#xff0c;怎么让更多的人填写又是一个让人头大的难题。 那有什么好的方式可以帮助我们尽可能地让更多的人填写问卷额&#xff0c;我整理了以下方法&#xff1a; 1、调查问卷尽可能做的美观一些。 设想一下&#xff0c;如果我们是填写者&#xff…

代码随想录算法训练营第五天 | 242.有效的字母异位词、349.两个数组的交集、202.快乐数、1.两数之和

代码随想录算法训练营第五天 | 242.有效的字母异位词、349.两个数组的交集、202.快乐数、1.两数之和 文章目录 代码随想录算法训练营第五天 | 242.有效的字母异位词、349.两个数组的交集、202.快乐数、1.两数之和1 哈希表理论基础1.1 哈希表的内部实现原理1.2 哈希函数1.3 哈希…

链表练习 Leetcode 61.旋转链表

给你一个链表的头节点 head &#xff0c;旋转链表&#xff0c;将链表每个节点向右移动 k 个位置。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], k 2 输出&#xff1a;[4,5,1,2,3]示例 2&#xff1a; 输入&#xff1a;head [0,1,2], k 4 输出&#xff1a;[2,0,1]…

什么是小红书seo,家居品牌关键词攻略

在如今移动互联网时代&#xff0c;媒介投放已经成为企业进行品牌传播的重要手段之一&#xff0c;这其中就小红书来说&#xff0c;其以其独特的内容方式和用户粘性而受到广大用户的喜爱。今天我们来针对大家都不陌生的seo&#xff0c;来说说什么是小红书seo&#xff0c;家居品牌…

HarmonyOS应用开发者初级认证试题库(鸿蒙)

目录 考试链接&#xff1a; 流程&#xff1a; 选择&#xff1a; 判断&#xff1a; 单选&#xff1a; 多选&#xff1a; 考试链接&#xff1a; 开发者能力认证-职业认证-鸿蒙能力认证-华为开发者学堂 (huawei.com)https://developer.huawei.com/consumer/cn/training/dev-…

Vue 如何把computed里的逻辑提取出来

借用一下百度的ai 项目使用&#xff1a; vue 文件引入 <sidebar-itemv-for"route in routes":key"route.menuCode":item"route":base-path"route.path"click"onColor"/>import { handleroutes } from "./handle…

Ceph分布式存储(1)

目录 一.ceph分布式存储 Ceph架构&#xff08;自上往下&#xff09; OSD的存储引擎&#xff1a; Ceph的存储过程&#xff1a; 二. 基于 ceph-deploy 部署 Ceph 集群 20-40节点上添加3块硬盘&#xff0c;一个网卡&#xff1a; 10节点为admin&#xff0c;20-40为node&…