View工作原理【触摸消息派发】

来源:互联网 发布:mysql语句降序排列 编辑:程序博客网 时间:2024/06/06 01:19
原文:《Android内核剖析》读书笔记 第13章 View工作原理【触摸消息派发】

相比按键消息,触摸消息也是由ViewRootImpl.WindowInputEventReceiver实例负责接收,然后判断消息类型之后执行不同的方法,对于触摸消息就是执行 deliverPointerEvent(.)方法;不同点主要以下几点:

  1. 触摸消息由消息获取模块InputManagerService直接派发给应用程序,而无需经过Wms内部的预处理,最新的版本中仅仅会对当屏幕关闭时执行 interceptMotionBeforeQueueingWhenScreenOff(.)
  2. 触摸消息在处理时,需要根据触摸点坐标计算该消息应该派发给哪个具体的View/ViewGroup,而在按键消息处理中却不存在该计算过程;
  3. 没有类似于“系统按键”之类的“系统触摸”,应用程序可以完全控制触摸行为;
  4. 子视图优先于父视图进行消息处理,这与按键消息的处理机制完全相反;

屏幕坐标/视图坐标/布局坐标三者间的关系

  1. 屏幕坐标:以屏幕左上方为(0,0)的坐标体系,X/Y轴的最大值即为物理屏幕分辨率的宽和高;
    触摸消息中MotionEvent.getX/getY取到的就是屏幕坐标值;
  2. 视图坐标:视图坐标是完全由视图内容的宽高决定的坐标体系,理论上他是没有边界的,不受物理屏幕大小限制;
    比如1000行的文本限定宽度为100px,每行高度为5px,那对应的X/Y最大坐标值为(100,5000);
  3. 布局坐标:子视图相对于父视图而言的相对屏幕坐标,以父视图的左上角为(0,0),而不关心父视图到底位于屏幕何处,X/Y最大坐标值为父视图的width/height;对于子视图而言,若内容过多,超过父视图分配的区域大小将由部分内容不能显示,此时就会出现滚动条,视图坐标=布局坐标+mScrollX/mScrollY;
    View体系下的getX/getY/getTop/getBottom/getLeft/getRight都是指的布局坐标;
    子视图的屏幕坐标=子视图的布局坐标+父视图的屏幕坐标;对于视图坐标和布局坐标两者之间可以进行转换,具体可以参见下图的事例,注意:空白区域不计尺寸,只是为了更明确的演示而已;

触摸消息总体派发过程

