Android中事件传递机制的总结

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介:

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

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

事件是如何传递的

事件是如何处理的

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

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

 

一、事件分发的原理:

1、事件是如何传递的:

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

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

如下图所示:

a981bf33-fe66-4914-9558-4751c474b19f

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

68067bcc-3d67-4eaf-98db-8906dcd4b521

上图显示:

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

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

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

    默认返回false

 

2、事件是如何处理的:

86ff9936-f642-4bc5-ac26-d1e2f0ba8c67

再来看下面这张图:

ba0b3001-afde-4e13-8819-d735293b2526

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

 

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

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

1、各种概念:

事件:

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

触摸事件:

  按下、滑动和离开

点击事件:

  按下、停留一会儿和离开

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

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

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

 

2、onTouch和onClick事件同时执行

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

复制代码
 1 import android.app.Activity;
 2 import android.os.Bundle;
 3 import android.util.Log;
 4 import android.view.MotionEvent;
 5 import android.view.View;
 6 import android.widget.Button;
 7 
 8 public class MainActivity extends Activity {
 9 
10     private static final String TAG = "MainActivity";
11     private Button btn;
12 
13     @Override
14     protected void onCreate(Bundle savedInstanceState) {
15         super.onCreate(savedInstanceState);
16         setContentView(R.layout.activity_main);
17         btn = (Button) findViewById(R.id.btn);
18 
19         //按钮的touch触摸事件
20         btn.setOnTouchListener(new View.OnTouchListener() {
21             @Override
22             public boolean onTouch(View v, MotionEvent event) {
23                 switch (event.getAction()) {
24                     case MotionEvent.ACTION_DOWN: //按下的动作
25                         Log.d(TAG, "btn is MotionEvent.ACTION_DOWN");
26                         break;
27                     case MotionEvent.ACTION_MOVE: //滑动的动作
28                         Log.d(TAG, "btn is MotionEvent.ACTION_MOVE");
29                         break;
30                     case MotionEvent.ACTION_UP: //离开的动作
31                         Log.d(TAG, "btn is MotionEvent.ACTION_UP");
32                         break;
33                 }
34 
35                 return false;  //默认的返回值
36             }
37         });
38 
39         //按钮的点击事件
40         btn.setOnClickListener(new View.OnClickListener() {
41             @Override
42             public void onClick(View v) {
43                 Log.d(TAG, "btn is click");
44             }
45         });
46     }
47 
48 }
复制代码

 

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

8a294e91-95fd-40e0-ba7a-c04a4cc0e0c3

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

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

 

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

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

3387cba5-3e8d-4eba-afae-efdd8a881491

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

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

ccd3f0f2-1fcc-4abf-9f17-bf41a8c58b6c

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

a8bef793-7950-4dcb-854a-9bedd1af06e0

407110d1-630d-4d88-a294-85231d0ad447

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

 

三、onClick和onLongClick事件能同时发生:

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

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

完整版代码如下:

复制代码
 1 import android.app.Activity;
 2 import android.os.Bundle;
 3 import android.util.Log;
 4 import android.view.MotionEvent;
 5 import android.view.View;
 6 import android.widget.Button;
 7 
 8 public class MainActivity extends Activity {
 9 
10     private static final String TAG = "MainActivity";
11     private Button btn;
12 
13     @Override
14     protected void onCreate(Bundle savedInstanceState) {
15         super.onCreate(savedInstanceState);
16         setContentView(R.layout.activity_main);
17         btn = (Button) findViewById(R.id.btn);
18 
19         //按钮的touch事件
20         btn.setOnTouchListener(new View.OnTouchListener() {
21             @Override
22             public boolean onTouch(View v, MotionEvent event) {
23 
24                 switch (event.getAction()) {
25                     case MotionEvent.ACTION_DOWN: //按下的动作
26                         Log.d(TAG, "btn is MotionEvent.ACTION_DOWN");
27                         break;
28 
29                     case MotionEvent.ACTION_MOVE: //滑动的动作
30                         Log.d(TAG, "btn is MotionEvent.ACTION_MOVE");
31                         break;
32 
33                     case MotionEvent.ACTION_UP: //离开的动作
34                         Log.d(TAG, "btn is MotionEvent.ACTION_UP");
35                         break;
36                 }
37 
38                 return false;  //默认的返回值
39             }
40         });
41 
42 
43         //按钮的onLongClick事件
44         btn.setOnLongClickListener(new View.OnLongClickListener() {
45             @Override
46             public boolean onLongClick(View v) {
47 
48                 Log.d(TAG, "btn is onLongClick");
49 
50                 return false; //默认的返回值
51             }
52         });
53         //按钮的onClick事件
54         btn.setOnClickListener(new View.OnClickListener() {
55             @Override
56             public void onClick(View v) {
57                 Log.d(TAG, "btn is onClick");
58             }
59         });
60     }
61 
62 }
复制代码

 

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

