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;}

一般有三种返回:truefalsesuper引用父类对应方法。

dispatchTouchEvent 返回 true:表示改事件在本层不再进行分发且已经在事件分发自身中被消费了。
dispatchTouchEvent 返回 false:表示事件在本层不再继续进行分发,并交由上层控件的onTouchEvent方法进行消费。dispatchTouchEvent 返回 super : 默认会调用自己的 onInterceptTouchEvent 方法

onInterceptTouchEvent 返回true:表示将事件进行拦截,并将拦截到的事件交由本层控件 的onTouchEvent 进行处理。
onInterceptTouchEvent 返回false:表示不对事件进行拦截,事件得以成功分发到子View。并由子ViewdispatchTouchEvent进行处理。

onTouchEvent 返回 true:表示onTouchEvent处理完事件后消费了此次事件。此时事件终结,将不会进行后续的传递。
onTouchEvent 返回 false:事件在onTouchEvent中处理后继续向上层View传递,且有上层ViewonTouchEvent进行处理。

除此之外还有一个方法也是经常用到的:requestDisallowInterceptTouchEvent(),这个方法能够影响父ViewGroup是否拦截事件,true表示 不拦截事件,false表示拦截事件。

Android开发艺术探索》里总结了11条关于事件传递的结论:

  1. 同一个事件序列是指手机接触屏幕那一刻起,到离开屏幕那一刻结束,有一个down事件,若干个move事件,一个up事件构成。

  2. 某个View一旦决定拦截事件,那么这个事件序列之后的事件都会由它来处理,并且不会再调用onInterceptTouchEvent。

  3. 正常情况下,一个事件序列只能被一个View拦截并消耗。这个原因可以参考第2条,因为一旦拦截了某个事件,那么这个事件序列里的其他事件都会交给这个View来处理,所以同一事件序列中的事件不能分别由两个View同时处理,但是我们可以通过特殊手段做到,比如一个View将本该自己处理的事件通过onTouchEvent强行传递给其他View处理。

  4. 一个View如果开始处理事件,如果它不处理down事件(onTouchEvent里面返回了false),那么这个事件序列的其他事件就不会交给它来继续处理了,而是会交给它的父元素去处理。

  5. 如果一个View处理了down事件,却没有处理其他事件,那么这些事件不会交给父元素处理,并且这个View还能继续受到后续的事件。而这些未处理的事件,最终会交给Activity来处理。

  6. ViewGroup的onInterceptToucheEvent默认返回false,也就是默认不拦截事件。

  7. View没有InterceptTouchEvent方法,如果有事件传过来,就会直接调用onTouchEvent方法。

  8. View的onTouchEvent方法默认都会消耗事件,也就是默认返回true,除非它是不可点击的(longClickable和clickable同时为false)。注意:View的longClickable默认都为false,clickable要根据控件属性判断。

  9. View的enable属性不会影响onTouchEvent的默认返回值。就算一个View是不可见的,只要它是可点击的(clickable或者longClickable有一个为true),它的onTouchEvent默认返回值也是true。

  10. onClick方法会执行的前提是当前View是可点击的,并且它收到了down和up事件。

  11. 事件传递过程是由外向内的,也就是事件会先传给父元素在向下传递给子元素。但是子元素可以通过requestDisallowInterceptTouchEvent来干预父元素的分发过程,但是down事件除外(因为down事件方法里,会清除所有的标志位)。

参考:

Android事件分发机制详解:史上最全面、最易懂

Android事件传递、多点触控及滑动冲突的处理

Android View的事件分发机制和滑动冲突解决方案

View事件分发、滑动冲突--《Android开发艺术探索》阅读笔记——第三章part2

Android进阶知识:事件分发与滑动冲突

