Android-你必须要懂得事件分发全解析

2020-05-14  本文已影响0人  沉淀者

一、 事件分发的对象是谁?

答:事件

当用户触摸屏幕时(View或ViewGroup派生的控件),将产生点击事件(Touch事件)。
Touch事件相关细节(发生触摸的位置、时间、历史记录、手势动作等)被封装成MotionEvent对象
主要发生的Touch事件有如下四种:

事件列:从手指接触屏幕至手指离开屏幕,这个过程产生的一系列事件 任何事件列都是以DOWN事件开始,UP事件结束,中间有无数的MOVE事件,如下图:

事件执行顺序.png

二、事件分发的本质

答:将点击事件(MotionEvent)向某个View进行传递并最终得到处理

即当一个点击事件发生后,系统需要将这个事件传递给一个具体的View去处理。这个事件传递的过程就是分发过程。

三、事件在哪些对象之间进行传递?

答:Activity、ViewGroup、View

一个点击事件产生后,传递顺序是:Activity(Window) -> ViewGroup -> View
Android的UI界面是由Activity、ViewGroup、View及其派生类组合而成的

image.png

View是所有UI组件的基类

一般Button、ImageView、TextView等控件都是继承父类View

ViewGroup是容纳UI组件的容器,即一组View的集合(包含很多子View和子VewGroup),

其本身也是从View派生的,即ViewGroup是View的子类
是Android所有布局的父类或间接父类:项目用到的布局(LinearLayout、RelativeLayout等),都继承自ViewGroup,即属于ViewGroup子类。

与普通View的区别:ViewGroup实际上也是一个View,只不过比起View,它多了可以包含子View和定义布局参数的功能。

四、 事件分发过程由哪些方法协作完成?

答:dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()

image.png

五、 总结

Android事件分发机制的本质是要解决:

点击事件由哪个对象发出,经过哪些对象,最终达到哪个对象并最终得到处理。

这里的对象是指Activity、ViewGroup、View
Android中事件分发顺序:Activity(Window) -> ViewGroup -> View

事件分发过程由dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()三个方法协助完成

经过上述3个问题,相信大家已经对Android的事件分发有了感性的认知,接下来,我将详细介绍Android事件分发机制。

六、事件分发机制方法介绍

Android事件分发流程如下:(必须熟记)
Android事件分发顺序:Activity(Window) -> ViewGroup -> View

事件分发顺序.png

super:调用父类方法(在这里Activity的父类是ViewGroup,ViewGroup的父类是View)
true:消费事件,即事件不继续往下传递
false:不消费事件,事件也不继续往下传递 / 交由给父控件onTouchEvent()处理

看图总结:首先当你触摸的屏幕的时候会一口气产生至少2个事件,一个down,一个up,其余就是你手指移动过程中产生的move事件。首先down事件会先执行activity的dispatchTouchEvent方法,不管它返回true或者false都会直接在这里被消费,不会继续往下传递。只有调用父类方法的时候会执行ViewGroup的dispatchTouchEvent方法,此时假如dispatchTouchEvent返回true也代表在此消费,不会继续传递,假如返回false,表示不分发,则交还给Activity,说大家都处理不了,你自己处理吧。假如dispatchTouchEvent返回父类,则会调用ViewGroup的onInterceptTouchEvent,返回true表示拦截这个事件,交给自己的onTouchEvent方法消费事件,假如返回false,表示不拦截,则交给View的dispatchTouchEvent方法,同样的做处理,之后交给View的onTouchEvent方法,返回true代表消费,返回false,表示无法处理,交还给上级。

其实,这些不需要硬记,实在开发中遇到不知道哪里返回true,哪里返回false时,回来看看这个顺序。最主要的是多去用用就记住了。下面我们看一般的事件传递。

七、一般事件传递

image.png

7.1 默认情况

即不对控件里的方法(dispatchTouchEvent()、onTouchEvent()、onInterceptTouchEvent())进行重写或更改返回值
那么调用的是这3个方法的默认实现:调用父类的方法
事件传递情况:

从Activity A---->ViewGroup B--->View C,从上往下调用dispatchTouchEvent()
再由View C--->ViewGroup B --->Activity A,从下往上调用onTouchEvent()

7.2处理事件

假设View C希望处理这个点击事件,即C被设置成可点击的(Clickable)或者覆写了C的onTouchEvent方法返回true。

事件传递情况:

