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

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

事件是如何传递的

事件是如何处理的

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

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

一、事件分发的原理:

1、事件是如何传递的:

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

(2)然后由根View分发到子的View

如下图所示:

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

上图显示:

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

    返回true代表不允许事件继续向子View传递,将会触发当前View的onTouchEvent(),进行事件的消费;

    返回false代表不对事件进行拦截,事件可以传递给孩子

    默认返回false

2、事件是如何处理的:

再来看下面这张图:

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

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

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

1、各种概念:

事件:

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

触摸事件:

  按下、滑动和离开

点击事件:

  按下、停留一会儿和离开

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

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

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

 

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; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button) findViewById(R.id.btn); //按钮的touch触摸事件
btn.setOnTouchListener(new View.OnTouchListener() {
@Override
public 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() {
@Override
public void onClick(View v) {
Log.d(TAG, "btn is click");
}
});
} }

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

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

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

3、只执行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; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button) findViewById(R.id.btn); //按钮的touch事件
btn.setOnTouchListener(new View.OnTouchListener() {
@Override
public 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() {
@Override
public boolean onLongClick(View v) { Log.d(TAG, "btn is onLongClick"); return false; //默认的返回值
}
});
//按钮的onClick事件
btn.setOnClickListener(new View.OnClickListener() {
@Override
public 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);
} @Override
public 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);
} @Override
public 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);
} @Override
public 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);
} @Override
public 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);
} @Override
public 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.MyLinearLayout
xmlns: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.MyButton
android: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; @Override
protected 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自己把事件给消费掉了

我的公众号

下图是我的微信公众号(生命团队id:vitateam),欢迎有心人关注。博客园分享技术,公众号分享心智

我会很感激第一批关注我的人。此时,年轻的我和你,一无所有;而后,富裕的你和我,满载而归。

Android中事件传递机制的总结的更多相关文章

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

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

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

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

  3. Android touch 事件传递机制

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

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

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

  5. Android Touch事件传递机制通俗讲解

    在讲正题之前我们讲一段有关任务传递的小故事,抛砖迎玉下: 话说一家软件公司,来一个任务,分派给了开发经理去完成: 开发经理拿到,看了一下,感觉好简单,于是 开发经理:分派给了开发组长 开发组长:分派给 ...

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

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

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

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

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

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

  9. Android触摸事件传递机制

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

随机推荐

  1. Java2_J2EE体系架构

    J2EE是Java2平台企业版(Java 2 Platform,Enterprise Edition),它的核心是一组技术规范与指南,提供基于组件的方式来设计.开发.组装和部署企业应用.J2EE使用多 ...

  2. 创建和删除节点:——核心DOM

    1. 创建单个元素节点:3步:       1. 创建空元素节点对象:          var elem=document.createElement("标签名");      ...

  3. Git本地仓库

    原文:http://www.cnblogs.com/wilber2013/p/4189920.html Git基本概念 在Git中,我们将需要进行版本控制的文件目录叫做一个仓库(repository) ...

  4. OPENGLES 基础(一些链接和随笔)

    http://imgtec.eetrend.com/blog/3912 http://blog.csdn.net/zj8792612/article/details/16116145   在线着色器编 ...

  5. 1分钟实现Autodesk Vault登录对话框

      .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courie ...

  6. JavaScript学习03 JS函数

    JavaScript学习03 JS函数 函数就是包裹在花括号中的代码块,前面使用了关键词function: function functionName() { 这里是要执行的代码 } 函数参数 函数的 ...

  7. iOS7.0后隐藏状态栏(UIStatusBar)

    现象: 升级到iOS7后,UIStatusBar的出现导致现有UI界面乱掉了. 原因: 由于写死了某些控件的绝对位置,原先隐藏UIStatusBar的代码没有在iOS7中起作用 解决方法: iOS7以 ...

  8. Spring MVC 原理小结

    主要由DispatcherServlet.处理器映射.处理器.视图解析器.视图组成   1.DispatcherServlet接收到一个HTTP请求,根据对应配置文件中的处理机映射,找到处理器(Han ...

  9. 第一个WPF应用程序

    WPF 全称为 Windows Presentation Foundation. 核心特性: WPF使用一种新的XAML(Extensible Application Markup Language) ...

  10. 转载文章----初识Ildasm.exe——IL反编译的实用工具

    转载地址http://www.cnblogs.com/yangmingming/archive/2010/02/03/1662307.html Ildasm.exe 概要:(路径:C:\Program ...