学习笔记:触摸事件MotionEvent
1. 铺垫
1.1 MotionEvent : 触屏事件
int ACTION_DOWN=0 : 代表down
Int ACTION_MOVE=2 ; 代表move
Int ACTION_UP=1 : 代表up
1.2 Activity
boolean dispatchTouchEvent(MotionEvent event) : 分发事件
boolean onTouchEvent(MotionEvent event) : 处理事件的回调
1.3 View
boolean dispatchTouchEvent(MotionEvent event) : 分发事件
void setOnTouchListener(OnTouchListener l) : 设置事件监听器
boolean onTouchEvent(MotionEvent event) : 处理事件的回调方法
1.4 ViewGroup
boolean dispatchTouchEvent(MotionEvent ev) : 分发事件
boolean onInterceptTouchEvent(MotionEvent ev) : 拦截事件的回调方法
1.5 若onTouchEvent或onTouchEvent返回true,则表示事件被消费了,就不会进一步分发。
2.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"tools:context="com.sagereal.motionevent.MainActivity"><com.sagereal.motionevent.MyImageViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@mipmap/ic_launcher"android:layout_margin="30dp"android:id="@+id/iv_test"/>
</LinearLayout>
public class MyImageView extends ImageView {private static final String TAG = "MyImageView";//布局 用两个参数的构造 : 属性集合 AttributeSet public MyImageView(Context context, AttributeSet attrs) {super(context, attrs);Log.e(TAG, "MyImageView()");}@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {Log.e(TAG, "dispatchTouchEvent: action = " + event.getAction());return super.dispatchTouchEvent(event);}@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.e(TAG, "onTouchEvent: action = " + event.getAction());return super.onTouchEvent(event);}
}
public class MainActivity extends Activity{private static final String TAG = "MainActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);findViewById(R.id.iv_test).setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {Log.e("MyImageView", "Listener onTouch: action = " + event.getAction());return false;}});}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {Log.e(TAG, "dispatchTouchEvent: action =" + ev.getAction());return super.dispatchTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.e(TAG, "onTouchEvent: action =" + event.getAction());return super.onTouchEvent(event);}
}
对imageView来说,其回调监听setOnTouchListener默认返回false,即处理事件但不消费事件。
2.1 点击图标之外的位置
对事件down来说,Activity的dispatchTouchEvent方法分发事件,但没有子view来处理、消费,只好调用自己的onTouchEvent分发来处理消费。事件move(1)/up(2)类似。
01-03 10:26:19.660 3437-3437/com.sagereal.motionevent E/MainActivity: dispatchTouchEvent: action =0
01-03 10:26:19.662 3437-3437/com.sagereal.motionevent E/MainActivity: onTouchEvent: action =0
01-03 10:26:19.685 3437-3437/com.sagereal.motionevent E/MainActivity: dispatchTouchEvent: action =2
01-03 10:26:19.686 3437-3437/com.sagereal.motionevent E/MainActivity: onTouchEvent: action =2
01-03 10:26:19.702 3437-3437/com.sagereal.motionevent E/MainActivity: dispatchTouchEvent: action =2
01-03 10:26:19.702 3437-3437/com.sagereal.motionevent E/MainActivity: onTouchEvent: action =2
01-03 10:26:19.722 3437-3437/com.sagereal.motionevent E/MainActivity: dispatchTouchEvent: action =2
01-03 10:26:19.722 3437-3437/com.sagereal.motionevent E/MainActivity: onTouchEvent: action =2
01-03 10:26:19.724 3437-3437/com.sagereal.motionevent E/MainActivity: dispatchTouchEvent: action =1
01-03 10:26:19.725 3437-3437/com.sagereal.motionevent E/MainActivity: onTouchEvent: action =1
2.2 点击图标
对事件down来说,Activity的dispatchTouchEvent方法分发事件,分发到子view,会先调用imgView的dispatchTouchEvent分发,
但它是最后一层,无子view,就由imageView来处理、消费。
对imageView来说,回调监听(setOnTouchListener)的优先级大于回调方法(onTouchEvent)的优先级,若回调监听(setOnTouchListener)、回调方法(onTouchEvent)都不处理消费该事件,则由Activity的onTouchEvent方法处理。
因为imageView连down事件都不处理,故move/up事件也不会分发到imageView,直接由Activity的onTouchEvent方法处理了。
01-03 10:31:14.237 3437-3437/com.sagereal.motionevent E/MainActivity: dispatchTouchEvent: action =0
01-03 10:31:14.239 3437-3437/com.sagereal.motionevent E/MyImageView: dispatchTouchEvent: action = 0
01-03 10:31:14.239 3437-3437/com.sagereal.motionevent E/MyImageView: Listener onTouch: action = 0
01-03 10:31:14.239 3437-3437/com.sagereal.motionevent E/MyImageView: onTouchEvent: action = 0
01-03 10:31:14.240 3437-3437/com.sagereal.motionevent E/MainActivity: onTouchEvent: action =0
01-03 10:31:14.262 3437-3437/com.sagereal.motionevent E/MainActivity: dispatchTouchEvent: action =2
01-03 10:31:14.262 3437-3437/com.sagereal.motionevent E/MainActivity: onTouchEvent: action =2
01-03 10:31:14.272 3437-3437/com.sagereal.motionevent E/MainActivity: dispatchTouchEvent: action =2
01-03 10:31:14.272 3437-3437/com.sagereal.motionevent E/MainActivity: onTouchEvent: action =2
01-03 10:31:14.288 3437-3437/com.sagereal.motionevent E/MainActivity: dispatchTouchEvent: action =2
01-03 10:31:14.289 3437-3437/com.sagereal.motionevent E/MainActivity: onTouchEvent: action =2
01-03 10:31:14.291 3437-3437/com.sagereal.motionevent E/MainActivity: dispatchTouchEvent: action =1
01-03 10:31:14.291 3437-3437/com.sagereal.motionevent E/MainActivity: onTouchEvent: action =1
3. 让imageView的回调监听返回true
findViewById(R.id.iv_test).setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {Log.e("MyImageView", "Listener onTouch: action = " + event.getAction());//return false;return true;}
});
因为imgView的回调监听(setOnTouchListener)的优先级大于回调方法(onTouchEvent)的优先级,
当消息从Activity分发到imageView后,就在setOnTouchListener内被消费了(返回了true),故不会走到回调方法onTouchEvent中
down/move/up事件的处理类似。
点击图标,对应的日志:
01-03 12:32:41.807 5681-5681/com.sagereal.motionevent E/MainActivity: dispatchTouchEvent: action =0
01-03 12:32:41.809 5681-5681/com.sagereal.motionevent E/MyImageView: dispatchTouchEvent: action = 0
01-03 12:32:41.809 5681-5681/com.sagereal.motionevent E/MyImageView: Listener onTouch: action = 0
01-03 12:32:41.839 5681-5681/com.sagereal.motionevent E/MainActivity: dispatchTouchEvent: action =2
01-03 12:32:41.840 5681-5681/com.sagereal.motionevent E/MyImageView: dispatchTouchEvent: action = 2
01-03 12:32:41.840 5681-5681/com.sagereal.motionevent E/MyImageView: Listener onTouch: action = 2
01-03 12:32:41.849 5681-5681/com.sagereal.motionevent E/MainActivity: dispatchTouchEvent: action =2
01-03 12:32:41.850 5681-5681/com.sagereal.motionevent E/MyImageView: dispatchTouchEvent: action = 2
01-03 12:32:41.850 5681-5681/com.sagereal.motionevent E/MyImageView: Listener onTouch: action = 2
01-03 12:32:41.852 5681-5681/com.sagereal.motionevent E/MainActivity: dispatchTouchEvent: action =1
01-03 12:32:41.853 5681-5681/com.sagereal.motionevent E/MyImageView: dispatchTouchEvent: action = 1
01-03 12:32:41.854 5681-5681/com.sagereal.motionevent E/MyImageView: Listener onTouch: action = 1
4. 让imageView的down事件对应的回调监听返回true,move/up事件都返回false
findViewById(R.id.iv_test).setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {Log.e("MyImageView", "Listener onTouch: action = " + event.getAction());//return false;if (event.getAction() == MotionEvent.ACTION_DOWN) {return true;}return false;}
});
down事件是被分发到imageView,被setOnTouchListener消费,但move/up事件并没有被消费,仍然返回false,故最终会被Activity的onTouchEvent处理消费。
点击图标,对应的日志:
01-01 19:40:02.793 5725-5725/com.sagereal.motionevent E/MyImageView: MyImageView()
01-01 19:40:09.469 5725-5725/com.sagereal.motionevent E/MainActivity: dispatchTouchEvent: action =0
01-01 19:40:09.472 5725-5725/com.sagereal.motionevent E/MyImageView: dispatchTouchEvent: action = 0
01-01 19:40:09.472 5725-5725/com.sagereal.motionevent E/MyImageView: Listener onTouch: action = 0
01-01 19:40:09.477 5725-5725/com.sagereal.motionevent E/MainActivity: dispatchTouchEvent: action =2
01-01 19:40:09.478 5725-5725/com.sagereal.motionevent E/MyImageView: dispatchTouchEvent: action = 2
01-01 19:40:09.479 5725-5725/com.sagereal.motionevent E/MyImageView: Listener onTouch: action = 2
01-01 19:40:09.479 5725-5725/com.sagereal.motionevent E/MyImageView: onTouchEvent: action = 2
01-01 19:40:09.480 5725-5725/com.sagereal.motionevent E/MainActivity: onTouchEvent: action =2
01-01 19:40:09.484 5725-5725/com.sagereal.motionevent E/MainActivity: dispatchTouchEvent: action =1
01-01 19:40:09.485 5725-5725/com.sagereal.motionevent E/MyImageView: dispatchTouchEvent: action = 1
01-01 19:40:09.486 5725-5725/com.sagereal.motionevent E/MyImageView: Listener onTouch: action = 1
01-01 19:40:09.486 5725-5725/com.sagereal.motionevent E/MyImageView: onTouchEvent: action = 1
01-01 19:40:09.487 5725-5725/com.sagereal.motionevent E/MainActivity: onTouchEvent: action =1
二.补充
事件序列,一般是指从手指触摸到屏幕在到离开屏幕这么一个过程。在这个过程中其实会产生多个事件,一般是以ACTION_DOWN作为开始,中间存在多个ACTION_MOVE,最后以ACTION_UP结束。我们称一次ACTION_DOWN-->ACTION_MOVE-->ACTION_UP过程称为一个事件序列。
事件分发主要3个方法:
dispatchTouchEvent:负责事件的传递分发,事件到达view时一定回调此方法。返回值表示是否消费此事件,受onTouchEvent和子view的dispatchTouchEvent返回值影响。
onInterceptTouchEvent:用于判断是否拦截事件,也是返回值的含义。在dispatchTouchEvent内部调用。view拦截了某个事件,那后续这一事件序列都会默认拦截,不再调用此方法。只有ViewGroup才有这个方法,View无此方法,即View不可以拦截事件。
onTouchEvent:用于处理事件,返回值表示是否消费此事件。在dispatchTouchEvent内部调用。如果不消耗某一事件,那当前view不再接受同一事件序列的事件。
三者关系:事件到达view时,会调用dispatchTouchEvent,然后内部调用onInterceptTouchEvent判断是否拦截,如果不拦截就调用子view的dispatchTouchEvent;若拦截,则执行onTouchEvent方法处理这个事件。
/*** 点击事件产生后*/ // 步骤1:调用dispatchTouchEvent()public boolean dispatchTouchEvent(MotionEvent ev) {boolean consume = false; //代表 是否会消费事件// 步骤2:判断是否拦截事件if (onInterceptTouchEvent(ev)) {// a. 若拦截,则将该事件交给当前View进行处理// 即调用onTouchEvent ()方法去处理点击事件consume = onTouchEvent (ev) ;} else {// b. 若不拦截,则将该事件传递到下层// 即 下层元素的dispatchTouchEvent()就会被调用,重复上述过程// 直到点击事件被最终处理为止consume = child.dispatchTouchEvent (ev) ;}// 步骤3:最终返回通知 该事件是否被消费(接收 & 处理)return consume;}
一般有三种返回:true
、false
和super
引用父类对应方法。
dispatchTouchEvent 返回 true:表示改事件在本层不再进行分发且已经在事件分发自身中被消费了。
dispatchTouchEvent 返回 false:表示事件在本层不再继续进行分发,并交由上层控件的onTouchEvent
方法进行消费。dispatchTouchEvent 返回 super : 默认会调用自己的 onInterceptTouchEvent 方法
onInterceptTouchEvent 返回true:表示将事件进行拦截,并将拦截到的事件交由本层控件 的onTouchEvent
进行处理。
onInterceptTouchEvent 返回false:表示不对事件进行拦截,事件得以成功分发到子View
。并由子View
的dispatchTouchEvent
进行处理。
onTouchEvent 返回 true:表示onTouchEvent
处理完事件后消费了此次事件。此时事件终结,将不会进行后续的传递。
onTouchEvent 返回 false:事件在onTouchEvent
中处理后继续向上层View传递,且有上层View
的onTouchEvent
进行处理。
除此之外还有一个方法也是经常用到的:requestDisallowInterceptTouchEvent(),这个方法能够影响父ViewGroup是否拦截事件,true表示 不拦截事件,false表示拦截事件。
《Android开发艺术探索》里总结了11条关于事件传递的结论:
同一个事件序列是指手机接触屏幕那一刻起,到离开屏幕那一刻结束,有一个down事件,若干个move事件,一个up事件构成。
某个View一旦决定拦截事件,那么这个事件序列之后的事件都会由它来处理,并且不会再调用onInterceptTouchEvent。
正常情况下,一个事件序列只能被一个View拦截并消耗。这个原因可以参考第2条,因为一旦拦截了某个事件,那么这个事件序列里的其他事件都会交给这个View来处理,所以同一事件序列中的事件不能分别由两个View同时处理,但是我们可以通过特殊手段做到,比如一个View将本该自己处理的事件通过onTouchEvent强行传递给其他View处理。
一个View如果开始处理事件,如果它不处理down事件(onTouchEvent里面返回了false),那么这个事件序列的其他事件就不会交给它来继续处理了,而是会交给它的父元素去处理。
如果一个View处理了down事件,却没有处理其他事件,那么这些事件不会交给父元素处理,并且这个View还能继续受到后续的事件。而这些未处理的事件,最终会交给Activity来处理。
ViewGroup的onInterceptToucheEvent默认返回false,也就是默认不拦截事件。
View没有InterceptTouchEvent方法,如果有事件传过来,就会直接调用onTouchEvent方法。
View的onTouchEvent方法默认都会消耗事件,也就是默认返回true,除非它是不可点击的(longClickable和clickable同时为false)。注意:View的longClickable默认都为false,clickable要根据控件属性判断。
View的enable属性不会影响onTouchEvent的默认返回值。就算一个View是不可见的,只要它是可点击的(clickable或者longClickable有一个为true),它的onTouchEvent默认返回值也是true。
onClick方法会执行的前提是当前View是可点击的,并且它收到了down和up事件。
事件传递过程是由外向内的,也就是事件会先传给父元素在向下传递给子元素。但是子元素可以通过requestDisallowInterceptTouchEvent来干预父元素的分发过程,但是down事件除外(因为down事件方法里,会清除所有的标志位)。
参考:
Android事件分发机制详解:史上最全面、最易懂
Android事件传递、多点触控及滑动冲突的处理
Android View的事件分发机制和滑动冲突解决方案
View事件分发、滑动冲突--《Android开发艺术探索》阅读笔记——第三章part2
Android进阶知识:事件分发与滑动冲突
学习笔记:触摸事件MotionEvent相关推荐
- Android笔记:触摸事件的分析与总结----多点触控
其他相关博文: Android笔记:触摸事件的分析与总结----MotionEvent对象 Android笔记:触摸事件的分析与总结----TouchEvent处理机制 An ...
- JavaScript 学习笔记 - 挂载事件 Demo
JavaScript 学习笔记 - 挂载事件 Demo 例子 addEventListener 监听事件 挂载事件的同时带上参数 dispatchEvent 触发事件 removeEventListe ...
- 前端学习笔记 - 触摸有几个事件?
返回目录 1.click事件 单击事件,类似于PC端的click,但在移动端中,连续click的触发有200ms ~ 300ms的延迟 2.touch类事件 a.触摸事件,有touchstart.to ...
- android 触摸事件 控制,Android笔记:触摸事件的分析与总结----TouchEvent处理机制
其他相关博文: Android中的事件类型分为按键事件和屏幕触摸事件.TouchEvent是屏幕触摸事件的基础事件,要深入了解屏幕触摸事件的处理机制,就必须掌握TouchEvent在整个触摸事件中的转 ...
- Caliburn.Micro学习笔记(三)----事件聚合IEventAggregator和 IhandleT
Caliburn.Micro学习笔记目录 今天 说一下Caliburn.Micro的IEventAggregator和IHandle<T>分成两篇去讲这一篇写一个简单的例子 看一它的的实现 ...
- android 触摸屏 滑动,android开发:触摸屏触摸事件MotionEvent演示实例
触摸事件,可以包含多点触摸,也可以使用捏合手势缩放,并且放大图片; 多点触摸的实现: num =motionEvent.getPointerCount()//使用MotionEvent的此方法来获取当 ...
- JavaScript 学习笔记 之事件
事件 事件是DOM(文档对象模型)的一部分.事件流就是事件发生顺序,这是IE和其他浏览器在事件支持上的主要差别. 一.事件流 1.冒泡型事件 IE上的解决方案就是冒泡型事件,它的基本思想是从最特定的目 ...
- Silverlight 2 学习笔记之事件的重复绑定问题
事件重复绑定是在Silverlight2应用程序开发过程中,开发者容易忽视,时常会为整个Silverlight2应用程序产生重大问题的原因,如果你发现你的Silverlight2应用程序在随着运行过程 ...
- jQuery学习笔记:事件
一.页面载入 1.ready(fn) 当DOM载入就绪可以查询及操纵时绑定一个要执行的函数. 这是事件模块中最重要的一个函数,因为它可以极大地提高web应用程序的响应速度. 简单地说,这个方法纯粹是对 ...
最新文章
- Springboot + redis + 注解 + 拦截器来实现接口幂等性校验
- 重温目标检测--YOLO v3
- MysqL数据库密码的管理
- MySQL root密码重置 报错:mysqladmin: connect to server at 'localhost' failed的解决方案
- python水仙花数总结_python打印n位数“水仙花数”(实例代码)
- c++二进制文件java读取int_吃透Java基础十二:IO
- 阿里动物园新成员来了,10本书带你读懂这个新物种
- python百分号字符串_python--003--百分号字符串拼接、format
- gridview為什麼分頁後,GridView1_RowDataBound就運行不了
- 老男孩python课程_老男孩python课程
- 台风怎么看内存颗粒_使用300多元的D4 16G内存是种什么体验
- Loadrunner报错汇总
- 大数据征信是个人信用风险管理的必然趋势
- matlab实时编辑器怎么用,Markdown 实时编辑器
- nyoj 779 兰州烧饼
- QT QColor颜色选择器学习
- Python - 体脂率
- 软考 软件设计师 第五版+历年真题
- [ZCMU OJ]1633: 酷酷的单词(遍历)
- RDKit | 计算拓扑极性表面积TPSA