68506973-12bc-41f8-a60c-979ce0afb252

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

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

 

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

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

f2303051-81ad-434d-935b-f0946f53a463

为什么这样改代码就可以了呢?这就需要从源码的角度来理解了。在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中的事件传递方法)

复制代码
 1 package com.example.smyhvae.touchdemo;
 2 
 3 import android.content.Context;
 4 import android.util.AttributeSet;
 5 import android.util.Log;
 6 import android.view.MotionEvent;
 7 import android.widget.LinearLayout;
 8 
 9 /**
10  * Created by smyhvae on 2015/9/11.
11  */
12 public class MyLinearLayout extends LinearLayout {
13 
14     private static final String TAG = "MainActivity";
15 
16     public MyLinearLayout(Context context) {
17         super(context);
18     }
19 
20     public MyLinearLayout(Context context, AttributeSet attrs) {
21         super(context, attrs);
22     }
23 
24     public MyLinearLayout(Context context, AttributeSet attrs, int defStyle) {
25         super(context, attrs, defStyle);
26     }
27 
28     @Override
29     public boolean dispatchTouchEvent(MotionEvent ev) {
30 
31         switch (ev.getAction()) {
32             case MotionEvent.ACTION_DOWN: //按下的动作
33                 Log.d(TAG, "ViewGroup dispatchTouchEvent ACTION_DOWN");
34                 break;
35             case MotionEvent.ACTION_MOVE: //滑动的动作
36                 Log.d(TAG, "ViewGroup dispatchTouchEvent ACTION_MOVE");
37                 break;
38             case MotionEvent.ACTION_UP: //离开的动作
39                 Log.d(TAG, "ViewGroup dispatchTouchEvent ACTION_UP");
40                 break;
41         }
42 
43         return super.dispatchTouchEvent(ev);
44     }
45 
46     @Override
47     public boolean onInterceptTouchEvent(MotionEvent ev) {
48 
49         switch (ev.getAction()) {
50             case MotionEvent.ACTION_DOWN: //按下的动作
51                 Log.d(TAG, "ViewGroup onInterceptTouchEvent ACTION_DOWN");
52                 break;
53             case MotionEvent.ACTION_MOVE: //滑动的动作
54                 Log.d(TAG, "ViewGroup onInterceptTouchEvent ACTION_MOVE");
55                 break;
56             case MotionEvent.ACTION_UP: //离开的动作
57                 Log.d(TAG, "ViewGroup onInterceptTouchEvent ACTION_UP");
58                 break;
59         }
60 
61         return super.onInterceptTouchEvent(ev);
62     }
63 
64     @Override
65     public boolean onTouchEvent(MotionEvent event) {
66 
67         switch (event.getAction()) {
68             case MotionEvent.ACTION_DOWN: //按下的动作
69                 Log.d(TAG, "ViewGroup onTouchEvent ACTION_DOWN");
70                 break;
71             case MotionEvent.ACTION_MOVE: //滑动的动作
72                 Log.d(TAG, "ViewGroup onTouchEvent ACTION_MOVE");
73                 break;
74             case MotionEvent.ACTION_UP: //离开的动作
75                 Log.d(TAG, "ViewGroup onTouchEvent ACTION_UP");
76                 break;
77         }
78 
79         return super.onTouchEvent(event);
80     }
81 }
复制代码

 

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

复制代码
 1 package com.example.smyhvae.touchdemo;
 2 
 3 import android.content.Context;
 4 import android.util.AttributeSet;
 5 import android.util.Log;
 6 import android.view.MotionEvent;
 7 import android.widget.Button;
 8 
 9 /**
10  * Created by smyhvae on 2015/9/11.
11  */
12 public class MyButton extends Button {
13     private static final String TAG = "MainActivity";
14 
15     public MyButton(Context context) {
16         super(context);
17     }
18 
19     public MyButton(Context context, AttributeSet attrs) {
20         super(context, attrs);
21     }
22 
23     public MyButton(Context context, AttributeSet attrs, int defStyle) {
24         super(context, attrs, defStyle);
25     }
26 
27     @Override
28     public boolean dispatchTouchEvent(MotionEvent event) {
29         switch (event.getAction()) {
30             case MotionEvent.ACTION_DOWN: //按下的动作
31                 Log.d(TAG, "View      dispatchTouchEvent ACTION_DOWN");
32                 break;
33             case MotionEvent.ACTION_MOVE: //滑动的动作
34                 Log.d(TAG, "View      dispatchTouchEvent ACTION_MOVE");
35                 break;
36             case MotionEvent.ACTION_UP: //离开的动作
37                 Log.d(TAG, "View      dispatchTouchEvent ACTION_UP");
38                 break;
39         }
40 
41         return super.dispatchTouchEvent(event);
42     }
43 
44     @Override
45     public boolean onTouchEvent(MotionEvent event) {
46         switch (event.getAction()) {
47             case MotionEvent.ACTION_DOWN: //按下的动作
48                 Log.d(TAG, "View      onTouchEvent ACTION_DOWN");
49                 break;
50             case MotionEvent.ACTION_MOVE: //滑动的动作
51                 Log.d(TAG, "View      onTouchEvent ACTION_MOVE");
52                 break;
53             case MotionEvent.ACTION_UP: //离开的动作
54                 Log.d(TAG, "View      onTouchEvent ACTION_UP");
55                 break;
56         }
57 
58         return true;
59     }
60 }
复制代码

 