学习笔记:触摸事件MotionEvent相关推荐

  1. Android笔记:触摸事件的分析与总结----多点触控

       其他相关博文:    Android笔记:触摸事件的分析与总结----MotionEvent对象    Android笔记:触摸事件的分析与总结----TouchEvent处理机制     An ...

  2. JavaScript 学习笔记 - 挂载事件 Demo

    JavaScript 学习笔记 - 挂载事件 Demo 例子 addEventListener 监听事件 挂载事件的同时带上参数 dispatchEvent 触发事件 removeEventListe ...

  3. 前端学习笔记 - 触摸有几个事件?

    返回目录 1.click事件 单击事件,类似于PC端的click,但在移动端中,连续click的触发有200ms ~ 300ms的延迟 2.touch类事件 a.触摸事件,有touchstart.to ...

  4. android 触摸事件 控制,Android笔记:触摸事件的分析与总结----TouchEvent处理机制

    其他相关博文: Android中的事件类型分为按键事件和屏幕触摸事件.TouchEvent是屏幕触摸事件的基础事件,要深入了解屏幕触摸事件的处理机制,就必须掌握TouchEvent在整个触摸事件中的转 ...

  5. Caliburn.Micro学习笔记(三)----事件聚合IEventAggregator和 IhandleT

    Caliburn.Micro学习笔记目录 今天 说一下Caliburn.Micro的IEventAggregator和IHandle<T>分成两篇去讲这一篇写一个简单的例子 看一它的的实现 ...

  6. android 触摸屏 滑动,android开发:触摸屏触摸事件MotionEvent演示实例

    触摸事件,可以包含多点触摸,也可以使用捏合手势缩放,并且放大图片; 多点触摸的实现: num =motionEvent.getPointerCount()//使用MotionEvent的此方法来获取当 ...

  7. JavaScript 学习笔记 之事件

    事件 事件是DOM(文档对象模型)的一部分.事件流就是事件发生顺序,这是IE和其他浏览器在事件支持上的主要差别. 一.事件流 1.冒泡型事件 IE上的解决方案就是冒泡型事件,它的基本思想是从最特定的目 ...

  8. Silverlight 2 学习笔记之事件的重复绑定问题

    事件重复绑定是在Silverlight2应用程序开发过程中,开发者容易忽视,时常会为整个Silverlight2应用程序产生重大问题的原因,如果你发现你的Silverlight2应用程序在随着运行过程 ...

  9. jQuery学习笔记:事件

    一.页面载入 1.ready(fn) 当DOM载入就绪可以查询及操纵时绑定一个要执行的函数. 这是事件模块中最重要的一个函数,因为它可以极大地提高web应用程序的响应速度. 简单地说,这个方法纯粹是对 ...

最新文章

  1. Springboot + redis + 注解 + 拦截器来实现接口幂等性校验
  2. 重温目标检测--YOLO v3
  3. MysqL数据库密码的管理
  4. MySQL root密码重置 报错:mysqladmin: connect to server at 'localhost' failed的解决方案
  5. python水仙花数总结_python打印n位数“水仙花数”(实例代码)
  6. c++二进制文件java读取int_吃透Java基础十二:IO
  7. 阿里动物园新成员来了,10本书带你读懂这个新物种
  8. python百分号字符串_python--003--百分号字符串拼接、format
  9. gridview為什麼分頁後,GridView1_RowDataBound就運行不了
  10. 老男孩python课程_老男孩python课程
  11. 台风怎么看内存颗粒_使用300多元的D4 16G内存是种什么体验
  12. Loadrunner报错汇总
  13. 大数据征信是个人信用风险管理的必然趋势
  14. matlab实时编辑器怎么用,Markdown 实时编辑器
  15. nyoj 779 兰州烧饼
  16. QT QColor颜色选择器学习
  17. Python - 体脂率
  18. 软考 软件设计师 第五版+历年真题
  19. [ZCMU OJ]1633: 酷酷的单词(遍历)
  20. RDKit | 计算拓扑极性表面积TPSA

热门文章

  1. NLP-Seq2Seq
  2. 严厉!毕业6年后被举报学位论文抄袭,高校撤销了他的硕士学位!
  3. 小波变换原理与JPEG2000
  4. 苹果手机微信小程序fixed失效问题记录
  5. Django+vue+ElementUi 实现前后端分离项目
  6. 解决delphi直接打开bpg工程组文件后提示PROJECTS macro in project group file missing or incorrect丢失错误
  7. 若干种窗口画面的捕获方法
  8. linux rev命令_如何在Linux上使用rev命令
  9. 火炬之光2控制台代码
  10. 最好大学信息爬取,跟我一起看大学排行榜