Android-事件的触发机制

一些有的没的

         View是Android中所有控件的基类,同时也是界面层所有控件的一种抽象,代表了一个控件,如Button,TextView。

         ViewGroup也是继承view的,翻译为控件组,代表许多View的集合,如LinearLayout,ListView。

        MotionEvent事件为手机屏幕触摸事件封装类的对象,其中封装了该事件的所有信息,如触摸位置、类型、时间等参数,其中最重要的参数有三个:

① ACTION_DOWN:表示所有事件的开始

② ACTION_MOVE:滑动事件

③ ACTION_UP:表示事件的结束

View和ViewGroup的主要事件函数:

① 对应view类控件

    dispatchTouchEvent(MotionEvent ev)    分发TouchEvent

    onTouchEvent(MotionEvent ev)         处理TouchEvent

② 对应viewgroup类控件

    dispatchTouchEvent(MotionEvent ev)    分发TouchEvent

    onTouchEvent(MotionEvent ev)         处理TouchEvent

    onInterceptTouchEvent(MotionEvent en) 拦截TouchEvent

方法一:

public boolean dispatchTouchEvent(MotionEvent ev){

           returnsuper.dispatchTouchEvent(ev);

}

主要负责对onTouchEvent事件进行分类,本身不处理onTouchEvent事件,而分类的方式由其返回值决定:

返回true:将事件交给当前view本身的onTouchEvent处理;

返回false:将事件交给当前view本身的onInterceptTouchEvent处理,再由其决定事件传递的方向。

方法二:

         publicboolean onInterceptTouchEvent(MotionEvent ev){

         return super.onInterceptTouchEvent(ev);

         }

主要用于负责处理事件并改变事件的传递方向,其传递方向也由返回值决定:

返回true:事件会传递给当前控件的onTouchEvent(),而不再传递给子控件,就是所谓的Intercept(截断),并且一旦截断,此方法就不会再被调用。

返回false:事件会传递给子控件的dispatchTouchEvent方法,再由子控件分发。

方法三:

   public Boolean onTouchEvent(MotionEvent event){

                   returnsuper.onTouchEvent(event);

    }

    当已经完整的处理了该事件且不希望其他回调方法再次处理时返回true,否则返回false。

View的事件分发

         首先给一个Button注册点击和触摸事件:

输出日志:

可以看到事件的传递顺序是先经过onTouch再传递到onClick,如果将onTouch方法的返回值改为true再执行:

可以看到onClick方法不再执行了!下面从源码中看原理。

         首先需要知道的是,只要触摸到了任何一个控件,就一定会调用该控件的dispatchTouchEvent方法,看一下view中dispatchTouchEvent方法的源码:

        第一个条件mOnTouchListener是在setOnTouchListener方法里赋值的,只要我们注册了onTouch事件,这个变量就一定被赋值了;第二个条件判断当前点击的控件是否是enable的,按钮默认都是enable;第三个条件是去回调onTouch方法,如果onTouch方法返回true,整个方法就返回true,不会再向下执行(这里所说的不会向下执行就是不会再执行onTouchEvent方法,从日志中看出其不会再执行onClick方法,也可以说明,onClick方法的调用肯定在onTouchEven方法中!),如果onTouch方法返回false,整个方法会去执行onTouchEven(event)方法。

onTouch和onTouchEvent方法的区别:

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

         从dispatchTouchEvent的执行条件可以看到,onTouch能够得到执行需要两个前提条件:mOnTouchListener的值不能为空;当前点击的控件必须是enable的。

         从onTouchEven的源码中可以得到的结论:如果该控件是可以点击的,就会返回true,也即,只要触摸的控件是可以点击的,dispatchTouchEvent方法的返回值就是true。

Touch事件的层级传递:

         如果给一个控件注册了Touch事件,每次点击它的时候就会触发一系列的ACTION_DOWN、ACTION_MOVE、ACTION_UP事件,在执行ACTION_DOWN的时候返回了false,后面一系列的其他action就不会再得到执行了,也即,当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回了true,才会触发后一个action。

ViewGroup的事件分发

         首先自定一个布局MyLayout并继承LinearLayout,并在布局中定义两个button,并注册MyLayout的触摸事件以及两个button的点击事件。

