android 事件分发

来源:互联网 发布:关于农副产品的软件 编辑:程序博客网 时间:2024/03/28 23:01

1. 基础认知

1.1 事件分发的对象是谁?

答:事件

  • 当用户触摸屏幕时(View或ViewGroup派生的控件),将产生点击事件(Touch事件)。

    Touch事件相关细节(发生触摸的位置、时间、历史记录、手势动作等)被封装成MotionEvent对象

  • 主要发生的Touch事件有如下四种:

    • MotionEvent.ACTION_DOWN:按下View(所有事件的开始)
    • MotionEvent.ACTION_MOVE:滑动View
    • MotionEvent.ACTION_CANCEL:非人为原因结束本次事件
    • MotionEvent.ACTION_UP:抬起View(与DOWN对应)
  • 事件列:从手指接触屏幕至手指离开屏幕,这个过程产生的一系列事件
    任何事件列都是以DOWN事件开始,UP事件结束,中间有无数的MOVE事件,如下图:


    事件列

即当一个MotionEvent 产生后,系统需要把这个事件传递给一个具体的 View 去处理,

1.2 事件分发的本质

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

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

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

答:Activity、ViewGroup、View

一个点击事件产生后,传递顺序是:Activity(Window) -> ViewGroup -> View

  • Android的UI界面是由Activity、ViewGroup、View及其派生类组合而成的

    UI界面
  • View是所有UI组件的基类

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

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

    1. 其本身也是从View派生的,即ViewGroup是View的子类
    2. 是Android所有布局的父类或间接父类:项目用到的布局(LinearLayout、RelativeLayout等),都继承自ViewGroup,即属于ViewGroup子类。
    3. 与普通View的区别:ViewGroup实际上也是一个View,只不过比起View,它多了可以包含子View和定义布局参数的功能。

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

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


事件分发相关方法

下文会对这3个方法进行详细介绍

1.5 总结

  • Android事件分发机制的本质是要解决:点击事件由哪个对象发出,经过哪些对象,最终达到哪个对象并最终得到处理。

    这里的对象是指Activity、ViewGroup、View

  • Android中事件分发顺序:Activity(Window) -> ViewGroup -> View
  • 事件分发过程由dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()三个方法协助完成

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

2. 事件分发机制方法&流程介绍

  • 事件分发过程由dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()三个方法协助完成,如下图:



