Android中事件分发机制

来源:互联网 发布:淘宝宝贝发货地址设置 编辑:程序博客网 时间:2024/05/15 00:53

在 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中事假的分发机制

0 0