前言

今天总结的一个知识点是Andorid中View事件传递机制,也是核心知识点,相信很多开发者在面对这个问题时候会觉得困惑,另外,View的另外一个难题滑动冲突,比如在ScrollView中嵌套ListView,都是上下滑动,这该如何解决呢,它解决的依据就是View事件的传递机制,所以开发者需要对View的事件传递机制有较深入的理解。

目录

  • Activity、View、ViewGroup三者关系
  • 触摸事件类型
  • 事件传递三个阶段
  • View事件传递机制
  • ViewGroup事件传递机制
  • 小结

Activity、View、ViewGroup三者关系

我们都知道Android中看到的页面很多是Activity组件,然后在Activity中嵌套控件,比如TextView、RelativeLayout布局等,其实这些控件的基类都是View这个抽象类,而ViewGroup也是View的子类,区别在于ViewGroup是可以当做其他子类的容器,一张关系图如下:

简单一句话,这些View控件的载体是Activity,Activity通过从DecorView开始进行绘制。

触摸事件类型

  • ACTION_DOWN:用户手指按下操作,往往也代表着一次触摸事件的开始。
  • ACTION_MOVE:用户手指在屏幕上移动,一般情况下的轻微移动都会触发一系列的移动事件。
  • ACTION_POINTER_DOWN:额外的手指按下操作。
  • ACTION_POINTER_UP:额外的手指的离开操作
  • ACTION_UP:用户手指离开屏幕的操作,一次抬起操作标志着一次触摸事件的结束。

在一次屏幕触摸操作中,ACTION_DOWNACTION_UP是必需的,ACTION_MOVE则是看情况而定,如果只是点击,那么检测到只有按下和抬起操作。

事件传递三个阶段

  • 分发(Dispatch):事件的分发对应着dispatchTouchEvent方法,在Andorid系统中,所有的触摸事件都是通过这个方法来分发的。

    boolean dispatchTouchEvent (MotionEvent ev)

    这个方法中,可以决定直接消费这个事件或者将事件继续分发给子视图处理。

  • 拦截(Intercept):事件拦截对应着onInterceptTouchEvent方法,这个方法只有在ViewGroup及其子类中才存在,在View和Activity中是不存在的。

    boolean onInterceptTouchEvent (MotionEvent ev)

    这个方法用来判断是否拦截某个事件,如果拦截了某个事件,那么在同一序列事件当中,那么这个方法不会被再次调用。

  • 消费(Consume):事件消费对应着onTouchEvent方法。

    boolean onTouchEvent (MotionEvent event)

    用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一事件序列中,当前View无法再接收到事件

在Android系统中,拥有事件传递处理能力的有三种:

  • Activity:拥有dispatchTouchEvent、onTouchEvent两个方法。
  • ViewGroup:拥有dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent三个方法。
  • View:拥有dispatchTouchEvent、onTouchEvent两个方法。

View事件传递机制

这里说的View指的是除了ViewGroup之外的View控件,比如TextView、Button、CheckBox等,View控件本身就是最小的单位,不能作为其他View的容器,View拥有dispatchTouchEvent、onTouchEvent两个方法,所以这里就定义了一个继承TextView的类MyTextView,通过代码查看日志,看流程如何走。

public class MyTextView extends TextView {

    private static final String TAG = "MyTextView";

    public MyTextView(Context context) {
super(context);
} public MyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
} @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "dispatchTouchEvent ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "dispatchTouchEvent ACTION_CANCEL");
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
} @Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "onTouchEvent ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "onTouchEvent ACTION_CANCEL");
break;
default:
break;
}
return super.onTouchEvent(event);
} }

同时定义一个MainActivity类用来展示MyTextView,在这个Activity中,我们为MyTextView设置了点击onClick和onTouch监听,方便跟踪了解事件传递的流程。

public class MainActivity extends AppCompatActivity implements View.OnClickListener, View.OnTouchListener {

    private static final String TAG = "MainActivity";

    private MyTextView mTextView;

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (MyTextView) findViewById(R.id.my_text_view);
mTextView.setOnClickListener(this); // 设置MyTextView的点击处理
mTextView.setOnTouchListener(this); // 设置MyTextView的触摸处理
} @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "dispatchTouchEvent ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "dispatchTouchEvent ACTION_CANCEL");
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
} @Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "onTouchEvent ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "onTouchEvent ACTION_CANCEL");
break;
default:
break;
}
return super.onTouchEvent(event);
} @Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.my_text_view:
Log.e(TAG, "MyTextView onClick");
break;
default:
break;
}
} @Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch(view.getId()) {
case R.id.my_text_view:
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "MyTextView onTouch ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "MyTextView onTouch ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "MyTextView onTouch ACTION_UP");
break;
default:
break;
}
break;
default:
break;
}
return false;
}
}