方法详细介绍
  • Android事件分发流程如下:(必须熟记

    Android事件分发顺序:Activity(Window) -> ViewGroup -> View


事件分发机制详细流程

其中:

  • super:调用父类方法
  • true:消费事件,即事件不继续往下传递
  • false:不消费事件,事件也不继续往下传递 / 交由给父控件onTouchEvent()处理

3.2 一般的事件传递情况

一般的事件传递场景有:

  • 默认情况
  • 处理事件
  • 拦截DOWN事件
  • 拦截后续事件(MOVE、UP)

3.2.1 默认情况

  • 即不对控件里的方法(dispatchTouchEvent()、onTouchEvent()、onInterceptTouchEvent())进行重写或更改返回值
  • 那么调用的是这3个方法的默认实现:调用父类的方法
  • 事件传递情况:(如图下所示)
    • 从Activity A---->ViewGroup B--->View C,从上往下调用dispatchTouchEvent()
    • 再由View C--->ViewGroup B --->Activity A,从下往上调用onTouchEvent()

流程图

注:虽然ViewGroup B的onInterceptTouchEvent方法对DOWN事件返回了false,后续的事件(MOVE、UP)依然会传递给它的onInterceptTouchEvent()

这一点与onTouchEvent的行为是不一样的。


5.1 onTouch()和onTouchEvent()的区别

View中dispatchTouchEvent()的源码分析

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

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

demo图解;

那么首先看一下默认的触屏事件的在两个函数之间的传递流程。如下图:


如果仅仅想让MyTextView来响应触屏事件,让MyTextView的OnTouchEvent返回true,那么事件流就变成如下图,可以看到layoutview1,layoutview2已经不能进入OnTouchEvent:


另外一种情况,就是外围容器想独自处理触屏事件,那么就应该在相应的onInterceptTouchEvent函数中返回true,表示要截获触屏事件,比如layoutview1作截获处理,处理流变成如下图:


以此类推,我们可以得到各种具体的情况,整个layout的view类层次中都有机会截获,而且能看出来外围的容器view具有优先截获权。

ViewGroup里的onInterceptTouchEvent默认值是false这样才能把事件传给View里的onTouchEvent.

ViewGroup里的onTouchEvent默认值是false。

View里的onTouchEvent返回默认值是true.这样才能执行多次touch事件。(返回false,认为你没有消费,接下来其他事件都不在这里触发)

(注:可能你会觉得是否消费了有关系吗,反正我已经针对事件编写了处理代码(即你在那里写了代码处理这个事件,但是不算消费,还是会传递)?答案是有区别!比如ACTION_MOVE或者ACTION_UP发生的前提是一定曾经发生了ACTION_DOWN,onTouchEvent,如果你没有消费ACTION_DOWN,那么系统会认为ACTION_DOWN没有发生过,所以ACTION_MOVE或者ACTION_UP就不能被捕获。)(不消费不代表不处理

结论总结(结合开头图表理解):
1. 事件处理分为了两个阶段,第一阶段是由上到下,也就是作者反复强调必须要熟记的:Activity (PhoneWindow) -> ViewGroup -> View,第二阶段为回传

2.事件处理最重要的三个方法: dispatchTouchEvent / onInterceptTouchEvent / onTouchEvent。dispatchTouchEvent 分发由上而下,中间加入是否拦截(onInterceptTouchEvent ),onTouchEvent消费事件不消费不代表不处理

3. 受益最多的是这一部分:当用户开始触摸屏幕的时候,触发到
Activity的dispatchTouchEvent方法,此方法如果直接返回true或false, 则该事件处理终止;Activity代码中默认是执行了superDispatchTouchEvent()方法,最终调用到了ViewGroup的dispatchTouchEvent方法。

对于ViewGroup的dispatchTouchEvent方法来说,跟Activity类似,如果此方法返回true,则该事件处理终止;如果返回false,则执行上一级的onTouchEvent方法(以Acivity中包含的第一级ViewGroup为例来说,如果这个ViewGroup又包含了子的ViewGroup,则类推为,父的ViewGroup的onTouchEvent方法). 默认的是执行super的方法,会调用到ViewGroup的onInterceptTouchEvent方法,此方法如果返回true,表示拦截此事件,则此事件不会再往下传递,并且执行ViewGroup的onTouchEvent方法;如果此方法返回false(或是执行super的方法),则事件继续往下传递,传递到View.

对于View的dispatchTouchEvent方法来说,跟Activity类似,如果此方法返回true,则该事件处理终止;如果返回false,则执行上一级也就是ViewGroup的onTouchEvent方法。默认执行super的方法的话,会调用到View的onTouchEvent方法。至此,事件由上到下的传递完成.


4. 收益最多的第二部分:对于onTouchEvent(第二阶段是由下到上)来说,如果返回true,则说明处理该事件完成,该事件处理结束,已消费,不会再传递给上一级的控件,且后续的 MOVE / UP 事件还会交给他处理。如果返回false,则说明该事件没有处理,也就是没有被消费,该事件会继续往上传递给上一级控件的onTouchEvent方法,且后续的 MOVE / UP 也就不会给这个View来处理(dispatchTouchEvent和onInterceptTouchEvent返回false后续的事件还是可以接收)。

5. 收益最多的第三部分:ViewGroup要拦截DOWN事件的话,需要在onInterceptTouchEvent() 这个方法中返回true,表示这个事件由自己来处理然后调用ViewGroup自己的onTouchEvent方法,此事件则不会再往下传递给View(即使子View调用requestDisallowInterceptTouchEvent也无法传递),之后的 MOVE / UP 事件也就直接传递给了这个ViewGroup;ViewGroup如果要拦截 MOVE / UP 事件的话,View还是会接收到 DOWN 事件,只不过当ViewGroup收到 MOVE 事件的时候,onInterceptTouchEvent拦截该事件返回了true,这个事件就被系统变成了CANCEL事件传递给了View的onTouchEvent方法,后续的MOVE事件会传递给ViewGroup的onTouchEvent方法

6.requestDisallowInterceptTouchEvent的用处,为了子View申请父类别拦截事件的方法(前提是子View可以收到Down事件)

如:在ListView里面套了ViewPager导致ViewPager不能滑动的问题,通常的处理方式:

 @Overridepublic boolean onInterceptTouchEvent(MotionEvent event) {    if (absListView != null) {        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                mDownX = event.getX();                mDownY = event.getY();                    //ACTION_DOWN的时候,赶紧把事件hold住                getParent().requestDisallowInterceptTouchEvent(true);                break;            case MotionEvent.ACTION_MOVE:                if(Math.abs(event.getX() - mDownX)>Math.abs(event.getY()-mDownY)) {                    getParent().requestDisallowInterceptTouchEvent(true);                }else {                    //发现不是自己处理,还给父类                    getParent().requestDisallowInterceptTouchEvent(false);                }                break;            case MotionEvent.ACTION_CANCEL:            case MotionEvent.ACTION_UP:                    //其实这里是多余的                getParent().requestDisallowInterceptTouchEvent(false);        }    }    return super.onInterceptTouchEvent(event);}

源码解析参考:
http://www.jianshu.com/p/7ff768a77410
http://www.jianshu.com/p/38015afcdb58



原创粉丝点击