Android View 事件基础

01.事件分发概念

1.1 事件分发的对象是谁

  • 事件分发的对象是事件。注意,事件分发是向下传递的,也就是父到子的顺序。
  • 当用户触摸屏幕时(View或ViewGroup派生的控件),将产生点击事件(Touch事件)。
    • Touch事件相关细节(发生触摸的位置、时间、历史记录、手势动作等)被封装成MotionEvent对象
  • 主要发生的Touch事件有如下四种:
    • MotionEvent.ACTION_DOWN:按下View(所有事件的开始)
    • MotionEvent.ACTION_MOVE:滑动View
    • MotionEvent.ACTION_CANCEL:非人为原因结束本次事件
    • MotionEvent.ACTION_UP:抬起View(与DOWN对应)
  • 事件列:
    • 从手指接触屏幕至手指离开屏幕,这个过程产生的一系列事件。即当一个MotionEvent 产生后,系统需要把这个事件传递给一个具体的 View 去处理
    • 任何事件列都是以DOWN事件开始,UP事件结束,中间有无数的MOVE事件,如下图:
    • image

1.2 事件分发的本质

  • 将点击事件(MotionEvent)向某个View进行传递并最终得到处理
    • 即当一个点击事件发生后,系统需要将这个事件传递给一个具体的View去处理。这个事件传递的过程就是分发过程。
    • Android事件分发机制的本质是要解决,点击事件由哪个对象发出,经过哪些对象,最终达到哪个对象并最终得到处理。

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

  • Activity、ViewGroup、View
    • 一个点击事件产生后,传递顺序是:Activity(Window) -> ViewGroup -> View
    • Android的UI界面是由Activity、ViewGroup、View及其派生类组合而成的
  • View是所有UI组件的基类
    • 一般Button、ImageView、TextView等控件都是继承父类View
  • ViewGroup是容纳UI组件的容器,即一组View的集合(包含很多子View和子VewGroup),
    • 其本身也是从View派生的,即ViewGroup是View的子类
    • 是Android所有布局的父类或间接父类:项目用到的布局(LinearLayout、RelativeLayout等),都继承自ViewGroup,即属于ViewGroup子类。
    • 与普通View的区别:ViewGroup实际上也是一个View,只不过比起View,它多了可以包含子View和定义布局参数的功能。

1.4 事件分发过程涉及方法

  • 事件分发过程由这几个方法协作完成
    • dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()

02.事件分发机制方法

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

2.1 dispatchTouchEvent()

  • 如下所示

    属性 介绍
    使用对象 Activity、ViewGroup、View
    作用 分发点击事件
    调用时刻 当点击事件能够传递给当前View时,该方法就会被调用
    返回结果 是否消费当前事件,详细情况如下:
  • 1. 默认情况:根据当前对象的不同而返回方法不同

    对象 返回方法 备注
    Activity super.dispatchTouchEvent() 即调用父类ViewGroup的dispatchTouchEvent()
    ViewGroup onIntercepTouchEvent() 即调用自身的onIntercepTouchEvent()
    View onTouchEvent() 即调用自身的onTouchEvent()
  • 流程解析

    • 返回true
      • 消费事件
      • 事件不会往下传递
      • 后续事件(Move、Up)会继续分发到该View
    • 返回false
      • 不消费事件
      • 事件不会往下传递
      • 将事件回传给父控件的onTouchEvent()处理
        • Activity例外:返回false=消费事件
      • 后续事件(Move、Up)会继续分发到该View(与onTouchEvent()区别)

2.2 onTouchEvent()

  • 如下所示

    属性 介绍
    使用对象 Activity、ViewGroup、View
    作用 处理点击事件
    调用时刻 在dispatchTouchEvent()内部调用
    返回结果 是否消费(处理)当前事件,详细情况如下:
  • 流程解析

    • 返回true
      • 自己处理(消费)该事情
      • 事件停止传递
      • 该事件序列的后续事件(Move、Up)让其处理;
    • 返回false(同默认实现:调用父类onTouchEvent())
      • 不处理(消费)该事件
      • 事件往上传递给父控件的onTouchEvent()处理
      • 当前View不再接受此事件列的其他事件(Move、Up);

2.3 onInterceptTouchEvent()

  • 如下所示

    属性 介绍
    使用对象 ViewGroup(注:Activity、View都没该方法)
    作用 拦截事件,即自己处理该事件
    调用时刻 在ViewGroup的dispatchTouchEvent()内部调用
    返回结果 是否拦截当前事件,详细情况如下:
  • 流程解析

    • true-当前ViewGroup(因为View中没有该方法,而没有child的VIew也不需要有拦截机制)希望该事件不再传递给其child,而是希望自己处理。
    • false-当前ViewGroup不准备拦截该事件,事件正常向下分发给其child。

2.4 三个方法执行顺序

  • 代码如下所示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
    LogUtils.e("yc----------事件拦截----------");
    return super.onInterceptTouchEvent(e);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    LogUtils.e("yc----------事件分发----------");
    return super.dispatchTouchEvent(ev);
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent e) {
    LogUtils.e("yc----------事件触摸----------");
    return super.onTouchEvent(e);
    }
  • 执行结果如下

    1
    2
    3
    yc----------事件分发----------
    yc----------事件拦截----------
    yc----------事件触摸----------

2.5 三者之间关系

  • 下面将用一段伪代码来阐述上述三个方法的关系和点击事件传递规则

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 点击事件产生后,会直接调用dispatchTouchEvent分发方法
    public boolean dispatchTouchEvent(MotionEvent ev) {
    //代表是否消耗事件
    boolean consume = false;

    if (onInterceptTouchEvent(ev)) {
    //如果onInterceptTouchEvent()返回true则代表当前View拦截了点击事件
    //则该点击事件则会交给当前View进行处理
    //即调用onTouchEvent ()方法去处理点击事件
    consume = onTouchEvent (ev) ;
    } else {
    //如果onInterceptTouchEvent()返回false则代表当前View不拦截点击事件
    //则该点击事件则会继续传递给它的子元素
    //子元素的dispatchTouchEvent()就会被调用,重复上述过程
    //直到点击事件被最终处理为止
    consume = child.dispatchTouchEvent (ev) ;
    }
    return consume;
    }