下面就介绍下ViewRootImpl接收到具体消息执行deliverPointerEvent(.)方法的具体过程:

  1. 进行物理像素到逻辑像素的转换;只有当物理屏幕像素与操作系统中识别的屏幕像素不一致时才需要,一般不需要这步;
  2. 如果是DOWN事件,调用ensureTouchMode(true)方法设置为触摸模式,这会引起相关视图状态的变化,详见后续说明;
  3. 将屏幕坐标转换成视图坐标,因为后续需要根据触摸点的坐标来确定到底由哪个视图来处理消息;
  4. 调用mView.dispatchTouchEvent()将消息派发给根视图,这里的mView实例为PhoneWindow.DecorView;
    1. 判断是否存在Window.Callback回调对象,其实就是Activity实例,若不存在,则直接调用DecorView.super.dispatchTouchEvent(ev),实际执行到的是ViewGroup.dispatchTouchEvent(.),注意:ViewGroup完全重载了该方法,未调用super方法;
      1. 执行 onInterceptTouchEvent(.);对当前事件进行拦截返回是否当前ViewGroup需要自己处理该消息,用来控制事件的传递方向,开发者可以通过调用 requestDisallowInterceptTouchEvent(.)设置是否允许做消息拦截;
      2. 若不需要,则遍历所有子视图,通过isTransformedTouchPointInView(…)判断当前触摸点是否在子视图以内,找到可以处理该消息的子视图,然后执行 dispatchTransformedTouchEvent(…),对坐标进行相应转换之后调用child.dispatchTouchEvent(.);对于子视图child而言,若是ViewGroup实例,则循环之前的逻辑;若不是,则执行View.dispatchTouchEvent(.);
        1. 首先判断当前View是否有设置过View.OnTouchListener接口Listener,若有,则执行Listener实例的 onTouch(.)方法;
          注意:该Listener只对非ViewGroup子类有效,因为ViewGroup完全重载了dispatchTouchEvent方法,没有机会调用到该Listener的方法;
        2. 若没有,则直接执行onTouchEvent(.)方法;在基类View.onTouchEvent(.)方法中主要是完成了对tap、click、longClick三种点击事件的处理,将其转换为对 OnClickListener/OnLongClickListener接口的回调,开发者只需要实现对应的Listener接口即可;其对应的事件处理模型如下图:

          注意:tap其实是没有单独的监听Listener接口的,他也并不会改变视图的状态,但会引起视图获得焦点,所以我们的一些selector中对pressed状态设置的UI效果对于tap而言是没有作用的;因为tap的时间间隔只有180ms,所以要想体验具体的UI效果,需要很快的轻触才行;
      3. 若需要自己处理,则直接执行dispatchTransformedTouchEvent(…);
    2. 若存在Activity回调实例,则执行Activity.dispatchTouchEvent()方法;
      1. 如果是DOWN消息,调用onUserInteraction();可以在消息被处理之前做点什么,该方法为按键和触摸消息通用,若重载了对两者都起效;
      2. 执行PhoneWindow.superDispatchTouchEvent();其实就是转交给DecorView.super.dispatchTouchEvent(.)处理,这点与没有Activity回调实例是一致的,即无论如何都是先由View自身先处理消息,然后才是Activity处理;
      3. 若View系统仍未处理消息,则调用Activity.onTouchEvent(.),开发者可以重载该方法实现自己的处理逻辑;

View/ViewGroup中dispatchTouchEvent/onInterceptTouchEvent/onTouchEvent的具体逻辑关系

  1. 在多层嵌套的View系统中,触摸事件由顶层向底层传递,在ViewGroup中对dispatchTouchEvent(.)进行递归调用,子视图优先父视图进行消息处理,整体一般呈现U型递归,参考下图所示,其中LayoutView1中包括子视图LayoutView2,LayoutView2中包括子视图TextView;

  2. dispatchTouchEvent为事件处理的源头,View/ViewGroup两者中的具体处理逻辑完全不一样,详见上面的具体内容;
    onTouchEvent是为开发者开放的回调方法,用来处理具体的消息,当然在默认实现中系统进一步抽取了很多的Listener回调接口供开发者使用;
    onInterceptTouchEvent是ViewGroup特有,在dispatchTouchEvent中被调用,让ViewGroup有机会决定是自己处理触摸消息,还是传递给子视图进行处理;
  3. 触摸事件都以DOWN事件开始,中间会经过多次的MOVE,最后以UP结束;
  4. 若ViewGroup.onInterceptTouchEvent()对DOWN事件处理返回false,则表示该ViewGroup不拦截该事件,交由子视图处理;那么后续的move, up等事件将继续会先传递给该ViewGroup,之后再传递给子视图处理。
    注意:若子视图未处理该DOWN事件,然后当前ViewGroup.onTouchEvent处理了该事件,那之后的MOVE/UP事件将会跳过ViewGroup.onInterceptTouchEvent()而直接执行ViewGroup.onTouchEvent;
  5. 若ViewGroup.onInterceptTouchEvent()对DOWN事件处理返回true,那么后续的MOVE/UP事件将不再传递给onInterceptTouchEvent(),而是直接传递给该ViewGroup的onTouchEvent()处理;
    注意,这种情况下子视图View将接收不到任何事件,因为被父视图拦截了。
  6. 若子视图的onTouchEvent()返回了false,那么当前事件将被传递至父视图的onTouchEvent()处理,其后续事件将不会再传递至该子视图。
    注意:这里指的是对DOWN事件处理的情况,若是其他事件,即便子视图的onTouchEvent()返回了false,该事件也不会传递至父视图;
  7. 若子视图的onTouchEvent()返回了true,那么对于当前事件父视图的onTouchEvent()将不会被触发,其后续事件将继续由该子视图的onTouchEvent()处理,并且若当前子视图仍是ViewGroup,那onInterceptTouchEvent()也将被跳过不会执行。
  8. 若对于DOWN事件最后没有任何View.onTouchEvent()返回true,那后续的move、up事件将会被自动丢弃,不会进行任何传递;
  9. 若ViewGroup.onInterceptTouchEvent()对DOWN事件返回false,但却对接下来的MOVE事件返回true,那此时系统将对子视图发送一个CANCEL事件,当前MOVE事件的ViewGroup.onTouchEvent()不会被执行,即当前的MOVE事件被丢弃了,但接下去新的MOVE/UP消息将直接进入ViewGroup.onTouchEvent()执行;

