zoukankan      html  css  js  c++  java
  • 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),欢迎有心人关注。博客园分享技术,公众号分享心智

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

  • 相关阅读:
    UVA 1025 A Spy in the Metro DP水题
    ZOJ 3814 Sawtooth Puzzle BFS
    ZOJ 3816 Generalized Palindromic Number
    UVA 10859 Placing Lampposts 树形DP
    UVA 11825 Hackers' Crackdown 状压DP
    POJ 2887 Big String 线段树 离线处理
    POJ 1635 Subway tree systems Hash法判断有根树是否同构
    BZOJ 3110 k大数查询 & 树套树
    sdoi 2009 & 状态压缩
    来自于2016.2.24的flag
  • 原文地址:https://www.cnblogs.com/qianguyihao/p/4802274.html
Copyright © 2011-2022 走看看