查看结果:

从中可以看到,事件是从down-move-up这样顺序执行,onTouch方法优先于onClick方法调用,如果都是以super方法传递的话,最后的结果是在MyTextView的onTouchEvent方法内被消费的,如果不消费的话,则会把事件返回到它的父级去消费,如果父级也没消费,那么最终会返回到Activity中处理。

ViewGroup事件传递机制

ViewGroup作为View控件的容器存在,ViewGroup拥有dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent三个方法。同样,我们自定义一个ViewGroup,继承自RelativeLayout,实现一个MyRelativeLayout。

public class MyRelativeLayout extends RelativeLayout {

    private static final 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_DOWN:
Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "dispatchTouchEvent ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "dispatchTouchEvent ACTION_CANCEL");
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
} @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onInterceptTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onInterceptTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "onInterceptTouchEvent ACTION_UP");
break;
default:
break;
}
return true;
} @Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "onTouchEvent ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "onTouchEvent ACTION_CANCEL");
break;
default:
break;
}
return super.onTouchEvent(event);
}
}

查看结果:

从中可以看到触摸事件的传递顺序也是从Activity到ViewGroup,再由ViewGroup递归传递给它的子View。ViewGroup通过onInterceptTouchEvent方法对事件进行拦截,如果该方法返回true,则事件不会继续传递给子View,如果返回false或者super.onInterceptTouchEvent,则事件会继续传递给子View。在子View中对事件进行消费后,ViewGroup将不接收到任何事件。

小结

在Android系统事件中,View和ViewGroup的伪代码如下:

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

用三张图来表示Android中触摸机制的流程。

1,View内触摸事件不消费

2,View内触摸事件消费

3,ViewGroup拦截触摸事件

一些总结:

  • 同一个事件序列是指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束。一般是以down事件开始,中间含有数量不定的move事件,最终以up事件结束。
  • 正常情况下,一个事件序列只能被一个View拦截且消耗。
  • 某个View一旦决定拦截,那么这个事件序列就只能由它来处理,那么同一事件序列中的其他事件都不会再交给它来处理,并且事件将重新交给它的父元素去处理,即父元素的onTouchEvent会被调用。
  • 如果View不消耗除ACTION_DOWN以外的其他事件,那么这个点击事件就会消失,此时父元素的onTouchEvent并不会被调用,最终会交给Activity处理。
  • ViewGroup默认不拦截任何事件。
  • View中没有onInterceptTouchEvent方法。
  • View的onTouchEvent默认都会被消耗,除非它是不可点击的。
  • 事件传递过程是由外向内的,即事件先是传递给父元素,然后再由父元素分发给子View。

参考地址:

1,https://www.youtube.com/watch?v=EZAoJU-nUyI

阅读扩展

源于对掌握的Android开发基础点进行整理,罗列下已经总结的文章,从中可以看到技术积累的过程。

1,Android系统简介

2,ProGuard代码混淆

3,讲讲Handler+Looper+MessageQueue关系

4,Android图片加载库理解

5,谈谈Android运行时权限理解

6,EventBus初理解

7,Android 常见工具类

8,对于Fragment的一些理解

9,Android 四大组件之 " Activity "

10,Android 四大组件之" Service "

11,Android 四大组件之“ BroadcastReceiver "

12,Android 四大组件之" ContentProvider "

13,讲讲 Android 事件拦截机制

14,Android 动画的理解

15,Android 生命周期和启动模式

16,Android IPC 机制

17,View 的事件体系

18,View 的工作原理

19,理解 Window 和 WindowManager

20,Activity 启动过程分析

21,Service 启动过程分析

22,Android 性能优化

23,Android 消息机制

24,Android Bitmap相关

25,Android 线程和线程池

26,Android 中的 Drawable 和动画

27,RecylerView 中的装饰者模式

28,Android 触摸事件机制

29,Android 事件机制应用

30,Cordova 框架的一些理解

31,有关 Android 插件化思考

32,开发人员必备技能——单元测试

