Android 带你读懂事件分发
工作有一段时间,有必要掌握事件传递的机制,最近研究了一下,记录下心得。
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事件。
后续的action_move,action_up事件不再传递给ViewGroup和View。直接由activity处理。

如果拦截,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 带你读懂事件分发的更多相关文章
- (转载) Android 带清除功能的输入框控件ClearEditText,仿IOS的输入框
Android 带清除功能的输入框控件ClearEditText,仿IOS的输入框 标签: Android清除功能EditText仿IOS的输入框 2013-09-04 17:33 70865人阅读 ...
- Android高级_视频播放控件
一.Android系统自带VideoView控件 1. 创建步骤: (1)自带视频文件放入res/raw文件夹下: (2)声明初始化VideoView控件: (3)创建视频文件Uri路径,Uri调用p ...
- 一文带你读懂什么是vxlan网络
一个执着于技术的公众号 一.背景 随着云计算.虚拟化相关技术的发展,传统网络无法满足大规模.灵活性要求高的云数据中心的要求,于是便有了overlay网络的概念.overlay网络中被广泛应用的就是vx ...
- JS调用Android、Ios原生控件
在上一篇博客中已经和大家聊了,关于JS与Android.Ios原生控件之间相互通信的详细代码实现,今天我们一起聊一下JS调用Android.Ios通信的相同点和不同点,以便帮助我们在进行混合式开发时, ...
- Android Material适配 为控件设置指定背景色和点击波纹效果
Android Material适配 为控件设置指定背景色和点击波纹效果,有需要的朋友可以参考下. 大部分时候,我们都需要为控件设置指定背景色和点击效果 4.x以下可以使用selector,5.0以上 ...
- android中的EditView控件
android中的EditView控件 EditText继承关系:View-->TextView-->EditText ,EditText是可编辑文本框 1.EditText默认情况下,光 ...
- Android 5.0新控件——FloatingActionButton(悬浮按钮)
Android 5.0新控件--FloatingActionButton(悬浮按钮) FloatingActionButton是5.0以后的新控件,一个悬浮按钮,之所以叫做悬浮按钮,主要是因为自带阴影 ...
- Android开发:文本控件详解——TextView(一)基本属性
一.简单实例: 新建的Android项目初始自带的Hello World!其实就是一个TextView. 在activity_main.xml中可以新建TextView,从左侧组件里拖拽到右侧预览界面 ...
- Android-事件分发(ViewGroup)
http://blog.csdn.net/guolin_blog/article/details/9153747 http://blog.csdn.net/lmj623565791/article/d ...
随机推荐
- 代码调试console对象的花式玩法
转自阮一峰http://www.ruanyifeng.com/home.html console.log(),console.info(),console.debug() console.log方法用 ...
- Django框架——基础之模型系统(ORM相关操作)
------------恢复内容开始------------ 1.必定会的十三条! 1.1记忆方法一:(按字母顺序记忆) <1> all(): 查询所有结果 <2> cou ...
- Linux Exploit系列之七 绕过 ASLR -- 第二部分
原文地址:https://github.com/wizardforcel/sploitfun-linux-x86-exp-tut-zh/blob/master/7.md 这一节是简单暴力的一节,作者讲 ...
- SSD源码解读——网络测试
之前,对SSD的论文进行了解读,可以回顾之前的博客:https://www.cnblogs.com/dengshunge/p/11665929.html. 为了加深对SSD的理解,因此对SSD的源码进 ...
- Zookeeper集群快速搭建
Zookeeper集群快速搭建 1.cd /usr/local/zookeeper/conf(如在192.168.212.101服务器) mv zoo_sample.cfg zoo.cfg 修改con ...
- BZOJ1306 [CQOI2009]match循环赛/BZOJ3139 [Hnoi2013]比赛[dfs剪枝+细节题]
地址 看数据范围很明显的搜索题,暴力dfs是枚举按顺序每一场比赛的胜败情况到底,合法就累计.$O(3^{n*(n-1)/2})$.n到10的时候比较大,考虑剪枝. 本人比较菜所以关键性的剪枝没想出来, ...
- jar启动名称示例
nohup java -jar -Dspring.profiles.active=20dev -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+Print ...
- Linux下C++编译(代码高亮自动换行)
1.环境准备 在ubuntu中要想编译c程序可以安装gcc编译器,编译c++的话就不能使用gcc了,要使用g++编译器. 安装gcc或是g++可以在新立得软件包管理器中直接搜索后安装或是使用终端文字命 ...
- Acwing-274-移动服务(DP)
链接: https://www.acwing.com/problem/content/276/ 题意: 一个公司有三个移动服务员,最初分别在位置1,2,3处. 如果某个位置(用一个整数表示)有一个请求 ...
- C# 写日志的方法
public void WriteLog(string msg) { string filePath = AppDomain.CurrentDomain.BaseD ...