Android触摸事件传递机制
前言:在Android开发中,经常会遇到触摸事件冲突,比如ViewPager的轮播图跟Fragment的划动事件冲突,或者轮播图跟下拉事件冲突,自定义view的事件处理等,本文章将会详细介绍Activity、View、ViewGroup三者的触摸事件传递机制,传递包括三个阶段:分发、拦截、消费。
本文章将会详细介绍Activity、View、ViewGroup三者的触摸事件传递机制,传递包括三个阶段:分发、拦截、消费。
一.触摸事件的类型
触摸事件对应的是 MotionEvent 类,事件类型主要有三种:
- ACTION_DOWN:用户按下操作,表示一次触摸事件的开始。
- ACTION_MOVE:在按下的情况下,进行移动。轻微的移动都会传递到该事件。
- ACTION_UP:用户手指离开屏幕,表示一次触摸事件的
注 :如果用户仅仅的是点击而已,则只会执行到 ACTION_DOWN 和 ACTION_UP 两个事件,不会执行到 ACTION_MOVE 事件。所以 ACTION_DOWN 和 ACTION_UP 是事件是必须的。
二.触摸事件的传递阶段
1.分发(Dispatch)
在Android系统中所有的触摸事件都是由 dispatchTouchEvent 方法进行分发的。该方法中判断事件是被消费( return true ),还是继续分发给子视图处理( return super.dispatchTouchEvent ),如果当前视图是ViewGroup或者其子类,则会调用 onInterceptTouchEvent 判断是否截拦。
@Override public boolean dispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); }
2.截拦(Intercept)
事件的截拦 InterceptTouchEvent 只存在于ViewGroup及其子类,activity和View是不存在该方法。该方法判断事件是被截拦 ( return true )并交给自身的 OnToucEvent 方法进行消费,还是继续传递给子视图( return super.InterceptTouchEvent 或者 return false )。
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { return super.onInterceptTouchEvent(ev); }
3.消费(Consume)
事件的消费通过 OnTouchEvent 方法判断,是被消费( return true ),还是不处理( return false )并将事件传递给父视图的 OnTouchEvent 方法进行处理。
@Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); }
所有拥有事件传递能力的类:
Activity: 拥有dispatchTouchEvent 、OnTouchEvent
ViewGroup: 拥有dispatchTouchEvent 、OnInterceptTouchEvent 、OnTouchEvent
View:拥有dispatchTouchEvent 、OnTouchEvent
三、View的事件传递机制
3.1 dome
虽然说ViewGroup是View的子类,但是这是说的View指的是除ViewGroup之外的View控件子类,首先定义一个MyTextView继承TextView,打印每次事件的触发以变了解事件传递的流程。
MyTextView 类
public class MyTextView extends TextView { private String tag = "MyTextView"; public MyTextView(Context context) { super(context); } public MyTextView(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_UP: Log.i(tag, "dispatchTouchEvent ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "dispatchTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "dispatchTouchEvent ACTION_DOWN"); break; } return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_UP: Log.i(tag, "onTouchEvent ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "onTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "onTouchEvent ACTION_DOWN"); break; } return super.onTouchEvent(event); } }
定义一个MainActivity来展现这个MyTextView,同时设置点击(onClick)和触摸(onTouch)监听。 MainActivity 类
public class MainActivity extends AppCompatActivity implements View.OnClickListener,View.OnTouchListener{ private MyTextView mMyTextView; private String tag = "MainActiviy"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mMyTextView = findViewById(R.id.text_view); // 点击监听 mMyTextView.setOnClickListener(this); // 触碰监听 mMyTextView.setOnTouchListener(this); } // MyTextView 点击事件 @Override public void onClick(View view) { switch (view.getId()){ case R.id.text_view: Log.i(tag, "MyTextView onClick"); break; } } // MyTextView 触碰事件 @Override public boolean onTouch(View view, MotionEvent motionEvent) { switch (motionEvent.getAction()){ case MotionEvent.ACTION_UP: Log.i(tag, "MyTextView onTouch ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "MyTextView onTouch ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "MyTextView onTouch ACTION_DOWN"); break; } return false; } // Activity 的事件分发 @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_UP: Log.i(tag, "dispatchTouchEvent ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "dispatchTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "dispatchTouchEvent ACTION_DOWN"); break; } return super.dispatchTouchEvent(ev); } // Activity 的事件消费 @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_UP: Log.i(tag, "onTouchEvent ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "onTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "onTouchEvent ACTION_DOWN"); break; } return super.onTouchEvent(event); } }
3.2 打印日志
运行后,点击Text View反馈的打印日志
03-28 08:05:14.824 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: dispatchTouchEvent ACTION_DOWN
03-28 08:05:14.824 1219-1219/com.mvp.chenzhesheng.androidadvance I/MyTextView: dispatchTouchEvent ACTION_DOWN
03-28 08:05:14.824 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onTouch ACTION_DOWN
03-28 08:05:14.824 1219-1219/com.mvp.chenzhesheng.androidadvance I/MyTextView: onTouchEvent ACTION_DOWN
03-28 08:05:15.034 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: dispatchTouchEvent ACTION_UP
03-28 08:05:15.034 1219-1219/com.mvp.chenzhesheng.androidadvance I/MyTextView: dispatchTouchEvent ACTION_UP
03-28 08:05:15.034 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onTouch ACTION_UP
03-28 08:05:15.034 1219-1219/com.mvp.chenzhesheng.androidadvance I/MyTextView: onTouchEvent ACTION_UP
03-28 08:05:15.044 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onClick
dispatchTouchEvent 、 OnTouchEvent 这两个方法的返回值存在三种情况:
- 直接返回true。
- 直接返回false。
- 返回父类同名方法,super.dispatchTouchEvent 或者 super.OnTouchEvent。
由于拥有不同的返回值,所以事件传递流程也有不同,经过不断修改返回值测试,最终得到了点击事件的流程图,ACTION_DOWN 和 ACTION_UP 事件的传递流程是相同的。
3.3 事件传递流程图
从上面的流程图可以得出结论:
- 触摸事件是从 dispatchTouchEvent 开始的,默认返回父类同名方法 super ,事件将会依照嵌套层次从外向内传递( MainActivity 到 MyTextView ),到达最内层的 View 时,将由 View 的 OnTouchEvent 方法处理,该方法返回 true 时进行消费不再传递,返回 false 时再由内向外传递,由外层的 OnTouchEvent 处理。
- 如果外层向内层传递过程中,人为干扰返回 true 消费,则不会继续继续像内部传递。
- View 的事件控制顺序先执行 onTouch 再执行 onClick ,如果 onTouch 返回 true 消费,则不会继续传递,也不会执行 onClick 方法。
四、ViewGroup的事件传递机制
4.1 dome
ViewGroup是 View 的控件容器存在,拥有 dispatchTouchEvent 、 onInterceptTouchEvent 和 onTouchEvent 三个方法,比 View 多了一个 onInterceptTouchEvent 方法。为了更好的观察,我们需要自定义 MyRelativeLayout 继承 RelativeLayout 。
MyRelativeLayout类
public class MyRelativeLayout extends RelativeLayout { private final static String tag = "MyRelativeLayout"; public MyRelativeLayout(Context context) { super(context); } public MyRelativeLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_UP: Log.i(tag, "dispatchTouchEvent ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "dispatchTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "dispatchTouchEvent ACTION_DOWN"); break; } return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_UP: Log.i(tag, "onInterceptTouchEvent ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "onInterceptTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "onInterceptTouchEvent ACTION_DOWN"); break; } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_UP: Log.i(tag, "onTouchEvent ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "onTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "onTouchEvent ACTION_DOWN"); break; } return super.onTouchEvent(event); } }
main_activity.xml 文件
<?xml version="1.0" encoding="utf-8"?> <com.mvp.chenzhesheng.androidadvance.MyRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <com.mvp.chenzhesheng.androidadvance.MyTextView android:id="@+id/text_view" android:clickable="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" </com.mvp.chenzhesheng.androidadvance.MyRelativeLayout>
4.2 打印日志
04-02 08:47:57.980 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: dispatchTouchEvent ACTION_DOWN
04-02 08:47:58.000 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyRelativeLayout: dispatchTouchEvent ACTION_DOWN
04-02 08:47:58.000 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyRelativeLayout: onInterceptTouchEvent ACTION_DOWN
04-02 08:47:58.000 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyTextView: dispatchTouchEvent ACTION_DOWN
04-02 08:47:58.010 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onTouch ACTION_DOWN
04-02 08:47:58.010 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyTextView: onTouchEvent ACTION_DOWN
04-02 08:47:58.200 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: dispatchTouchEvent ACTION_UP
04-02 08:47:58.200 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyRelativeLayout: dispatchTouchEvent ACTION_UP
04-02 08:47:58.200 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyRelativeLayout: onInterceptTouchEvent ACTION_UP
04-02 08:47:58.200 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyTextView: dispatchTouchEvent ACTION_UP
04-02 08:47:58.210 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onTouch ACTION_UP
04-02 08:47:58.210 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyTextView: onTouchEvent ACTION_UP
04-02 08:47:58.260 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onClick
可以看到 MainActivity 和 MyTextView 的事件传递处理中添加了一层 MyRelativeLayout 。通过不同返回值测试,得到一套流程图。
4.3 流程图
从上面的流程图可以得出结论:
- 触摸事件传递是从 Activity 传递到 ViewGroup ,再传递到 View 。如果中间没有 ViewGroup 则直接从 Activity 传递到 View 。
- ViewGroup 通过 onInterceptTouchEvent 方法对事件进行截拦,如果返回 false 或者 super.onInterceptTouchEvent ,则事件会继续传递给子 View 。
- 子 View 中对事件进行消费后, ViewGroup 将不会接收到任何事件。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
- Docker 容器操作
- Ubuntu 17.04 x64 安装 Docker CE 初窥 Dockerfile 部署 Nginx
- 基于Metronic的Bootstrap开发框架经验总结(4)--Bootstrap图标的提取和利用
- WebView 的 input 上传照片的兼容问题
- 在 Linux 上搭建Jekyll静态博客
- 基于Metronic的Bootstrap开发框架经验总结(5)--Bootstrap文件上传插件File Input的使用
- 网易严选 App 感受 Weex 开发
- MBR勒索木马再度来袭:GoldenEye分析
- Docker Compose 1.18.0 之服务编排详解
- 基于Metronic的Bootstrap开发框架经验总结(6)--对话框及提示框的处理和优化
- 基于Metronic的Bootstrap开发框架经验总结(7)--数据的导入、导出及附件的查看处理
- Ubuntu 17.04 x64 安装 Docker CE
- 这是一篇清晰易懂的 Rxjava 入门教程
- 可能是最详细的部署:Docker Registry企业级私有镜像仓库Harbor管理WEB UI
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- 用百度接口实现图片文字识别,并打包成安装包软件
- 视野前端(二)V8引擎是如何工作的
- 【干货】Chrome插件(扩展)开发全攻略
- 超性感的React Hooks(一):为何她独具魅力
- 超性感的React Hooks(二)再谈闭包
- Python全栈(一)基础之11.函数(3)
- Python全栈(二)数据结构和算法之1.算法和数据结构引入
- Android开发(第一行代码 第二版) 常见异常和解决办法(基于Android Studio)(一)
- Python SQLite 基本操作和经验技巧(一)
- Python字典及基本操作(超级详细)
- Python matplotlab库使用方法及注意事项
- 超性感的React Hooks(三):useState
- 数据库编程 MySQL 技巧与经验
- Python 编程开发 实用经验和技巧
- 超性感的React Hooks(四):useEffect