运行程序,并分别点击button1,button2和MyLayout的空白区域后,输出日志如下:

        可以发现,当点击按钮的时候,MyLayout注册的onTouch方法并不会执行,只有点击空白区域的时候才会执行该方法,可以理解为button的onClick方法把事件先消费掉了,因此事件不会再继续传递。从源码中看原理:

         MyLayout继承的LinearLayout属于viewgroup,其中有一个onInterceptTouchEvent方法,其源码为:

        在MyLayout中重写此方法,返回值改为true后,再点击button1、button2以及MyLayout的空白区,输出日志如下:

        无论点击哪里,都只执行MyLayout的onTouch方法,看一下viewgroup的dispatchTouchEvent方法的源码:

        代码13行有一个条件判断,如果disallowIntercept和!onInterceptTouchEvent(ev)两者有一个为true,就会进入到这个条件判断中,disallowIntercept是指是否禁用掉事件拦截的功能,默认为false;当第一个值为false时就会完全依赖第二个值来决定是否可以进入到条件判断的内部,第二个值就是对onInterceptTouchEvent方法的返回值取反,默认值为false,取反后为true就进入到条件判断的内部了;当重写了onInterceptTouchEvent方法后返回值为true,导致所有按钮的点击事件都屏蔽了,也就说明按钮点击事件的处理就是在此条件判断内部进行的。

        情况①:当不更改onInterceptTouchEvent的返回值时,会进入到13行的条件判断,在条件语句内部19行有一个for循环,遍历了当前viewgroup下的所有字view,之后在第24行判断当前的view是不是正在点击的view,如果是的话就进入到下一个条件判断内,在第29行调用了当前view的dispatchTouchEvent方法,下面执行的活动和view的事件分发是一致的了。调用子view的dispatchTouchEvent方法后是有返回值的,当一个控件是可点击的,那么点击该控件时,dispatchTouchEvent的返回值必定是true,也就会在31行代码处给viewgroup的dispatchTouchEvent方法直接返回true,这样后面的代码就不会执行了。

        做个测试,重写button1的onTouch方法,点击button1、button2和MyLayout的空白区域后,调用过程应该为:button1的onTouch方法,button1的onClick方法,button2的onClick方法,MyLayout的onTouch方法,结果如下:

        情况②:点击空白区域。在第44行代码,如果target等于null,就会进入到之后的条件判断内部,就会在第50行调用super.dispatchTouchEvent方法,也就是会进入View中的dispatchTouchEvent方法,也就只会运行MyLayout的onTouch方法。(在介绍View的事件分发时说过,执行View的dispatchTouchEvent方法需要判断三个条件:1. mOnTouchListener是否注册了点击事件;2.控件是否可点击;3.回调onTouch方法返回值。如果三个条件都为true,则执行条件语句返回true,如果其中有一个不为true,则执行onTouchEvent方法)

        情况③:更改onInterceptTouchEvent方法的返回值为true,再分别点击button和MyLayout的空白区域,则不管点击哪里,都只会执行MyLayout的onTouch方法,输出日志为:

        笔记写到这其实应该很清楚才对,可我已经成功把自己绕晕了,所以,我决定不看源码了,直接写代码!

        自定义了一个TestButton继承Button,一个GroupView继承LinearLayout,并重写他们的dispatchTouchEvent方法、onTouchEvent方法和onInterceptTouchEvent方法。

         首先模拟TestButton处理该事件,也就是令TestButton的onTouchEvent方法的返回值为true,并点击TestButton:

点击空白区域:

        模拟GroupView处理该事件,令ViewGroup的onTouchEvent方法的返回值为true,点击TestButton:

点击空白区域:

        恩。。。。。运行完之后我发现绕的圈圈在哪里了,就是,在模拟GroupView处理事件时,点击按钮,为啥还会执行TestButton的onTouchEvent方法???(只执行到ACTION_DOWN这个动作)

         我懂了!!!

         刚才模拟GroupView处理事件时,是将GroupView的onTouchEvent的返回值改为了true,并不是将GroupView的onInterceptTouchEvent的返回值改为了true,它还会向view传递,并调用view的dispatchTouchEvent方法和onTouchEvent方法,但执行到ACTION_DOWN动作后,onTouchEvent方法的返回值为false,就不会再执行下面一系列的action了。

好啦,绕晕后梳理一哈

Activity对点击事件的分发

         点击事件发生的时候,最先传递给了Activity,由Activity的dispatchTouchEvent来进行事件的分发。具体工作由Activity内部的Window来完成,Window将事件传递给décor view(一般是当前界面的底层容器,即setContentView所设置的View的父容器,通过Activity.getWindow.getDecorView()可以获得。)

         看下Activity的dispatchTouchEvent方法:

        一般事件都是从ACTION_DOWN开始的,所以进入if语句后执行onUserInteraction()方法:

        没错这是一个空方法,主要用于屏保。接下来看一下方法superDispatchTouchEvent():

        再看mDecor.superDispatchTouchEvent()方法:

        明白啦,就是调用了ViewGroup的dispatchTouchEvent方法,也就是将事件分发给了ViewGroup!也就是说,当ViewGroup的dispatchTouchEvent方法返回true后,Activity的dispatchTouchEvent方法也返回true,表示被消费掉了(具体被谁消费掉的不管,反正不是Activity),如果返回false,则执行Activity的onTouchEvent方法,表示事件被Activity消费掉了。