初识Android触摸事件传递机制的更多相关文章

  1. Android触摸事件传递机制

    简单梳理一下Android触摸事件传递机制的知识点. 一.View与ViewGroup的关系 View和ViewGroup二者的继承关系如下图所示: View是Android中最基本的一种UI组件,它 ...

  2. Android触摸事件传递机制,这一篇就够了

    整个触摸事件牵涉到的是,Activity,View,ViewGroup三者的传递机制. 这个触摸事件就是从外层往内层一层层的传递. 整个传递机制,分为3个步骤:分发,拦截,和消费. 1. 触摸事件的类 ...

  3. Android touch 事件传递机制

    前言: (1)在自定义view的时候经常会遇到事件拦截处理,比如在侧滑菜单的时候,我们希望在侧滑菜单里面有listview控件,但是我们希望既能左右滑动又能上下滑动,这个时候就需要对触摸的touch事 ...

  4. Android 的事件传递机制,详解

    Android 的事件传递机制,详解 前两天和一个朋友聊天的时候.然后说到事件传递机制.然后让我说的时候,忽然发现说的不是非常清楚,事实上Android 的事件传递机制也是知道一些,可是感觉自己知道的 ...

  5. Android Touch事件传递机制引发的血案

    尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38942135 关于Android Touch事件传递机制我之前也写过两篇文章,自觉得对Tou ...

  6. Android Touch事件传递机制 二:单纯的(伪生命周期)

    转载于:http://blog.csdn.net/yuanzeyao/article/details/38025165 在前一篇文章中,我主要讲解了Android源码中的Touch事件的传递过程,现在 ...

  7. Android Touch事件传递机制 一: OnTouch,OnItemClick(监听器),dispatchTouchEvent(伪生命周期)

      ViewGroup View  Activity dispatchTouchEvent 有 有 有 onInterceptTouchEvent 有 无 无 onTouchEvent 有 有 有 例 ...

  8. iOS 和 Android 触摸事件传递

    先看文章,写得很好 ios 触摸事件传递 http://www.cnblogs.com/Quains/p/3369132.html 另外一篇 http://blog.csdn.net/yongyinm ...

  9. 【转】Android TouchEvent事件传递机制

    Android TouchEvent事件传递机制   事件机制参考地址: http://www.cnblogs.com/sunzn/archive/2013/05/10/3064129.html ht ...

随机推荐

  1. Maximum Depth of Binary Tree leetcode

    Given a binary tree, find its maximum depth. The maximum depth is the number of nodes along the long ...

  2. 1054: [HAOI2008]移动玩具

    1054: [HAOI2008]移动玩具 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 1272  Solved: 690[Submit][Statu ...

  3. CoreAnimation 核心动画

    - (void)createBaseAnimation{ //基础动画 CABasicAnimation *animation = [CABasicAnimation animation]; anim ...

  4. C++primer拾遗(第二章:变量和基本类型)

    这是我对c++primer第二章的一个整理总结,算是比较适用于我自己吧,一小部分感觉不用提及的就省略了,只提了一下平时不注意,或者不好记住的内容. 排版太费劲了,直接放了图片格式.从自己的oneNot ...

  5. HTML5初步了解

        一.使用HTML5的十大原因 你难道还没有考虑使用HTML5? 当然我猜想你可能有自己的原因:它现在还没有被广泛的支持,在IE中不好使,或者你就是喜欢写比较严格的XHTML代码.HTML5是w ...

  6. Erlang模块gen_server翻译

    gen_server 概要: 通用服务器行为描述: 行为模块实现服务器的客户端-服务器关系.一个通用的服务器进程使用这个模块将实现一组标准的接口功能,包括跟踪和错误报告功能.它也符合OTP进程监控树. ...

  7. hog特征及其提取方法图示

    1 什么是hog特征 hog特征是histogram of gradient的缩写.我们观察图像时,信息更多来自目标边沿的突变.我们计算一块区域内的所有像素处的梯度信息,即突变的方向和大小,然后对36 ...

  8. 2017java预备作业2

    1 安装git • 到Git官网https://www.git-scm.com/ 下载Git客户端 • 安装时选择默认即可. • 安装完成后在桌面的快捷菜单中选择Git Bash Here 或者在开始 ...

  9. Asp.Net MVC4中的全局过滤器,

    可以对整个项目进行全局监控. 新建一个MVC4项目,可以在global.asax文件中看到如下代码:  FilterConfig.RegisterGlobalFilters(GlobalFilters ...

  10. Python中的变量

    多个变量赋值 Python允许你同时为多个变量赋值.例如: a = b = c = 1 以上实例,创建一个整型对象,值为1,三个变量被分配到相同的内存空间上. 您也可以为多个对象指定多个变量.例如: ...