事件view分发

来源:互联网 发布:act1.0凯撒贝利亚淘宝 编辑:程序博客网 时间:2024/06/05 15:31

事件分发机制浅析

  一直以来想用博客写自己学习的心得体会,之前都是用word写的,容易很乱不定位,而且说不定哪天就丢了。所以决定还是用博客去写,方便分类,而且有网的地方随时看。

事件分发机制浅析

1事件分发过程由哪些方法协作完成?
答:dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()
三个方法的比较

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

几点注意

这里的对象是指Activity、ViewGroup、View
1. Android中事件分发顺序:Activity(Window) -> ViewGroup -> View
2. 事件分发过程由dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()三个方法协助完成.
关于上图有几点说明( 仅针对ACTION_DOWN事件的传递 ):
1. dispatchTouchEvent 和 onTouchEvent 一旦return true,终结事件传递;
2. dispatchTouchEvent 和 onTouchEvent return false,事件都回传给父控件的 onTouchEvent 处理。
3. dispatchTouchEvent 返回值为 false,意味着 事件停止往子View分发,并往父控件回溯 。
4. onTouchEvent 返回值为 false,意味着 不消费事件,并往父控件回溯 。
5. return super.xxxxxx() 就会让事件 依照U型的方向的完整走完整个事件流动路径 。
6. ViewGroup 的 dispatchTouchEvent 方法返回 super 的时候,默认调用 onInterceptTouchEvent
7. onInterceptTouchEvent return true时, 拦截事件并交由自己的 onTouchEvent 处理
8. onInterceptTouchEvent return super和false, 不拦截事件,并将事件传递给子View

onInterceptTouchEvent()

这里写图片描述
方法特点

1、View事件传递机制

给按钮注册一个点击事件,注册一个touch事件
这里写图片描述

这里写图片描述

这里写图片描述
    那么如果我两个事件都注册了,哪一个会先执行呢?我们来试一下就知道了,运行程序点击按钮,打印结果如下:
执行结果
ACTION_DOWN 0按下
ACTION_UP 1抬起
ACTION_MOVW 2移动
   可以看到,onTouch是优先于onClick执行的,并且onTouch执行了两次,一次是ACTION_DOWN,一次是ACTION_UP(你还可能会有多次ACTION_MOVE的执行,如果你手抖了一下)。因此事件传递的顺序是先经过onTouch,再传递到onClick。
   细心的朋友应该可以注意到,onTouch方法是有返回值的,这里我们返回的是false,如果我们尝试把onTouch方法里的返回值改成true,再运行一次,结果如下:
运行结果
   首先你需要知道一点,只要你触摸到了任何一个控件,就一定会调用该控件的dispatchTouchEvent方法。那当我们去点击按钮的时候,就会去调用Button类里的dispatchTouchEvent方法,可是你会发现Button类里并没有这个方法,那么就到它的父类TextView里去找一找,你会发现TextView里也没有这个方法,那没办法了,只好继续在TextView的父类View里找一找,这个时候你终于在View里找到了这个方法,示意图如下:
示意图
   我们知道 Button ,TextView 等基础控件的基类都是View,只要你触摸到了任何一个控件,就一定会调用该控件的dispatchTouchEvent方法。那当我们去点击按钮的时候,就会去调用Button类(实际上是基类View)里的dispatchTouchEvent方法,所以接下来看View源码中dispatchTouchEvent()方法的具体实现:dispatch:调度、派遣。
源码
   这个方法非常的简洁,只有短短几行代码!我们可以看到,在这个方法内,首先是进行了一个判断,如果mOnTouchListener != null,(mViewFlags & ENABLED_MASK) == ENABLED和mOnTouchListener.onTouch(this, event)这三个条件都为真,就返回true,否则就去执行onTouchEvent(event)方法并返回。
先看一下第一个条件,mOnTouchListener这个变量是在哪里赋值的呢?我们寻找之后在View类里发现了如下方法:
源码
   找到了,mOnTouchListener正是在setOnTouchListener方法里赋值的,也就是说只要我们给控件注册了touch事件,mOnTouchListener就一定被赋值了。
  第二个条件(mViewFlags & ENABLED_MASK) == ENABLED是判断当前点击的控件是否是enable的,按钮默认都是enable的,因此这个条件恒定为true。
   第三个条件就比较关键了,mOnTouchListener.onTouch(this, event),其实也就是去回调控件注册touch事件时的onTouch方法。也就是说如果我们在onTouch方法里返回true,就会让这三个条件全部成立,从而整个方法直接返回true。如果我们在onTouch方法里返回false,就会再去执行onTouchEvent(event)方法。
