工作有一段时间,有必要掌握事件传递的机制,最近研究了一下,记录下心得。
1 Android中的事件
android中触摸事件比较多,封装中MotionEvent类中,点击、触摸、滑动是我们常用的事件

  • MotionEvent.ACTION_DOWN
  • MotionEvent.ACTION_MOVE
  • MotionEvent.ACTION_UP
  • MotionEvent.ACTION_CANCEL

2 核心方法
了解传递机制前,我们需要先学习几个方法。
2.1ViewGroup

  • dispatchTouchEvent 分发事件,默认接受事件并往下分发。如果返回true,拦截事件,不分发。下同。
  • onInterceptTouchEvent 预处理事件 默认返回false ,返回true表示拦截
  • onTouchEvent 处理事件 默认返回false ,不消费事件。如果在根布局或代码中设置了clickable=true,则super.onTouchEvent返回true,表示消费了事件。

2.2View

  • dispatchTouchEvent
  • onTouchEvent 处理事件 返回值需要看view的属性clickable==true

2.3View/ViewGroup 的 onTouchEvent返回值:

  • 如果view/viewGroup可点击的,比如Button,clickable = true,则super.onTouchEvent 返回true
  • 如果view/viewGroup不可点击,比如TextView,clickable = false,则super.onTouchEvent 返回false
  • 在xml中设置view/viewGroup clickable属性为true, 则该view:super.onTouchEvent 返回true

2.4核心

  • clickable影响了super.onTouchEvent返回值
  • 如果返回false,View只能处理down事件,后续事件不会触发
  • 如果返回true,View的down\move\up都能触发

3 事件传递
  一次完整的屏幕触摸事件:手指按下,滑动,抬起。这个事件由一个action_down,若干个action_move,和一个action_up组成。那么它在我们的屏幕中是如何传递的呢?
默认情况下事件传递是从Activity到ViewGroup到View的过程,最终由View接受到。如图:

  

我们定义一个MyViewGroup和一个MyView,打印其中的方法。
MyViewGroup,clickable为false,super.onTouchEvent默认为false,不消费事件

public class MyViewGroup extends FrameLayout {

    String Tag = "===MyViewGroup";

    public MyViewGroup(Context context) {
super(context);
} @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;
}
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;
}
return super.onInterceptTouchEvent(ev);
} @Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.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;
}
return super.onTouchEvent(ev);
}
}

MyView 继承TextView,clickable默认false,super.onTouchEvent默认为false,不消费事件

public class MyView extends android.support.v7.widget.AppCompatTextView {

    String Tag = "===MyView";

    public MyView(Context context) {
super(context);
} @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;
}
return super.dispatchTouchEvent(ev);
} @Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.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;
}
return super.onTouchEvent(ev);
}
}

activity布局中引用

<com.zcwipe.frecyclerviewdemo.MyViewGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"> <com.zcwipe.frecyclerviewdemo.MyView
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/color_theme"
android:gravity="center"
android:text="content" />
</com.zcwipe.frecyclerviewdemo.MyViewGroup>

事件都是由action_down开始,
首先activity执行super.dispatchTouchEvent分发给viewgroup。
然后viewgroup执行super.dispatchTouchEvent,super.onInterceptTouchEvent。
最后view执行super.dispatchTouchEvent,onTouchEvent。这个时候view在onTouchEvent里就接受到了action_down事件。

情况1:
如果view不在onTouchEvent里消费action_down事件(默认返回false,不消费),那么这个action_down事件就会交由父布局,也就是viewgroup的onTouchEvent来处理。如果viewgroup也不处理。就会继续交给上层布局activity的onTouchEvent处理action_down事件。
此时完成了action_down事件的传递。由于view和viewgroup都没有处理action_down事件,系统就不会再传递后继事件action_move,action_up.日志输出

后续的action_move,action_up事件不再传递给ViewGroup和View。直接由activity处理。

情况2:
我们在view的action_down事件里返回true,消费了这个事件,(也可以定义一个button,或者设置clickable=true,默认消费事件)那么action_down事件便不会传递给viewgroup及activity,那后续的事件都由谁来处理?
action_down是由上层传递到view中的,后续动作action_move和action_up 也是如此。由于我们在子view的down中消费了事件,那么viewgroup在传递后续事件时会在onInterception方法中判断是否需要拦截此action_move、action_up动作
如果不拦截,viewgroup中onInterception不处理,动作由view的onTouchEvent处理。且每次传递move,up事件都会执行onInterception方法,决定是否拦截。日志输出:

如果拦截,viewgroup的action_move返回true,拦截事件,

@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");
return true;
case MotionEvent.ACTION_UP:
Log.e(Tag, "onInterceptTouchEvent ACTION_UP");
break;
}
return super.onInterceptTouchEvent(ev);
}

后续move,up动作由viewgroup的onTouchEvent处理,且不再执行onInterception方法

情况3:

如果viewgroup拦截down事件,那么down事件不再传递给子view,直接由viewgroup的ontouch处理。在viewgroup的onTouchEvent事件中:

如果消费了down事件,返回true,那么后续事件move,up都会直接交由viewgroup的onTouchEvent处理,且不再执行viewgroup的onInterceptTouchEvent方法。日志输出:

如果没有消费,那么move,up将由上层view处理。

案例:加入我们有这么一个需求,在自定义viewgroup中接受action_move事件,实现滑动效果,如何处理?