接下来针对以上描述的情况给出一些案例说明,简单的情况参见http://blog.csdn.net/ddna/article/details/5473293

但要注意:目前网络上大部分对触摸事件传递的描述其实都是不够准确的,大家可以结合以下的事例自己多琢磨琢磨;

因为触摸消息是一个事件系列,包括一次DOWN事件、多次MOVE事件、一次UP事件;对于每次事件都会涉及到onInterceptTouchEvent/onTouchEvent的执行,而每次执行你都可以根据需要返回true/false,这导致的组合情况就非常多了,但原则还是基于以上提到的这几条,下面来看看以下事例:

  1. 执行条件如下:
    LayoutView1.onIntercept始终返回false;
    LayoutView2.onIntercept始终返回true;
    LayoutView1.onTouchEvent始终返回true;
    LayoutView2.onTouchEvent始终返回false;
    MyTextView.onTouchEvent始终返回true;
    执行结果如下:
    06-25 16:32:41.779: D/LayoutView1(25253): onInterceptTouchEvent action:ACTION_DOWN06-25 16:32:41.779: D/LayoutView2(25253): onInterceptTouchEvent action:ACTION_DOWN
    06-25 16:32:41.784: D/LayoutView2(25253): onTouchEvent action:ACTION_DOWN
    06-25 16:32:41.784: D/LayoutView1(25253): onTouchEvent action:ACTION_DOWN
    06-25 16:32:41.789: D/LayoutView1(25253): onTouchEvent action:ACTION_MOVE
    06-25 16:32:41.804: D/LayoutView1(25253): onTouchEvent action:ACTION_MOVE
    06-25 16:32:41.974: D/LayoutView1(25253): onTouchEvent action:ACTION_MOVE
    06-25 16:32:41.979: D/LayoutView1(25253): onTouchEvent action:ACTION_UP
    解读如下:
    对于DOWN事件,LayoutView1.onIntercept执行但未拦截,LayoutView2.onIntercept执行并拦截,即可转交给LayoutView2.onTouchEvent处理,但由于返回false,所以事件交由父视图LayoutView1.onTouchEvent处理,并成功消耗返回true;
    对于后续MOVE/UP事件,直接由LayoutView1.onTouchEvent处理,LayoutView1/2的onIntercept均被略过了;
  2. 执行条件如下:
    LayoutView1.onIntercept始终返回false;
    LayoutView2.onIntercept对DOWN事件返回false,但对MOVE事件返回true;
    LayoutView1.onTouchEvent始终返回true;
    LayoutView2.onTouchEvent始终返回false;
    MyTextView.onTouchEvent始终返回false;
    执行结果如下:
    06-25 17:04:26.764: D/LayoutView1(21298): onInterceptTouchEvent action:ACTION_DOWN06-25 17:04:26.764: D/LayoutView2(21298): onInterceptTouchEvent action:ACTION_DOWN
    06-25 17:04:26.764: D/MyTextView(21298): onTouchEvent action:ACTION_DOWN
    06-25 17:04:26.764: D/LayoutView2(21298): onTouchEvent action:ACTION_DOWN
    06-25 17:04:26.764: D/LayoutView1(21298): onTouchEvent action:ACTION_DOWN
    06-25 17:04:26.804: D/LayoutView1(21298): onTouchEvent action:ACTION_MOVE
    06-25 17:04:26.819: D/LayoutView1(21298): onTouchEvent action:ACTION_MOVE
    06-25 17:04:27.059: D/LayoutView1(21298): onTouchEvent action:ACTION_MOVE
    06-25 17:04:27.064: D/LayoutView1(21298): onTouchEvent action:ACTION_UP
    解读如下:
    对于DOWN事件,LayoutView1.onIntercept执行但未拦截,LayoutView2.onIntercept也未拦截,交由MyTextView.onTouchEvent处理,但由于返回false,所以事件交由父视图LayoutView2.onTouchEvent执行,但可惜也返回false,继续交由LayoutView1.onTouchEvent执行,成功消耗消息返回true;
    对于后续MOVE/UP事件,直接由LayoutView1.onTouchEvent处理,LayoutView1/2的onIntercept均被略过了;
  3. 执行条件如下:
    LayoutView1.onIntercept始终返回false;
    LayoutView2.onIntercept对DOWN事件返回false,但对MOVE事件返回true;
    LayoutView1.onTouchEvent始终返回true;
    LayoutView2.onTouchEvent始终返回true;
    MyTextView.onTouchEvent始终返回true;
    执行结果如下:
    06-25 15:56:09.219: D/LayoutView1(28368): onInterceptTouchEvent action:ACTION_DOWN06-25 15:56:09.219: D/LayoutView2(28368): onInterceptTouchEvent action:ACTION_DOWN
    06-25 15:56:09.219: D/MyTextView(28368): onTouchEvent action:ACTION_DOWN
    06-25 15:56:09.254: D/LayoutView1(28368): onInterceptTouchEvent action:ACTION_MOVE
    06-25 15:56:09.254: D/LayoutView2(28368): onInterceptTouchEvent action:ACTION_MOVE
    06-25 15:56:09.254: D/MyTextView(28368): onTouchEvent action:ACTION_CANCEL
    06-25 15:56:09.274: D/LayoutView1(28368): onInterceptTouchEvent action:ACTION_MOVE
    06-25 15:56:09.274: D/LayoutView2(28368): onTouchEvent action:ACTION_MOVE
    06-25 15:56:09.389: D/LayoutView1(28368): onInterceptTouchEvent action:ACTION_MOVE
    06-25 15:56:09.389: D/LayoutView2(28368): onTouchEvent action:ACTION_MOVE
    06-25 15:56:09.389: D/LayoutView1(28368): onInterceptTouchEvent action:ACTION_UP
    06-25 15:56:09.389: D/LayoutView2(28368): onTouchEvent action:ACTION_UP
    解读如下:
    对于DOWN事件,LayoutView1/2均没有进行拦截,直接交由MyTextView处理;
    对于首次MOVE事件,LayoutView1执行了onInterceptTouchEvent方法但未拦截,LayoutView2执行onInterceptTouchEvent方法并进行了拦截,此次MOVE事件并未直接交由LayoutView2.onTouchEvent处理,而是向MyTextView发送了一次CANCEL事件,并执行MyTextView.onTouchEvent方法;
    对于后续MOVE/UP事件,LayoutView1执行了onInterceptTouchEvent方法仍未拦截,之后便直接进入LayoutView2.onTouchEvent处理了,一直到UP事件结束都不会执行LayoutView2.onInterceptTouchEvent方法了;
  4. 执行条件如下:
    LayoutView1.onIntercept始终返回false;
    LayoutView2.onIntercept对DOWN事件返回false,但对MOVE事件返回true;
    LayoutView1.onTouchEvent始终返回true;
    LayoutView2.onTouchEvent始终返回false;上个试验这里是返回true;
    MyTextView.onTouchEvent始终返回true;
    执行结果如下:
    06-25 17:18:48.914: D/LayoutView1(1819): onInterceptTouchEvent action:ACTION_DOWN06-25 17:18:48.914: D/LayoutView2(1819): onInterceptTouchEvent action:ACTION_DOWN
    06-25 17:18:48.914: D/MyTextView(1819): onTouchEvent action:ACTION_DOWN
    06-25 17:18:48.954: D/LayoutView1(1819): onInterceptTouchEvent action:ACTION_MOVE
    06-25 17:18:48.954: D/LayoutView2(1819): onInterceptTouchEvent action:ACTION_MOVE
    06-25 17:18:48.954: D/MyTextView(1819): onTouchEvent action:ACTION_CANCEL
    06-25 17:18:48.964: D/LayoutView1(1819): onInterceptTouchEvent action:ACTION_MOVE
    06-25 17:18:48.964: D/LayoutView2(1819): onTouchEvent action:ACTION_MOVE
    06-25 17:18:49.139: D/LayoutView1(1819): onInterceptTouchEvent action:ACTION_MOVE
    06-25 17:18:49.139: D/LayoutView2(1819): onTouchEvent action:ACTION_MOVE
    06-25 17:18:49.139: D/LayoutView1(1819): onInterceptTouchEvent action:ACTION_UP
    06-25 17:18:49.139: D/LayoutView2(1819): onTouchEvent action:ACTION_UP
    解读如下:
    对于DOWN事件,LayoutView1/2均没有进行拦截,直接交由MyTextView处理;
    对于首次MOVE事件,LayoutView1执行了onInterceptTouchEvent方法但未拦截,LayoutView2执行onInterceptTouchEvent方法并进行了拦截,此次MOVE事件并未直接交由LayoutView2.onTouchEvent处理,而是向MyTextView发送了一次CANCEL事件,并执行MyTextView.onTouchEvent方法;
    对于后续MOVE/UP事件,LayoutView1执行了onInterceptTouchEvent方法仍未拦截,之后便直接进入LayoutView2.onTouchEvent处理了,一直到UP事件结束都不会执行LayoutView2.onInterceptTouchEvent方法了;
    从执行结果来看和上个案例是完全一样,为什么LayoutView2.onTouchEvent返回false,该消息却未被交由父视图执行LayoutView1.onTouchEvent呢?原因是此时事件已经不是DOWN事件了,只有在触摸的初始DOWN事件中onTouchEvent才会向父视图传递;
  5. 执行条件如下:
    LayoutView1.onIntercept始终返回false;
    LayoutView2.onIntercept始终返回false;
    LayoutView1.onTouchEvent始终返回true;
    LayoutView2.onTouchEvent始终返回false;
    MyTextView.onTouchEvent对DOWN/UP事件返回true,但对MOVE事件返回false;
    执行结果如下:
    06-25 18:05:21.029: D/LayoutView1(9095): onInterceptTouchEvent action:ACTION_DOWN06-25 18:05:21.029: D/LayoutView2(9095): onInterceptTouchEvent action:ACTION_DOWN
    06-25 18:05:21.029: D/MyTextView(9095): onTouchEvent action:ACTION_DOWN
    06-25 18:05:21.039: D/LayoutView1(9095): onInterceptTouchEvent action:ACTION_MOVE
    06-25 18:05:21.039: D/LayoutView2(9095): onInterceptTouchEvent action:ACTION_MOVE
    06-25 18:05:21.039: D/MyTextView(9095): onTouchEvent action:ACTION_MOVE
    06-25 18:05:21.224: D/LayoutView1(9095): onInterceptTouchEvent action:ACTION_MOVE
    06-25 18:05:21.224: D/LayoutView2(9095): onInterceptTouchEvent action:ACTION_MOVE
    06-25 18:05:21.224: D/MyTextView(9095): onTouchEvent action:ACTION_MOVE
    06-25 18:05:21.239: D/LayoutView1(9095): onInterceptTouchEvent action:ACTION_UP
    06-25 18:05:21.239: D/LayoutView2(9095): onInterceptTouchEvent action:ACTION_UP
    06-25 18:05:21.239: D/MyTextView(9095): onTouchEvent action:ACTION_UP
    解读如下:
    对于DOWN/MOVE/UP事件,LayoutView1/2均执行了onInterceptTouchEvent方法但未拦截,都交由MyTextView.onTouchEvent处理;即便对DOWN事件处理返回false,该MOVE事件仍未交由父视图LayoutView2.onTouchEvent处理;
    该实例和上面的实例一样,只有为了说明:只有在触摸的初始DOWN事件中onTouchEvent才会向父视图传递;


0 0
原创粉丝点击