ViewGroup对点击事件的分发

         ViewGroup的dispatchTouchEvent方法很长,但倔强的我还是把它贴了出啦(哈哈哈,神经病!)

        图中框出来的黄色框框,有一个if条件判断,当进入条件判断之后,就会进入一个红色框框的for循环,对子view进行遍历,判断是否是当前点击的view,看蓝色框框的if判断,似曾相识有木有!和Activity的dispatchTouchEvent里面的判断一样有木有,调用子view的dispatchTouchEvent方法,也即将点击事件分发给子view。厉害了,下面分析一下ViewGroup甩锅的两个条件(有一个为true即执行):

① disallowIntercept:表示是否禁用事件拦截的功能,默认值为false,可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改;

② onInterceptTouchEvent(ev)方法返回值的取反,默认为false:

        �那么问题还有一个,如果没有进入到这个条件判断呢(比如拦截,比如没有遍历到正确的子View,再进一步讲,子View的dispatchTouchEvent方法返回的是false,又会执行什么操作呢,看源码谢谢!)

         先看有拦截时,也就是当onInterceptTouchEvent方法返回值为true时,黄色框,return true,意思是ViewGroup要自己消费这个事件,这里又有一个坑,请跳:(如果当前的ViewGroup是可以点击的,那么当前ViewGroup的onTouchEvent方法会返回true,如果不可点击,onTouchEvent方法返回false,当返回false时,事件就会推给上一级处理,也就是会交给ViewGroup的上一级Activity处理)

        当前ViewGroup可点击输出日志:

        当前ViewGroup不可点击输出日志:

        区别在:在执行ACTION_DOWN动作的时候,执行完ViewGroup的onTouchEvent方法后又执行了Activity的onTouchEvent方法,并且在执行ACTION_UP和ACTION_MOVE动作时,都是Activity做的处理,ViewGroup已不再进行处理。

         再看另一种情况,没有遍历到目标子view,也就是点击了空白区域。绿色框框对当前的点击进行了判断,判断是不是点击了空白区域,如果点击的区域是空白区,便return super.dispatchTouchEvent()(这里有个坑,我很勇敢的跳了进去),就问!ViewGroup的父类是谁!是View啊!!!不是Activity啊!!!这里操作的就是View的dispatchTouchEvent方法啊,那么就看下一小节,View对点击事件的分发。�

View对点击事件的分发

看View的dispatchTouchEvent方法的源码:

首先三个判断:

① mOnTouchListener != null

        此变量在setOnTouchListener()方法里赋值,也即只要给控件注册了Touch事件,此变量一定不为空;

② (mViewFlags&ENABLE_MASK) == ENABLE

        判断当前点击事件是否可点击,很多View默认条件是enable的,所以默认值为true;

③ mOnTouchListener.onTouch(this,event)

        回调控件的Touch事件的onTouch方法,默认返回值为false。

         当这三个条件都为true时,则View的dispatchTouchEvent方法返回值为true,直接由当前view消费此事件,若有一个条件为false,则View的dispatchTouchEvent方法返回值为onTouchEvent()方法。那我们就来看一下onTouchEvent方法吧好吧。

        看上面的红色框框,是对控件是否可点击进行判断,如果是可以点击的,就一定会return true,该控件自己处理这个事件,就会调用上面的黄色框框的方法,performClick:

        就在这个函数里面调用了onClick有没有,也就解释了,当ACTION_UP动作结束之后才会有onClick方法的日志输出:

        如果这个控件是不可点击的,就一定会return false,则相当于view的dispatchTouchEvent方法返回了false,这里也就对应上面介绍ViewGroup的dispatchTouchEvent方法子view的dispatchTouchEvent方法返回false的情况,这种情况下从ViewGroup的dispatchTouchEvent方法中可以看出,其走的是target = mMotionTarget,而此时mMotionTarget = null,也就是target = null,也就会调用:

        �ViewGroup的super.dispatchTouchEvent方法就是view的dispatchTouchEvent方法,控件不可点击,也就直接调用当前viewGroup的onTouchEvent方法了,如果ViewGroup的onTouchEvent也返回false,则会继续调用Activity的onTouchEvent方法。

总结,这个图还是蛮好的~~~

         事件由Activity的dispatchTouchEvent开始,将事件传递给当前的Activity的下一层ViewGroup;事件分发给ViewGroup时,调用其dispatchTouchEvent方法接着分发,首先会调用ViewGroup的onInterceptTouchEvent方法进行拦截,如果返回false(不拦截),就开始遍历ViewGroup的子view,将事件分发给子view,若返回值为true(拦截),事件由ViewGroup自己处理,将会调用ViewGroup自己的onTouchEvent方法,若当前ViewGroup是可点击的,则onTouchEvent返回true,自己消费事件,若当前ViewGroup不可点击,则onTouchEvent返回false,交由上一级处理;当事件分发到View时,首先还是调用view的dispatchTouchEvent方法,若返回true,则当前view消耗掉此事件,若返回true,则会调用当前view的onTouchEvent方法,同样,当前view是可以点击的,则返回true,对事件进行处理,返回false,则交由上一级处理。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容