现在我们可以结合前面的例子来分析一下了,首先在dispatchTouchEvent中最先执行的就是onTouch方法,因此onTouch肯定是要优先于onClick执行的,也是印证了刚刚的打印结果。而如果在onTouch方法里返回了true,就会让dispatchTouchEvent方法直接返回true,不会再继续往下执行。而打印结果也证实了如果onTouch返回true,onClick就不会再执行了。
onTouchEvent(MotionEvent event)方法同样也是在view中定义的一个方法,主要是处理传递到view 的手势事件,包括ACTION_DOWN,ACTION_MOVE,
ACTION_UP,ACTION_CANCEL四种事件。
onClick事件的具体调用执行肯定是在View.onTouchEvent(MotionEvent event)方法源码中,接下来分析一下该函数的源码:
View类的onTouchEvent(MotionEvent event)方法的源码比较多,看提到的郭霖的博客。这里只粘贴关键代码。
源码
   首先在第14行我们可以看出,如果该控件是可以点击的就会进入到第16行的switch判断中去,而如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。在经过种种判断之后,会执行到第38行的performClick()方法,那我们进入到这个方法里瞧一瞧:
源码
   在38行我们看到了代码:performClick();这个方法从名字表义来看就是OnClick方法的调用,我们进入到该方法中去看一探究竟,是否执行了OnClick方法呢?
源码
   从上述代码可以看到,只要mOnClickListener不是null,就会去调用它的onClick方法,那mOnClickListener又是在哪里赋值的呢?经过分析后找到如下方法:setOnClickListener是View类的方法。找到源码如下。
这里写图片描述
而上述这个方法就是我们在Application层经常使用的方法,即我们给button 设置点击事件的时候就会调用该方法了,分析到这了,我们知道了OnClick方法确实是在OnTouchEvent方法中,那么除了要设置 OnClickListener,调用onClick的条件又是什么呢?我们从38行代码往前推,从第14行可以分析出:
这里写图片描述
   只要该控件是可点击的或者是长按类型的,则会进入到MotionEvent.ACTION_UP这个分支当中 ,然后经过各种条件判断,则会进入到38行的performClick()方法中。ACTION_UP是手势操作的结束点。除了改变View的一系列状态外,它的另一个重要操作是判断是否会产生Click,即开发人员setOnClick所要响应的事件。
!!当抬起之后,才去运行OnClick(View v)方法。
猜的没错onClick就在onTouchEvent中执行的,而且是在onTouchEvent的ACTION_UP事件中执行的。
   至此,一切都清晰明白了! 当我们通过调用setOnClickListener方法来给控件注册一个点击事件时,就会给mOnClickListener赋值。然后每当控件被点击时或者长按时,都会在performClick()方法里回调被点击控件的onClick方法。

经验之谈

   这样View的整个事件分发的流程就让我们搞清楚了!不过别高兴的太早,现在还没结束,还有一个很重要的知识点需要说明,就是touch事件的层级传递。我们都知道如果给一个控件注册了touch事件,每次点击它的时候都会触发一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。这里需要注意,如果你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。
  说到这里,很多的朋友肯定要有巨大的疑问了。这不是在自相矛盾吗?前面的例子中,明明在onTouch事件里面返回了false,ACTION_DOWN和ACTION_UP不是都得到执行了吗?其实你只是被假象所迷惑了,让我们仔细分析一下,在前面的例子当中,我们到底返回的是什么。
参考着我们前面分析的源码,首先在onTouch事件里返回了false,就一定会进入到onTouchEvent方法中,然后我们来看一下onTouchEvent方法的细节。由于我们点击了按钮,就会进入到第14行这个if判断的内部,然后你会发现,不管当前的action是什么,最终都一定会走到第89行,返回一个true。
源码

这里写图片描述
这里写图片描述
   是不是有一种被欺骗的感觉?明明在onTouch事件里返回了false,系统还是在onTouchEvent方法中帮你返回了true。就因为这个原因,才使得前面的例子中ACTION_UP可以得到执行。也就是action0走完了走action1,在这个if语句中的Swicth语句中,三个case总有一个符合,然后就返回true了。
总结结论

   在View的触摸屏传递机制中通过分析dispatchTouchEvent方法源码我们会得出如下基本结论:

  • 触摸控件(View)首先执行dispatchTouchEvent方法。
  • 在dispatchTouchEvent方法中先执行onTouch方法,后执行onClick方法(onClick方法在onTouchEvent中执行,下面会分析)。
  • 如果控件(View)的onTouch返回false或者mOnTouchListener为null(控件没有设置setOnTouchListener方法)或者控件不是enable的情况下会调运onTouchEvent,dispatchTouchEvent返回值与onTouchEvent返回一样。
  • 如果控件不是enable的设置了onTouch方法也不会执行,只能通过重写控件的onTouchEvent方法处理,dispatchTouchEvent返回值与onTouchEvent返回一样。
  • 如果控件(View)是enable且onTouch返回true情况下,dispatchTouchEvent直接返回true,不会调用onTouchEvent方法。
  • onTouchEvent方法中会在ACTION_UP分支中触发onClick的监听。
  • 当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发下一个action。
    参考的博客:
    郭霖的博客
    工匠若水博客
原创粉丝点击