Android中事件分发机制

本文详细解析了Android中Touch事件的分发机制,包括dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent的作用及流程,并探讨了onClick事件与Touch事件的关系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在 Android 的诸多点击事件中,似乎存在很多问题,为什么图片轮播器里的图片使用ImageView无法监听?为什么按钮和布局同时监听会相互干扰?类似一系列的问题本质上就是 Android 中事件分发机制的问题,只有理解事件分发机制,我们才能对事件响应作出正确的判断。

概述

我们通过一个例子来看看事件分发机制的过程,我们有这样一个简单的界面,一个 Activity 中有一个全屏的MyLayout(继承自Linearlayout),在MyLayout中间有一个按钮 MyButton (继承自 Button),上面写着CLICK ME
这里写图片描述

代码如下:

<?xml version="1.0" encoding="utf-8"?>
<com.example.hkxlegend.shijianfenfa.app.MyLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center">

    <com.example.hkxlegend.shijianfenfa.app.MyButton
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="click me" />

</com.example.hkxlegend.shijianfenfa.app.MyLayout>

然后我们看一下Activity、自定义ViewGroup和MyButton的代码

主Activity的代码:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "tag";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.v(TAG, "activity-touchEvent" + event.getAction());
        return super.onTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.v(TAG, "activity-dispatchTouchEvent" + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }
}

MyLayout的代码:

/**
 * @since 2016
 */
public class MyLayout extends LinearLayout {

    private static final String TAG = "tag";

    public MyLayout(Context context) {
        super(context);
    }

    public MyLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.v(TAG,"layout-touchEvent"+event.getAction());
        return super.onTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.v(TAG,"layout-dispatchTouchEvent"+ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.v(TAG,"layout-interceptTouchEvent"+ev.getAction());
        return super.onInterceptTouchEvent(ev);
    }
}

MyButton的代码:

/**
 * @since 2016
 */
public class MyButton extends Button {

    private static final String TAG = "tag";

    public MyButton(Context context) {
        super(context);
    }

    public MyButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.v(TAG, "button-dispatchTouchEvent" + event.getAction());
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.v(TAG, "button=touchEvent" + event.getAction());
        return super.onTouchEvent(event);
    }
}

然后我们点击一下界面上的按钮,通过Log日志,我们可以看到如下输出
这里写图片描述

在Android中,Touch事件都是从ACTION_DOWN开始的,一次完整的触摸事件中,Down和Up都只有一个,Move有若干个。触摸事件的传递过程主要涉及三个Touch事件:dispatchTouchEvent、onInterceptTouchEvent 和 onTouchEvent。从上面的Log日志我们可以看出来,Event0代表着Down事件,Event1代表着Up事件,Event2代表着Move事件。

dispatchTouchEvent:这个方法用来分发TouchEvent(Activity/ViewGroup/View)

onInterceptTouchEvent:这个方法用来拦截TouchEvent,默认返回false,返回true表示拦截(ViewGroup)

onTouchEvent:这个方法用来处理TouchEvent(Activity/ViewGroup/View)

下面我们就从这三个事件开始说起


Touch 事件

Android中默认情况下事件传递是由最终的view的接收到,传递过程是从父布局到子布局,也就是从Activity到ViewGroup到View的过程,默认情况,ViewGroup起到的是透传作用。总的事件流程我们可以根据这个图看出来
这里写图片描述


dispatchTouchEvent事件

dispatchTouchEvent 会将一个Touch事件进行分发,事件分发时从根节点的ViewGroup分发开始,ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViewGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法来向下分发

ViewGroup的dispatchTouchEvent是真正在执行“分发”工作,而View的dispatchTouchEvent方法,并不执行分发工作,根据源码我们可以看到

public boolean dispatchTouchEvent(MotionEvent event) {  
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
            mOnTouchListener.onTouch(this, event)) {  
        return true;  
    }  
    return onTouchEvent(event);  
}  

在View中,首先有一个判断,这三个条件分别是:是否给控件注册了touch事件 & 判断当前点击的控件是否是enable的(button默认是enable) & 回调控件注册touch事件时的onTouch方法,也就是说如果我们在onTouch方法里返回true,就会让这三个条件全部成立;
满足以上三个条件,整个方法直接返回true。如果我们在onTouch方法里返回false,就会再去执行onTouchEvent(event)方法。

 一般情况下,我们不该在普通View内重写dispatchTouchEvent方法,因为它并不执行分发逻辑。当Touch事件到达View时,我们该做的就是是否在onTouchEvent事件中处理它。

关注返回值:

  • 默认:默认继续传递事件
  • true:事件会分发给当前 View 并由 dispatchTouchEvent 方法进行消费,同时事件会停止向下传递
  • false:会将事件返回给父 View 的 onTouchEvent 进行消费
    如果当前 View 获取的事件直接来自 Activity,则会将事件返回给 Activity 的 onTouchEvent 进行消费
    如果当前 View 获取的事件来自外层父控件,则会将事件返回给父 View 的 onTouchEvent 进行消费

在项目开始阶段,每个返回值都是默认super形式的,这样的形式会形成一个完整的事件传递(如上图),现在我们通过改动返回值测试一下其他情况,测试中全部用Event0事件举例

1. 将Activity中dispatchTouchEvent的值改为true和false
  • 当返回值为true时

V/tag: activity-dispatchTouchEvent0