由情况1我们知道,如果view不消费down事件,viewgroup也不消费事件,那么无法实现。
由情况2我们知道,如果view消费了down事件,viewgroup也不拦截,那么也无法实现。
实现方案两种:
* view onTouchEvent中down消费,viewgroup:onInterceptTouchEvent中move拦截
* view onTouchEvent中down不消费 ,viewgroup:onTouchEvent中down消费
有时候我们不知道view是否消费了事件,那么使用以下方案写出健壮行代码,viewgroup中
1. ontouchevent down消费事件
2. onInterceptTouchEvent中move拦截

 @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(Tag, "onInterceptTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
return true;
// break;
case MotionEvent.ACTION_UP:
Log.e(Tag, "onInterceptTouchEvent ACTION_UP");
break;
}
return super.onInterceptTouchEvent(ev);
} @Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(Tag, "onTouchEvent ACTION_DOWN");
return true;
case MotionEvent.ACTION_MOVE:
Log.e(Tag, "onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(Tag, "onTouchEvent ACTION_UP");
break;
}
return super.onTouchEvent(ev);
}

这样可以稳定的在viewgroup中处理action_move事件,且不影响子view的点击事件。日志输出:

Android 带你读懂事件分发的更多相关文章

  1. (转载) Android 带清除功能的输入框控件ClearEditText,仿IOS的输入框

    Android 带清除功能的输入框控件ClearEditText,仿IOS的输入框 标签: Android清除功能EditText仿IOS的输入框 2013-09-04 17:33 70865人阅读  ...

  2. Android高级_视频播放控件

    一.Android系统自带VideoView控件 1. 创建步骤: (1)自带视频文件放入res/raw文件夹下: (2)声明初始化VideoView控件: (3)创建视频文件Uri路径,Uri调用p ...

  3. 一文带你读懂什么是vxlan网络

    一个执着于技术的公众号 一.背景 随着云计算.虚拟化相关技术的发展,传统网络无法满足大规模.灵活性要求高的云数据中心的要求,于是便有了overlay网络的概念.overlay网络中被广泛应用的就是vx ...

  4. JS调用Android、Ios原生控件

    在上一篇博客中已经和大家聊了,关于JS与Android.Ios原生控件之间相互通信的详细代码实现,今天我们一起聊一下JS调用Android.Ios通信的相同点和不同点,以便帮助我们在进行混合式开发时, ...

  5. Android Material适配 为控件设置指定背景色和点击波纹效果

    Android Material适配 为控件设置指定背景色和点击波纹效果,有需要的朋友可以参考下. 大部分时候,我们都需要为控件设置指定背景色和点击效果 4.x以下可以使用selector,5.0以上 ...

  6. android中的EditView控件

    android中的EditView控件 EditText继承关系:View-->TextView-->EditText ,EditText是可编辑文本框 1.EditText默认情况下,光 ...

  7. Android 5.0新控件——FloatingActionButton(悬浮按钮)

    Android 5.0新控件--FloatingActionButton(悬浮按钮) FloatingActionButton是5.0以后的新控件,一个悬浮按钮,之所以叫做悬浮按钮,主要是因为自带阴影 ...

  8. Android开发:文本控件详解——TextView(一)基本属性

    一.简单实例: 新建的Android项目初始自带的Hello World!其实就是一个TextView. 在activity_main.xml中可以新建TextView,从左侧组件里拖拽到右侧预览界面 ...

  9. Android-事件分发(ViewGroup)

    http://blog.csdn.net/guolin_blog/article/details/9153747 http://blog.csdn.net/lmj623565791/article/d ...

随机推荐

  1. 【项目构建工具】 Gradle笔记2

    一.Gradle执行流程 1.Gradle的执行流程(生命周期)主要是三个阶段: 初始化阶段:解析整个工程中所有Project,构建所有的Project对应的project对象 配置阶段:解析所有的p ...

  2. Jenkins+GitHub 项目环境搭建(一)

    安装Jenkins yum install -y java-1.8.0-openjdk wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkin ...

  3. 面试复习题(二)JavaSE高级(未完成)

    一.Java中的反射 3.说说你对Java中反射的理解 Java中的反射首先是能够获取到Java中要反射类的字节码,获取字节码有3种办法. class.forName(className) 类名.cl ...

  4. Java注解demo

    # 为了熟悉了解注解,写的一个小demo# demo的主要功能是扫描一个class中的包含我们自定义注解的方法,然后把他们的返回值放到一个map中 public class QQ { public s ...

  5. lightinthebox 批量设置分类产品排列方式为List、Grid、Gallery

    lightinthebox 批量设置分类产品排列方式为Grid categories_type = '1'表示List,2表示Grid,3表示Gallery方式 设置单个分类 ; ; ; 设置全部 ' ...

  6. ffmpeg函数05__vcodec_decode_video2()

    vcodec_decode_video2()的作用是解码一帧视频数据

  7. CentOS系统下使用docker安装pinpoint

    准备:安装docker与docker-compose docker安装参考:https://www.cnblogs.com/zhi-leaf/p/10561501.html docker-compos ...

  8. 第二章 Vue快速入门-- 24 过滤器-Vue中全局过滤器的基本使用

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8&quo ...

  9. orm查询优化 MTV和MVC模型 字段的chioces参数 Ajax

    目录 一.ORM查询优化 1. all()查询 2. only()/defer()方法 (1)only()方法 (2)defer()方法 3. select_related()/prefetch_re ...

  10. Acwing-278-数字组合(背包)

    链接: https://www.acwing.com/problem/content/280/ 题意: 给定N个正整数A1,A2,-,AN,从中选出若干个数,使它们的和为M,求有多少种选择方案. 思路 ...