事件传递虽然算不上某个单独的知识点,但是在实际项目开发中肯定会碰到,如果不明白其中的原理,那在设计各种滑动效果时就会感到很困惑。

关于事件的传递,我们可能会有以下疑问:

  • 事件是如何传递的

  • 事件是如何处理的

  • 自定义view的时候,事件也冲突了怎么解决

带着这三个疑问,我们来总结一下事件传递机制是怎么回事。

事件分发原理

事件是如何传递的:

  • 首先由Activity分发,分发给根View,也就是DecorView(DecorView为整个Window界面的最顶层View)

  • 然后由根View分发到子的View

如下图所示:

再来看下面这张图:(这张图是整个事件传递机制的核心)

如上图所示:

在ViewGroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截。

onInterceptTouchEvent方法:

  • 返回true代表不允许事件继续向子View传递,将会触发当前View的onTouchEvent(),进行事件的消费;
  • 返回false代表不对事件进行拦截,事件可以传递给子View;
  • 默认返回false。

事件是如何处理的:

再来看下面这张图:

上图显示:子View中如果将传递的事件消费掉,父类的ViewGroup中将无法接收到任何事件。

onTouch和onClick事件同时发生的问题

首先这里要解释一下各种概念,避免混淆。

各种概念

事件:

  混合体(可能是点击事件也可能是触摸事件)。

触摸事件:

  按下、滑动和离开

点击事件:

  按下、停留一会儿和离开

触摸onTouch事件和点击onClick事件有什么关系?

  1. 执行先后不一样。触摸事件先执行

  2. 触摸事件返回值影响点击事件(前者影响后者,而后者不影响前者)

onTouch和onClick事件同时执行:

