关闭

Android中事件传递机制的总结

标签: android事件传递
1383人阅读 评论(1) 收藏 举报
分类:

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

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

  • 事件是如何传递的

  • 事件是如何处理的

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

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

事件分发原理

事件是如何传递的:

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

  • 然后由根View分发到子的View

如下图所示:
这里写图片描述

再来看下面这张图:(这张图是整个事件传递机制的核心)
这里写图片描述

如上图所示:

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

onInterceptTouchEvent方法:

  • 返回true代表不允许事件继续向子View传递,将会触发当前View的onTouchEvent(),进行事件的消费;
  • 返回false代表不对事件进行拦截,事件可以传递给子View;
  • 默认返回false。

事件是如何处理的:

这里写图片描述

再来看下面这张图:

这里写图片描述

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

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

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

各种概念

事件:

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

触摸事件:

  按下、滑动和离开

点击事件:

  按下、停留一会儿和离开

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

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

  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动作就会不停的执行。现在我们应该能明白这三个动作的含义了吧?

只执行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自己把事件给消费掉了。

2
0
查看评论

Android Touch事件传递机制全面解析(从WMS到View树)

转眼间近一年没更新博客了,工作一忙起来,很难有时间来写博客了,由于现在也在从事Android开发相关的工作,因此以后的博文也会更多地专注于这一块。 这篇文章准备从源码层面为大家带来Touch事件的传递机制,我这里分析的源码时Android4.4的。说到分析源码,光看肯定是不行的,一定要亲自去跟,并...
  • mmc_maodun
  • mmc_maodun
  • 2015-12-21 07:43
  • 8358

Android事件分发传递回传机制详解

如果想参与实际开发项目,若不理解事件分发回传机制的话,几乎等于“”摸黑抓鳅”,因为几乎每个项目都会出现滑动冲突问题;而要想解决滑动冲突问题,必须先了解甚至掌握事件分发传递机制。等到了解决滑动冲突时,至少没有一种“断层”的感觉。所以本专栏开篇先介绍事件机制,对安卓中的事件机制,做一个详细的介绍与分析。...
  • qq_32059827
  • qq_32059827
  • 2016-09-18 22:54
  • 1401

android 事件机制图文详解-从源码角度分析彻底理解事件传递机制(上)

最近在看android开发艺术一书,因此对android 事件机制有更深层次的理解,在此记录一遍 探讨Android事件传递机制前,务必明确android的两大基础控件类型:View和ViewGroup。 View即普通的控件,没有子布局的,如Button、TextView. ViewGrou...
  • javazejian
  • javazejian
  • 2016-01-07 23:50
  • 1403

Android事件传递流程-伪代码

android 事件传递伪代码
  • u012227177
  • u012227177
  • 2017-04-24 19:36
  • 350

Android 事件传递机制(简要总结)

最近在开发房价图的时候正好碰到事件的传递问题,以前也没有系统的研究过,只知道个大概,这次正好研究下,做一个总结。 Android的事件机制用一句话来说就是:事件的分发是自上而下的,而事件的处理是自下而上的,整个事件的传递是U型的。 ViewGroup的touch事件的官方说明。 关于touch...
  • u011494050
  • u011494050
  • 2014-05-09 12:45
  • 767

Android触摸屏事件派发机制详解与源码分析一(View篇)

PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN。因为CSDN也支持MarkDown语法了,牛逼啊!【工匠若水 http://blog.csdn.net/yanbober】Notice:阅读完该篇之后如果想继续深入阅读Android触摸屏事件派发机制详解与源码分析下一...
  • yanbober
  • yanbober
  • 2015-05-21 17:03
  • 50998

Android自定义view之事件传递机制

Android自定义view之事件传递机制 本文是自定义view入门的第二篇文章,主要讲解Android的事件分发流程,结合上一篇的绘制等流程,基本就可以掌握自定义view的核心内容。后续我会写一些实际的小例子,加深理解。
  • zuguorui
  • zuguorui
  • 2017-08-08 23:27
  • 330

Android-三张图搞定Touch事件传递机制

上图之前先讲下Android事件的基础知识:1.所有的Touch事件都封装到MotionEvent里面2.事件处理包括三种情况,分别为:传递—-dispatchTouchEvent()函数、拦截——onInterceptTouchEvent()函数、消费—-onTouchEvent()函数和OnTo...
  • Bond_zhe
  • Bond_zhe
  • 2016-08-13 18:27
  • 441

android中事件传递机制(2)

事件传递机制(1)写这个博客前先补充一下上一个博客的部分内容,我们上一个博客介绍了viewGroup中的代码,那么viewGroup中的分发函数由谁来调用的呢?,当然是activity。 1.首先我们看一下activity中的代码 public boolean dispatchTouchEve...
  • qq_22157767
  • qq_22157767
  • 2016-04-05 18:13
  • 229

Android点击事件传递机制详解

在讲正题之前我们讲一段有关任务传递的小故事,抛砖迎玉下: 话说一家软件公司,来一个任务,分派给了开发经理去完成: 开发经理拿到,看了一下,感觉好简单,于是 开发经理:分派给了开发组长 开发组长:分派给了自己组员(程序员) 程序员:分派给了自己带的实习生。 实习生:好苦逼,无法分派,怎么办啊?只能...
  • RuingMan
  • RuingMan
  • 2016-05-16 15:45
  • 3355
    个人资料
    • 访问:11108339次
    • 积分:77452
    • 等级:
    • 排名:第24名
    • 原创:506篇
    • 转载:912篇
    • 译文:4篇
    • 评论:2264条
    打赏
    如果您认为本博客不错,读后觉得有收获,不妨打赏赞助我一下,让我有动力继续写出高质量的博客。



    赠人玫瑰,手有余香。分享技术,传递快乐。

    有心课堂,传递的不仅仅是技术!

    QQ交流群:250468947

    有心课堂会员,请加入VIP QQ交流群:213725333

    github
    我的视频
    博客专栏
    最新评论