工作有一段时间,有必要掌握事件传递的机制,最近研究了一下,记录下心得。
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. spring boot 开启Druid监控功能

    1.配置yml spring: datasource: # 数据源基本配置 username: song password: 123456 driver-class-name: com.mysql.j ...

  2. 关于IDEA顶部栏隐藏问题,

    那天手残,点到了 IDEA顶部菜单栏 > View > Appearance >Main Menu ,然后取消了勾选 然后就成了这个样子,没了顶部栏,恢复不过来,不知道如何进行设置 ...

  3. 设置VMware 以及指定 虚拟机 ,开机自启动

    进入开机自启动目录 C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp 重命名为 start.bat 编辑 这个文件,编辑之后Ct ...

  4. 防范DDoS攻击的15个方法

    0x01 背景 为了对抗 DDoS(分布式拒绝服务)攻击,你需要对攻击时发生了什么有一个清楚的理解..简单来讲,DDoS 攻击可以通过利用服务器上的漏洞,或者消耗服务器上的资源(例如 内存.硬盘等等) ...

  5. kubesphere集群节点扩容

    原有的节点是 : master[123] , node[1234] 新加的节点node5 一.修改配置文件hosts.ini [root@master0 ~]# /conf/hosts.ini [al ...

  6. 浅析Java泛型

    什么是泛型? 泛型是JDK 1.5的一项新特性,它的本质是参数化类型(Parameterized Type)的应用,也就是说所操作的数据类型被指定为一个参数,在用到的时候在指定具体的类型.这种参数类型 ...

  7. 自学Python-基于tcp协议的socket

    自学Python之路-Python基础+模块+面向对象自学Python之路-Python网络编程自学Python之路-Python并发编程+数据库+前端自学Python之路-django 自学Pyth ...

  8. linux系统很卡的基本排查方法

    1. 查看内存使用情况 free -g 当观察到free栏已为0的时候,表示内存基本被吃完了,那就释放内存吧(释放内存参考上篇文章) 2. 查看磁盘使用情况 df -h 当发现磁盘使用率很高时,那就要 ...

  9. pkg-config --libs libusb-1.0

    pkg-config --libs libusb-1.0 pkg-config --libs libusb-1.0 pkg-config --libs libusb-1.0

  10. 使用gson将字符串转换成对象

    Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create(); System.out.pr ...