上方代码中,将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:

复制代码
 1 package com.example.smyhvae.touchdemo;
 2 
 3 import android.app.Activity;
 4 import android.os.Bundle;
 5 import android.widget.Button;
 6 
 7 public class MainActivity extends Activity {
 8 
 9     private static final String TAG = "MainActivity";
10     private Button btn;
11 
12     @Override
13     protected void onCreate(Bundle savedInstanceState) {
14         super.onCreate(savedInstanceState);
15         setContentView(R.layout.activity_main);
16         btn = (Button) findViewById(R.id.btn);   
17     }
18 }
复制代码

 

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

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

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

1e8f1be4-ba35-4338-a3b4-e177936df75d

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

 

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

ab3a7724-bdfa-487c-b616-ad6bf9dcafc2

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

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

e0114a8f-013c-472d-a6b5-dbc9b0e94a75

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

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
7月前
|
前端开发 编译器 Android开发
构建高效Android应用:探究Kotlin协程的异步处理机制
【4月更文挑战第2天】在现代移动应用开发中,提供流畅且响应迅速的用户体验是至关重要的。随着Android平台的发展,Kotlin语言凭借其简洁性和功能性编程的特点成为了主流选择之一。特别地,Kotlin协程作为一种新型的轻量级线程管理机制,为开发者提供了强大的异步处理能力,从而显著提升了应用程序的性能和响应速度。本文将深入探讨Kotlin协程在Android中的应用,分析其原理、实现以及如何通过协程优化应用性能。
|
8天前
|
算法 Linux 调度
深入探索安卓系统的多任务处理机制
【10月更文挑战第21天】 本文旨在为读者提供一个关于Android系统多任务处理机制的全面解析。我们将从Android操作系统的核心架构出发,探讨其如何管理多个应用程序的同时运行,包括进程调度、内存管理和电量优化等方面。通过深入分析,本文揭示了Android在处理多任务时所面临的挑战以及它如何通过创新的解决方案来提高用户体验和设备性能。
17 1
|
7月前
|
存储 Java Android开发
Android系统升级的机制概要
Android系统升级的机制概要
123 0
|
13天前
|
存储 安全 Android开发
探索Android与iOS的隐私保护机制
在数字化时代,移动设备已成为我们生活的一部分,而隐私安全是用户最为关注的问题之一。本文将深入探讨Android和iOS两大主流操作系统在隐私保护方面的策略和实现方式,分析它们各自的优势和不足,以及如何更好地保护用户的隐私。
|
2月前
|
消息中间件 存储 Java
Android消息处理机制(Handler+Looper+Message+MessageQueue)
Android消息处理机制(Handler+Looper+Message+MessageQueue)
41 2
|
2月前
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
|
2月前
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
53 1
|
2月前
|
存储 安全 数据安全/隐私保护
探索安卓与iOS的隐私保护机制####
【10月更文挑战第15天】 本文深入剖析了安卓和iOS两大操作系统在隐私保护方面的策略与技术实现,旨在揭示两者如何通过不同的技术手段来保障用户数据的安全与隐私。文章将逐一探讨各自的隐私控制功能、加密措施以及用户权限管理,为读者提供一个全面而深入的理解。 ####
59 1
|
2月前
|
消息中间件 存储 Java
Android消息处理机制(Handler+Looper+Message+MessageQueue)
Android消息处理机制(Handler+Looper+Message+MessageQueue)
47 2
|
3月前
|
存储 缓存 Android开发
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,详细解析了 `RecyclerView` 的缓存机制,包括多级缓存的原理与流程,并提供了常见面试题及答案。通过本文,你将深入了解 `RecyclerView` 的高性能秘诀,提升列表和网格的开发技能。
74 8