返回为true时候,事件分发停止,由本Activity的dispatchTouchEvent方法处理事件

  • 当返回值为false时

V/tag: activity-dispatchTouchEvent0

交由父View的onTouchEvent事件,由于没有父View,没有后续执行步骤


2. 将MyLayout中dispatchTouchEvent的值改为true和false
  • 当返回值为true时

V/tag: activity-dispatchTouchEvent0
V/tag: layout-dispatchTouchEvent0

返回为true时候,事件分发停止,由本MyLayout的dispatchTouchEvent方法处理事件

  • 当返回值为false时

V/tag: activity-dispatchTouchEvent0
V/tag: layout-dispatchTouchEvent0
V/tag: activity-touchEvent0

交由父View即Activity的onTouchEvent事件执行


3. 将MyButton中dispatchTouchEvent的值改为true和false

当返回值为true时

V/tag: activity-dispatchTouchEvent0
V/tag: layout-dispatchTouchEvent0
V/tag: layout-interceptTouchEvent0
V/tag: button-dispatchTouchEvent0

事件被MyButton的dispatchTouchEvent本身消费

当返回值为false时

V/tag: activity-dispatchTouchEvent0
V/tag: layout-dispatchTouchEvent0
V/tag: layout-interceptTouchEvent0
V/tag: button-dispatchTouchEvent0
V/tag: layout-touchEvent0

分发给父View的onTouchEvent方法进行了消费

通过以上的测试,我们就可以知道dispatchTouchEvent的事件分发是什么形式,总结起来可以归类为一张图,图上标注的都是dispatchTouchEvent事件的返回值
这里写图片描述


onInterceptTouchEvent事件

onInterceptTouchEvent这个方法的返回值是最简单的,表示是否拦截事件,这个只有在ViewGroup组件中存在,所以ViewGroup可以通过onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true代表不允许事件继续向子View传递,事件交由本ViewGroup的onTouchEvent事件处理;返回false代表不对事件进行拦截,默认返回false。


onTouchEvent事件

onTouchEvent 的默认返回值为true ,表示对事件进行了处理

如果某个控件的onTouchEvent返回值为true,ACTION_DOWN以及后续的n个ACTION_MOVE与1个ACTION_UP都会逐层传递到这个控件的onTouchEvent进行处理。我们在MyBotton的onTouchEvent方法中返回true(默认值),可以看到Log日志:

V/tag: activity-dispatchTouchEvent0
V/tag: layout-dispatchTouchEvent0
V/tag: layout-interceptTouchEvent0
V/tag: button-dispatchTouchEvent0
V/tag: button-touchEvent0
V/tag: activity-dispatchTouchEvent2
V/tag: layout-dispatchTouchEvent2
V/tag: layout-interceptTouchEvent2
V/tag: button-dispatchTouchEvent2
V/tag: button-touchEvent2
V/tag: activity-dispatchTouchEvent1
V/tag: layout-dispatchTouchEvent1
V/tag: layout-interceptTouchEvent1
V/tag: button-dispatchTouchEvent1
V/tag: button-touchEvent1

如果返回值是false,则会将ACTION_DOWN传递给其父ViewGroup的onTouchEvent进行处理,直到由哪一层ViewGroup消费了ACTION_DOWN事件为止,后续的n个ACTION_MOVE与1个ACTION_UP都会逐层传递到处理这个事件的那一层View或ViewGroup,我们现在将MyButton的onTouchEvent返回为false,MyLayout的onTouchEvent返回值为true看一下Log日志:

V/tag: activity-dispatchTouchEvent0
V/tag: layout-dispatchTouchEvent0
V/tag: layout-interceptTouchEvent0
V/tag: button-dispatchTouchEvent0
V/tag: button-touchEvent0
V/tag: layout-touchEvent0
V/tag: activity-dispatchTouchEvent2
V/tag: layout-dispatchTouchEvent2
V/tag: layout-touchEvent2
V/tag: activity-dispatchTouchEvent2
V/tag: layout-dispatchTouchEvent2
V/tag: layout-touchEvent2
V/tag: activity-dispatchTouchEvent2
V/tag: layout-dispatchTouchEvent2
V/tag: layout-touchEvent2
V/tag: activity-dispatchTouchEvent1
V/tag: layout-dispatchTouchEvent1
V/tag: layout-touchEvent1

Touch 事件说完后,我们来看一下onClick事件,看一下onClick 和 Touch 有没有什么关系



onClick 事件

还是这一段代码,View的dispatchTouchEvent代码

public boolean dispatchTouchEvent(MotionEvent event) {  
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
            mOnTouchListener.onTouch(this, event)) {  
        return true;  
    }  
    return onTouchEvent(event);  
}  

可以看到,第三个判断比较重要,如果对一个控件添加onClick事件,当控件中onTouch方法返回是true的时候,onClick事件没有响应。 这是为什么呢?我们来看一下第三个判断

第三个判断回调控件注册touch事件时的onTouch方法,也就是说如果我们在onTouch方法里返回true,就会让这三个条件全部成立,无法执行下面的onTouchEvent方法,我们要知道onClick的调用是在onTouchEvent(event)方法中的performClick()方法,所以如果此时onTouch返回了true,onClick()方法就无法执行。

我们还要知道,performClick()这个方法是在onTouchEvent()方法的Up事件中的,所以执行流程也是先onTouch()然后才会执行onClick()事件。
通过这些,我们就大概了解了Android中事假的分发机制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值