转载

Android中事件传递机制的总结

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

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

事件是如何传递的

事件是如何处理的

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

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

一、事件分发的原理:

1、事件是如何传递的:

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

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

如下图所示:

Android中事件传递机制的总结

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

Android中事件传递机制的总结

上图显示:

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

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

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

默认返回false

2、事件是如何处理的:

Android中事件传递机制的总结

再来看下面这张图:

Android中事件传递机制的总结

上图显示: 子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事件,现在运行程序,点击按钮,后台打印的日志如下:

Android中事件传递机制的总结

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

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

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

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

Android中事件传递机制的总结

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

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

Android中事件传递机制的总结

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

Android中事件传递机制的总结

Android中事件传递机制的总结

于是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 }

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

Android中事件传递机制的总结

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

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

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

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

Android中事件传递机制的总结

为什么这样改代码就可以了呢?这就需要从源码的角度来理解了。在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、按照上面的代码,后台日志如下:

Android中事件传递机制的总结

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

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

Android中事件传递机制的总结

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

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

Android中事件传递机制的总结

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

正文到此结束
Loading...