zoukankan      html  css  js  c++  java
  • 通俗理解Android事件分发与消费机制

    深入:Android Touch事件传递机制全面解析(从WMS到View树)

    通俗理解Android事件分发与消费机制

      说起Android滑动冲突,是个很常见的场景,比如SliddingMenu与ListView的嵌套,要解决滑动冲突,不得不提及到View的事件分发机制。


      Touch事件传递规则分析
    首先,我们要知道Touch事件是包装在MotionEvent对象中的,在手指与屏幕接触过程中产生一系列事件,典型的事件有以下三种:
    ACTION_DOWN:手指刚接触屏幕的瞬间

    ACTION_MOVE:手指在屏幕上滑动

    ACTION_UP:手指刚离开屏幕的瞬间

     

    那么,Android中Touch事件是一个怎样的传递过程呢?

     

     1 , 事件分发:public boolean dispatchTouchEvent(MotionEvent ev)

    Touch事件发生时Activity的dispatchTouchEvent(MotionEvent ev)方法会将事件传递给最外层View的dispatchTouchEvent(MotionEvent ev)方法,该方法对事件进行分发。分发逻辑如下:
        如果return true,事件会由当前View的dispatchTouchEvent方法进行消费,同时事件会停止向下传递;

        如果return false,事件分发分为两种情况:
          如果当前 View 获取的事件直接来自 Activity,则会将事件返回给Activity的onTouchEvent进行消费;
          如果当前 View 获取的事件来自外层父控件,则会将事件返回给父View的onTouchEvent进行消费。

        如果return super.dispatchTouchEvent(ev),事件分发分为两种情况:

         如果当前View是ViewGroup,则事件会分发给onInterceptTouchEvent方法进行处理;

         如果当前View是普通View,则事件直接交给onTouchEvent方法进行处理

       2, 事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)

    此方法只有ViewGroup才有, Activity与普通View没有。上面已经提到,如果当前ViewGroup的dispatchTouchEvent(事件分发)返回super.dispatchTouchEvent(ev), 那么事件会传递到传递到onInterceptTouchEvent方法, 该方法对事件进行拦截。拦截逻辑如下:
        如果return true,则表示拦截该事件,并将事件交给当前View的onTouchEvent方法;

        如果return false,则表示不拦截该事件,并将该事件交由子View的dispatchTouchEvent方法进行事件分发,重复上述过程;

        如果return super.onInterceptTouchEvent(ev), 事件拦截分两种情况:       

           如果该View(ViewGroup)存在子View且点击到了该子View, 则不拦截, 继续分发给子View 处理, 此时相当于return false。

         如果该View(ViewGroup)没有子View或者有子View但是没有点击中子View(此时ViewGroup相当于普通View), 则交由该View的onTouchEvent响应,此时相当于return true。 

    一般的LinearLayout、 RelativeLayout、FrameLayout等ViewGroup默认不拦截, 而ScrollView、ListView等ViewGroup则可能拦截,得看具体情况。

       3, 事件响应:public boolean onTouchEvent(MotionEvent ev)

    上面已经提到,在dispatchTouchEvent(事件分发)返回super.dispatchTouchEvent(ev)并且onInterceptTouchEvent进行拦截(事件拦截返回true)的情况下,那么事件会传递到onTouchEvent方法,该方法对事件进行响应。响应逻辑如下:
        如果return true,则表示响应并消费该事件;
        如果return fasle,则表示不响应事件,那么该事件将会不断向上层View的onTouchEvent方法传递,直到某个View的onTouchEvent方法返回true,如果到了最顶层View还是返回false,那么认为该事件不消耗,则在同一个事件系列中,当前View无法再次接收到事件,该事件会交由Activity的onTouchEvent进行处理;
        如果return super.dispatchTouchEvent(ev),事件处理分为两种情况:

        如果该View是clickable或者longclickable的,则会返回true, 表示消费了该事件, 与返回true一样;

        如果该View不是clickable或者longclickable的,则会返回false, 表示不消费该事件,将会向上传递,与返回false一样.

     

    上述三个方法到底有什么区别与联系呢?我们通过一段伪代码来表示:

     事件分发伪代码

    [java] view plain copy
     
    1. public boolean dispatchTouchEvent(MotionEvent ev){  
    2.     boolean consume = false;  
    3.     if(onInterceptTouchEvent(ev)){ // 如果onInterceptTouchEvent返回true  
    4.         consume = onTouchEvent(ev);  // 则交由该View的onTouchEvent方法  
    5.     } else {  
    6.         consume = child. dispatchTouchEvent(ev); // 否则交由子View的dispatchTouchEvent事件进行分发  
    7.     }  
    8.     return consume; // 如果成功消费该事件,则返回true,然后停止传递,否则返回false  
    9. }  

     -------------------------------------------------------------------分割线------------------------------------------------------------

    各组件对应方法的有无情况:

     注: Activity的dispatchTouchEvent最终是调用了Window对应DecorView的dispatchTouchEvent, 相当于ViewGroup; onTouchEvent是Activity自带的方法并不是DecorView的onTouchEvent; 同时,没有onInterceptTouchEvent方法是因为Window并没有回调该方法。

    返回值作用:true和false标志事件是否被消费。

    如果消费了就不再传递给其他控件了,如果没有消费则还会传递给父控件或者子控件,触发相应控件的事件处理函数。

    控件默认返回值

    1,对于ViewGroup的onInterceptTouchEvent方法:

      如果存在子View且点击到了子View, 则不拦截, 继续分发给子View 处理, 此时返回super.onInterceptTouchEvent(ev) 就相当于return false。

      如果没有子View或者有子View但是没有点击中子View(此时ViewGroup相当于普通View), 则交由当前View的onTouchEvent响应,此时返回super.onInterceptTouchEvent(ev) 相当于return true。 

    2,对于View的onTouchEvent方法: 如果是clickable或者longClickable的,则返回true消费该事件; 否则返回false不消费该事件,从而往上传递.

     -------------------------------------------------------------------分割线------------------------------------------------------------

     注:同一个事件序列是指从手指接触屏幕的那一刻开始,到手指离开屏幕那一刻结束,在这过程中所产生的一系列事件,这个事件序列以down事件开始,以up事件结束,中间含有数量不定的move事件.

    事件分发与消费的规则总结:   

      (1)事件的分发是以隧道方式由上到下的, 即事件总是先传递给父元素, 然后再由父元素分发给子元素。对于onTouchEvent事件,如果返回false,则会以冒泡方式向上传递。 顶级View接收到事件之后,就会按相应规则去分发事件。如果一个View的onTouchEvent方法返回false,那么将会交给父容器的onTouchEvent方法进行处理,以冒泡方式逐级往上,如果所有的View都不处理该事件,则交由Activity的onTouchEvent进行处理。就跟工作中遇到了难题,逐级找领导解决一个道理,领导解决不了,再找上一级领导。

        (2)正常情况下,一个事件序列只能被一个View拦截且消耗。某个View一旦进行事件拦截,那么这一个事件序列都只能交由他处理,并且onInterceptTouchEvent也不会被再次调用。因此,正常情况下一个事件是不能交给两个View来处理的,当然,特殊做法就是在View的onTouchEvent处理完之后再返回false,强行交给其他View处理。

        (3)如果某一个View开始处理事件,如果他不消耗ACTION_DOWN事件(也就是onTouchEvent返回false),则同一事件序列比如接下来进行ACTION_MOVE、ACTION_UP,则不会再交给该View处理,并且事件将重新提交给它的父元素处理。就像工作中做一件事情,你要么做完,要么你就不要做这件事了。

        (4)ViewGroup的onInterceptTouchEvent方法默认返回false,即不拦截任何事件,而交给子View进行分发处理(前提是有子View)。

        (5)普通View(比如TextView、ImageView,非ViewGroup)没有onInterceptTouchEvent方法, 一旦有事件传递给它,它的onTouchEvent方法就会被调用。正常情况下,它们都会消耗事件(返回true),除非它们是不可点击的(clickable和longClickable都为false),那么就会交由父容器的onTouchEvent处理。View的longClickable默认都是false的,而对于clickable则要分情况,比如Button的clickable默认你是true,而TextView默认是false.

        (6)如果View不消耗除down以外的其他事件, 此时父View的onTouchEvent并不会被调用, 并且当前View可以持续收到后续事件,最终这些事件会传递给Activity处理.

      (7)View的enable属性不影响onTouchEvent的默认返回值,只要它clickable或者longClickable为true,则onTouchEvent就会返回true。

      (8)  如果当前View是可点击的,并且它收到了down和up事件(以down开始,以up结束),则它的click事件就会触发;对于onLongClick,则只要当前View是longClickable的并接收到down事件且超过了系统默认的long时间,则就会触发,只与down事件有关而与up事件无关.

        (9)点击事件分发过程如下 dispatchTouchEvent—->OnTouchListener的onTouch方法—->onTouchEvent-->OnClickListener的onClick方法。也就是说,我们平时调用的setOnClickListener,优先级是最低的,所以,OnTouchListener的onTouch方法如果返回true,则不响应onClick方法...

      (10) 子View可以通过requestDisallowInterceptTouchEvent方法请求父控件不要拦截事件,从而干预事件的分发过程,但是down事件除外,无法干预到

     (11)如果一个View监听了onTouch,则在onTouch里面应该返回false,否则onTouchEvent事件及点击、长按事件就无法监听到

       (12)如果ViewGroup中的子View将传递的事件消费掉,ViewGroup的onTouch将无法接收到任何事件, 但onTouchEvent还是能接收到的;  如果是View的onTouchEvent消费,则该View的onTouch仍然能接收到事件,因为此时onTouch的调用在onTouchEvent之前。   总之,对于View, 无论onTouchEvent消费与否,都会触发View的onTouch事件, 因为onTouch的调用在onTouchEvent之前。

     

     -------------------------------------------------------------------分割线------------------------------------------------------------

    【参考资料】

    Trinea 

    Android Touch事件传递机制

    郭林

    Android事件分发机制完全解析,带你从源码的角度彻底理解(上) 

    Android事件分发机制完全解析,带你从源码的角度彻底理解(下) 

    鸿洋

    Android  View事件分发机制 源码解析 (上)

    Android ViewGroup事件分发机制 源码解析(下) 

    工匠若水

    Android触摸屏事件派发机制详解与源码分析一(View篇)

    Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)

    Android触摸屏事件派发机制详解与源码分析三(Activity篇) 

    AigeStudio 

    Android事件分发完全解析之为什么是她 

    Android事件分发完全解析之事件从何而来

     

    click相关:  

    Android中onTouchEvent, onClick及onLongClick的调用机制(一) 

    Android中onTouchEvent, onClick及onLongClick的调用机制(二)

  • 相关阅读:
    清除陷入CLOSE_WAIT的进程
    Eclipse
    远程连接elasticsearch遇到的问题
    Linux环境Nginx安装
    CentOS安装mysql
    py2exe使用方法
    Python3.4如何读写Excel
    getPhysicalNumberOfCells 与 getLastCellNum的区别
    浅析MySQL中exists与in的使用
    【MongoDB for Java】Java操作MongoDB
  • 原文地址:https://www.cnblogs.com/wytiger/p/5235393.html
Copyright © 2011-2022 走看看