• Android中事件传递机制的总结


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

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

    事件是如何传递的

    事件是如何处理的

    自定义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自己把事件给消费掉了

    我的公众号

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

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

  • 相关阅读:
    被@ResponseBoby注释的方法在拦截器的posthandle方法中设置cookie失效的问题
    python之异常处理
    python之url编码
    python之发送邮件
    python之使用request模块发送post和get请求
    python之小技巧积累
    python之sys.argv[]
    python之MD5加密
    python之os、sys和random模块
    python之time和datetime的常用方法
  • 原文地址:https://www.cnblogs.com/qianguyihao/p/4802274.html
Copyright © 2020-2023  润新知