DOWN事件被传递给C的onTouchEvent方法,该方法返回true,表示处理这个事件
因为C正在处理这个事件,那么DOWN事件将不再往上传递给B和A的onTouchEvent();
该事件列的其他事件(Move、Up)也将传递给C的onTouchEvent()

7.3 拦截DOWN事件

假设ViewGroup B希望处理这个点击事件,即B覆写了onInterceptTouchEvent()返回true、onTouchEvent()返回true。 事件传递情况:

DOWN事件被传递给B的onInterceptTouchEvent()方法,该方法返回true,表示拦截这个事件,即自己处理这个事件(不再往下传递)
调用onTouchEvent()处理事件(DOWN事件将不再往上传递给A的onTouchEvent())
该事件列的其他事件(Move、Up)将直接传递给B的onTouchEvent()
该事件列的其他事件(Move、Up)将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。

7.4 拦截DOWN的后续事件

假设ViewGroup B没有拦截DOWN事件(还是View C来处理DOWN事件),但它拦截了接下来的MOVE事件。

DOWN事件传递到C的onTouchEvent方法,返回了true。
在后续到来的MOVE事件,B的onInterceptTouchEvent方法返回true拦截该MOVE事件,但该事件并没有传递给B;这个MOVE事件将会被系统变成一个CANCEL事件传递给C的onTouchEvent方法
后续又来了一个MOVE事件,该MOVE事件才会直接传递给B的onTouchEvent()
后续事件将直接传递给B的onTouchEvent()处理
后续事件将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。
C再也不会收到该事件列产生的后续事件。

八、源码分析

View中dispatchTouchEvent()的源码分析

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

从上面可以看出:

只有以下三个条件都为真,dispatchTouchEvent()才返回true;否则执行onTouchEvent(event)方法

下面,我们来看看下这三个判断条件:

第一个条件:mOnTouchListener!= null

//mOnTouchListener是在View类下setOnTouchListener方法里赋值的
public void setOnTouchListener(OnTouchListener l) { 

//即只要我们给控件注册了Touch事件,mOnTouchListener就一定被赋值(不为空)
    mOnTouchListener = l;  
}

第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED

该条件是判断当前点击的控件是否enable
由于很多View默认是enable的,因此该条件恒定为true

第三个条件:mOnTouchListener.onTouch(this, event)

回调控件注册Touch事件时的onTouch方法

//手动调用设置
button.setOnTouchListener(new OnTouchListener() {  
  @Override  
  public boolean onTouch(View v, MotionEvent event) {  
      return false;  
  }  
});

如果在onTouch方法返回true,就会让上述三个条件全部成立,从而整个方法直接返回true。

如果在onTouch方法里返回false,就会去执行onTouchEvent(event)方法。

下面看onTouchEvent(event)**的源码分析

主要这里面调用到了performClick()方法,关注这个方法即可

public boolean performClick() {  
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
    if (mOnClickListener != null) {  
        playSoundEffect(SoundEffectConstants.CLICK);  
        mOnClickListener.onClick(this);  
        return true;  
    }  
    return false;  
}

只要mOnClickListener不为null,就会去调用onClick方法;
那么,mOnClickListener又是在哪里赋值的呢?请继续看:

public void setOnClickListener(OnClickListener l) {  
    if (!isClickable()) {  
        setClickable(true);  
    }  
    mOnClickListener = l;  
}

当我们通过调用setOnClickListener方法来给控件注册一个点击事件时,就会给mOnClickListener赋值(不为空),即会回调onClick()。

结论

onTouch()的执行高于onClick()

每当控件被点击时:如果在回调onTouch()里返回false,就会让dispatchTouchEvent方法返回false,那么就会执行onTouchEvent();如果回调了setOnClickListener()来给控件注册点击事件的话,最后会在performClick()方法里回调onClick()。

onTouch()返回false(该事件没被onTouch()消费掉) = 执行onTouchEvent() = 执行OnClick()
如果在回调onTouch()里返回true,就会让dispatchTouchEvent方法返回true,那么将不会执行onTouchEvent(),即onClick()也不会执行;

onTouch()返回true(该事件被onTouch()消费掉) = dispatchTouchEvent()返回true(不会再继续向下传递) = 不会执行onTouchEvent() = 不会执行OnClick()

onTouch()和onTouchEvent()的区别

这两个方法都是在View的dispatchTouchEvent中调用,但onTouch优先于onTouchEvent执行。
如果在onTouch方法中返回true将事件消费掉,onTouchEvent()将不会再执行。

上一篇下一篇

猜你喜欢

热点阅读