如果按钮的onTouch和onClick方法同时执行,会有什么效果呢?我们通过代码来看一下:

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;public class MainActivity extends Activity {private static final String TAG = "MainActivity";private Button btn;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btn = (Button) findViewById(R.id.btn);//按钮的touch触摸事件btn.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN: //按下的动作Log.d(TAG, "btn is MotionEvent.ACTION_DOWN");break;case MotionEvent.ACTION_MOVE: //滑动的动作Log.d(TAG, "btn is MotionEvent.ACTION_MOVE");break;case MotionEvent.ACTION_UP: //离开的动作Log.d(TAG, "btn is MotionEvent.ACTION_UP");break;}return false;  //默认的返回值}});//按钮的点击事件btn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Log.d(TAG, "btn is click");}});}}

上方代码中,按钮btn既包含了onTouch事件,也包含了onClick事件,现在运行程序,点击按钮,后台打印的日志如下:

通过上方日志我们可以看到,onTouch事件是比onClick事件先执行的。

备注:这里提示一下,如果我们仅仅只是用手指点击按钮,然后马上松开,onTouch事件中只会执行ACTION_DOWN和ACTION_UP动作;如果用手机点击按钮,并且手指还在按钮上滑动了一会儿,那么滑动的过程中,ACTION_MOVE动作就会不停的执行。现在我们应该能明白这三个动作的含义了吧?

只执行onTouch事件,不执行onClick事件:

如果按钮的onTouch和onClick方法同时执行,在有些情况下不太满足产品的需求。那如果只想执行onTouch事件,不执行onClick事件,该怎么做呢?很简单,只需要在上方代码中,将第39行的代码改为return true,就行了,即:将onTouch方法的返回值改为true,就会只执行onTouch事件,不执行onClick事件。改完代码之后,后台的运行效果如下:

为什么这样改代码就可以了呢?这就需要从源码的角度来理解了。

button按钮中没有dispatchTouchEvent方法,需要去它的父类View.java中去找dispatchTouchEvent方法。源码如下所示:

上图分析:红框部分的onTouch()方法默认是返回false,所以就会执行蓝框部分的代码,即:调用onTouchEvent()方法。而onTouchEvent方法中,会在ACTION_UP动作里面会去初始化onClick事件。如下图所示:

于是onClick事件就得到了执行。

onClick和onLongClick事件能同时发生:

我们通过代码来演示一下。

1、onTouch事件、onLongClick事件、onClick事件默认是同时执行:(执行的先后顺序:onTouch > onLongClick > onClick)

完整版代码如下:

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;public class MainActivity extends Activity {private static final String TAG = "MainActivity";private Button btn;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btn = (Button) findViewById(R.id.btn);//按钮的touch事件btn.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN: //按下的动作Log.d(TAG, "btn is MotionEvent.ACTION_DOWN");break;case MotionEvent.ACTION_MOVE: //滑动的动作Log.d(TAG, "btn is MotionEvent.ACTION_MOVE");break;case MotionEvent.ACTION_UP: //离开的动作Log.d(TAG, "btn is MotionEvent.ACTION_UP");break;}return false;  //默认的返回值}});//按钮的onLongClick事件btn.setOnLongClickListener(new View.OnLongClickListener() {@Overridepublic boolean onLongClick(View v) {Log.d(TAG, "btn is onLongClick");return false; //默认的返回值}});//按钮的onClick事件btn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Log.d(TAG, "btn is onClick");}});}}

运行程序后,长按按钮,后台日志如下:

源码比较长,就不贴出来了,通过查看源码我们得知,当onTouch事件中的ACTION_DOWN动作执行180ms之后,就会执行onLongClick事件。

那我们现在知道了,如果在一个按钮上按下的时间过长,onLongClick事件会比onClick事件先执行。

2、只执行onTouch事件和onLongClick事件,不执行onClick事件:

为了实现这种逻辑,也很简单,只需要将上方的第50行代码改为return true就行了,即:将onLongClick方法的返回值改为true,就不会执行onClick事件了。改完代码之后,后台的运行效果如下:

为什么这样改代码就可以了呢?这就需要从源码的角度来理解了。在View.java中的dispatchTouchEvent方法里,ACTION_UP动作里面对onLongTouch事件进行了处理,具体源码就不展示出来了,这个有点复杂。

四、事件传递机制调用顺序:

ViewGroup的事件传递方法:

dispatchTouchEvent
onInterceptTouchEvent
onTouchEvent
View的事件传递方法:

View的dispatchTouchEvent
View的onTouchEvent
注意,只有父的ViewGroup容器才有onInterceptTouchEvent方法。这也很好理解,最小的那个子的view没必要再拦截了,因为无法继续向下传递事件,是否拦截已经没有意义了。

接下来,我们用LinearLayout代表ViewGroup,用Button代表子View,然后去重写LinearLayout和Button中的事件传递方法,看一下各个方法的调用顺序。代码如下:

(1)MyLinearLayout.java:(重写LinearLayout中的事件传递方法)

package com.example.smyhvae.touchdemo;import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout;/*** Created by smyhvae on 2015/9/11.*/
public class MyLinearLayout extends LinearLayout {private static final String TAG = "MainActivity";public MyLinearLayout(Context context) {super(context);}public MyLinearLayout(Context context, AttributeSet attrs) {super(context, attrs);}public MyLinearLayout(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN: //按下的动作Log.d(TAG, "ViewGroup dispatchTouchEvent ACTION_DOWN");break;case MotionEvent.ACTION_MOVE: //滑动的动作Log.d(TAG, "ViewGroup dispatchTouchEvent ACTION_MOVE");break;case MotionEvent.ACTION_UP: //离开的动作Log.d(TAG, "ViewGroup dispatchTouchEvent ACTION_UP");break;}return super.dispatchTouchEvent(ev);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN: //按下的动作Log.d(TAG, "ViewGroup onInterceptTouchEvent ACTION_DOWN");break;case MotionEvent.ACTION_MOVE: //滑动的动作Log.d(TAG, "ViewGroup onInterceptTouchEvent ACTION_MOVE");break;case MotionEvent.ACTION_UP: //离开的动作Log.d(TAG, "ViewGroup onInterceptTouchEvent ACTION_UP");break;}return super.onInterceptTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN: //按下的动作Log.d(TAG, "ViewGroup onTouchEvent ACTION_DOWN");break;case MotionEvent.ACTION_MOVE: //滑动的动作Log.d(TAG, "ViewGroup onTouchEvent ACTION_MOVE");break;case MotionEvent.ACTION_UP: //离开的动作Log.d(TAG, "ViewGroup onTouchEvent ACTION_UP");break;}return super.onTouchEvent(event);}
}

(2)MyButton.java:(重写Button中的事件传递方法,注意:这里面没有onInterceptTouchEvent方法)

package com.example.smyhvae.touchdemo;import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.Button;/*** Created by smyhvae on 2015/9/11.*/
public class MyButton extends Button {private static final String TAG = "MainActivity";public MyButton(Context context) {super(context);}public MyButton(Context context, AttributeSet attrs) {super(context, attrs);}public MyButton(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN: //按下的动作Log.d(TAG, "View      dispatchTouchEvent ACTION_DOWN");break;case MotionEvent.ACTION_MOVE: //滑动的动作Log.d(TAG, "View      dispatchTouchEvent ACTION_MOVE");break;case MotionEvent.ACTION_UP: //离开的动作Log.d(TAG, "View      dispatchTouchEvent ACTION_UP");break;}return super.dispatchTouchEvent(event);}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN: //按下的动作Log.d(TAG, "View      onTouchEvent ACTION_DOWN");break;case MotionEvent.ACTION_MOVE: //滑动的动作Log.d(TAG, "View      onTouchEvent ACTION_MOVE");break;case MotionEvent.ACTION_UP: //离开的动作Log.d(TAG, "View      onTouchEvent ACTION_UP");break;}return true;}
}

上方代码中,将onTouchEvent方法的返回值修改为true(59行),表示这个子的view希望消费这个事件。

(3)activity_main.xml:

<com.example.smyhvae.touchdemo.MyLinearLayoutxmlns: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=".MainActivity"><com.example.smyhvae.touchdemo.MyButtonandroid:id="@+id/btn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="按钮"/></com.example.smyhvae.touchdemo.MyLinearLayout>

上面的xml中,将我们自定义的MyLinearLayout和MyButton用上了。

(4)MainActivity.java:

package com.example.smyhvae.touchdemo;import android.app.Activity;
import android.os.Bundle;
import android.widget.Button;public class MainActivity extends Activity {private static final String TAG = "MainActivity";private Button btn;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btn = (Button) findViewById(R.id.btn);   }
}

分析之前,我们先记住下面这句话:(记住这句话,分析下面的日志就好理解了)

在Android中,一切事件处理的开始都是从Down事件开始的,如何你处理了Down事件,其他的事件就都收不到了。

1、按照上面的代码,后台日志如下:

通过上图的箭头处可以看到,事件是传递给了子view去消费。

2、上面的代码中,将MyLinearLayout.java中onTouchEvent方法返回值修改为true,将MyButton.java中的onTouchEvent方法返回值修改为false,后台日志如下:

通过上图的箭头处可以看到,事件是传递给了子view,子view说它不消费了,于是又回传给父的ViewGroup,ViewGroup消费了这个事件。

3、上面的代码中,将MyLinearLayout.java中onTouchEvent方法返回值修改为true,将MyLinearLayout.java中onInterceptTouchEvent方法返回值修改为true,后台日志如下:

通过上图的箭头处可以看到,此时ViewGroup已经将事件拦截了,所以根本就不会传递给子的Veiw,父的ViewGroup自己把事件给消费掉了。

Android中事件传递机制的总结相关推荐

  1. Android Touch事件传递机制 二:单纯的(伪生命周期) 这个清楚一点

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

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

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

  3. Android之Android触摸事件传递机制

    转载请标明出处: http://blog.csdn.net/hai_qing_xu_kong/article/details/53431274 本文出自:[顾林海的博客] ##前言 关于Android ...

  4. Android中事件分发机制的总结

    原文出处:http://blog.csdn.net/jdsjlzx/article/details/52355249 事件传递虽然算不上某个单独的知识点,但是在实际项目开发中肯定会碰到,如果不明白其中 ...

  5. Android Touch事件传递机制解析 (推荐)

    最近新闻列表里的下拉 down up  move 等等让我十分头疼 ,无意间看到了一篇非常不错的帖子,转载如下: 开篇语:最近程序在做一个小效果,要用到touch,结果整得云里面雾里的,干脆就好好把a ...

  6. android方向触摸事件,Android触摸事件传递机制,这一篇就够了

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

  7. Android onTouch事件传递机制

    Android onTouch事件介绍: Android的触摸事件:onClick, onScroll, onFling等等,都是由许多个Touch组成的.其中Touch的第一个状态肯定是ACTION ...

  8. Android 触摸事件传递机制

    android系统中的每个View的子类都具有下面三个和TouchEvent处理密切相关的方法: 1)public boolean dispatchTouchEvent(MotionEvent ev) ...

  9. Android事件传递机制(转)

    Android事件构成 在Android中,事件主要包括点按.长按.拖拽.滑动等,点按又包括单击和双击,另外还包括单指操作和多指操作.所有这些都构成了Android中的事件响应.总的来说,所有的事件都 ...

最新文章

  1. 爬一爬那些年你硬盘存过的“老师”
  2. 23、OSPF配置实验之特殊区域Totally NSSA
  3. 手把手教你安装深度学习软件环境(附代码)
  4. java基础---JVM---java内存区域与内存溢出问题
  5. 【Paper】2007_Consensus control for a class of networks of dynamic agents 二阶静态一致性
  6. html标签体,HTMLCSS基础-html标签的实体
  7. 小米回应将放弃“MI”字logo:不存在停止使用
  8. 加拿大大学计算机排名2015,加拿大大学计算机排名
  9. WebSocket简单使用
  10. tortoise从服务器获取项目_项目中一次网络问题处理的复盘
  11. 02 Apache Solr: 概览 Solr在信息系统架构中的位置
  12. Transformations on DStreams之transform的使用 实现黑名单操作/指定过滤
  13. T-SQL查询进阶—理解SQL Server中的锁
  14. APP开发策划方案怎么写?
  15. 使用Colab对wget下载加速
  16. openpyxl实现表头隔行插入及合并单元格
  17. 计算机基础长文档的排版,大学计算机基础中Word长文档排版的教学思考
  18. 学单片机有前途还是嵌入式系统有前途?
  19. 六、流行框架介绍(SpringBoot框架详解(含底层原理介绍,适用于springBoot1.x和springBoot2.x,属于通用版本))
  20. OCR表格识别—(一)

热门文章

  1. sdcms栏目图片的调用方法
  2. 2023小米未来星专项招聘正式启动
  3. Unity3D error: ArgumentException: Key duplication when adding: XXX
  4. 兔子繁殖问题(斐波那契数列c语言版)
  5. ARMA 时间序列模型
  6. 最新县及县以上行政区划代码(截止2013年1月18日) 全国省市县数据库 之县级数据
  7. 来自老男孩的五篇精彩博文
  8. 开发工具-PxCook
  9. 关于Python去掉字符串中的空格
  10. flask 下载文件