View的事件分发机制

来源:互联网 发布:pic单片机开发环境iar 编辑:程序博客网 时间:2024/06/02 05:19

点击事件的传递规则


       所谓点击事件的事件分发,其实就是对MotionEvent事件的分发过程,即当一个MotionEvent产生了以后,系统需要把这个事件传递给一个具体的View,而这个传递的过程就是分发过程。点击事件的分发过程由三个很重要的方法来共同完成:dispatchTouchEvent、 onInterceptTouchEvent 和 onTouchEvent,下面我们先介绍一下这几个方法。

public boolean dispatchTouchEvent(MotionEvent ev)
用来进行事件的分发,其中调用了onInterceptTouchEvent和onTouchEvent。如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。

public boolean onInterceptTouchEvent(MotionCvent event)
dispatchTouchEvent()方法内部调用,用来判断是否拦截某个事件,ViewGroup中源码实现是return false,表示不拦截该事件,事件将向下传递,若手动重写该方法,使其返回ture则表示拦截,事件将终止向下传递,事件由当前ViewGroup调用onTouchEvent来处理。如果当前ViewGroup拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。

public boolean onTouchEvent(MotionEvent event)
在dispatchTouchEvet方法中调用,用来处理点击事件,返回true表示该View能处理该事件,事件终止向上传递(传递给其父VIew);返回false表示不能处理,则把事件传递给其父View的onTouchEvent()方法来处理。返回结果表示是false时,则在同一个事件序列中,当前View无法再次接收到事件。

如果事件不被中断,整个事件流向是一个类U型图,如下




分析:

  1. 如果我们没有对控件里面的方法进行重写或更改返回值,而直接用super调用父类的默认实现,那么整个事件流向应该是从Activity---->ViewGroup--->View 从上往下调用dispatchTouchEvent方法,一直到叶子节点(View)的时候,再由View--->ViewGroup--->Activity从下往上调用onTouchEvent方法。
  2. dispatchTouchEvent 和 onTouchEvent 一旦return true,事件就停止传递了(到达终点)(没有谁能再收到这个事件)。对于return true我们经常说事件被消费了,消费了的意思就是事件走到这里就是终点,不会往下传,没有谁能再收到这个事件了。
  3. dispatchTouchEvent 和 onTouchEvent return false的时候事件都回传给父控件的onTouchEvent处理。
  4. onInterceptTouchEvent方法中 return true就会交给自己的onTouchEvent的处理,如果不拦截就是继续往子控件往下传。默认是不会去拦截的,因为子View也需要这个事件,所以onInterceptTouchEvent拦截器return super.onInterceptTouchEvent()和return false是一样的,是不会拦截的,事件会继续往子View的dispatchTouchEvent传递。
  5. 由于View没有子View所以不需要onInterceptTouchEvent 来控制是否把事件传递给子View还是拦截,所以为了让View可以把事件分发给自己的onTouchEvent,View的dispatchTouchEvent默认实现(super)就是把事件分发给自己的onTouchEvent。

红色的箭头代表ACTION_DOWN 事件的流向
蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向

ACTION_DOWN事件在哪个控件消费了(return true), 那么ACTION_MOVE和ACTION_UP就会从上往下(通过dispatchTouchEvent)做事件分发往下传,就只会传到这个控件,不会继续往下传,如果ACTION_DOWN事件是在dispatchTouchEvent消费,那么事件到此为止停止传递,如果ACTION_DOWN事件是在onTouchEvent消费的,那么会把ACTION_MOVE或ACTION_UP事件传给该控件的onTouchEvent处理并结束传递。


事件分发源码解析


1.Activity对点击事件的分发过程


点击事件用MotionEvent来表示,当一个点击操作发生时,事件最先传递给当前Activity,由Activity的dispatchTouchEvent来进行事件派发,具体的工作是由Activity内部的Window来完成的。Window会将事件传递给decor view, decor view —般就是当前界面的底层容器(即setContentView所设置的View的父容器),通过Activity.getWindow.getDecorView()可以获得。我们先从 Activity 的 dispatchTouchEvent 开始分析


 /**     * Called to process touch screen events.  You can override this to     * intercept all touch screen events before they are dispatched to the     * window.  Be sure to call this implementation for touch screen events     * that should be handled normally.     *     * @param ev The touch screen event.     *     * @return boolean Return true if this event was consumed.     */    public boolean dispatchTouchEvent(MotionEvent ev) {        if (ev.getAction() == MotionEvent.ACTION_DOWN) {            onUserInteraction();        }        if (getWindow().superDispatchTouchEvent(ev)) {            return true;        }        return onTouchEvent(ev);    }


现在分析上面的代码。首先事件开始交给Activity所附属的Window进行分发,如果返 回true,整个事件循环就结束了,返回false意味着事件没人处理,所有View的onTouchEvent 都返回了 false,那么 Activity 的 onTouchEvent 就会被调用。

接下来看Window是如何将事件传递给ViewGroup的。通过源码我们知道, Window是个抽象类,而Window的superDispatchTouchEvent方法也是个抽象方法,因此我们必须找到Window的实现类才行。


public abstract boolean superDispatchTouchEvent(MotionEvent event);

那么到底 Window的实现类是什么呢?其实是 PhoneWindow,这一点从 Window的 源码中也可以看出来,因此接下来看一下PhoneWindow是如何处理点击事件的,如下所示。


public boolean superDispatchTouchEvent(MotionEvent event) {return mDecor.superDispatchTouchEvent(event);}


到这里逻辑就很清晰了, PhoneWindow将事件直接传递给了 DecorView,这个DecorView是什么呢?请看下面:
  @Override    public final View getDecorView() {        if (mDecor == null || mForceDecorInstall) {            installDecor();        }        return mDecor;    }


       我们知道,通过 ((ViewGroup)getWindow().getDecorView().fmdViewById(android.R.id.content)).getChildAt(0)这种方式就可以获取Activity所设置的View,这个mDecor显然就是getWindow().getDecorView()返回的 View,而我们通过 setContentView 设置的 View 是它的一个子view。目前事件传递到了 DecorView这里,由于DecorView继承自FrameLayout且是父View,所以最终事件会传递给View。换句话来说,事件肯定会传递到View,不然应用如何响应点击事件呢?不过这不是我们的重点,重点是事件到了View以后应该如何传递,这对我们更有用。从这里开始,事件已经传递到顶级View 了,即在Activity中通过setContentView所设置的View,另外顶级View也叫根View,顶级View一般来说都是ViewGroup。









原创粉丝点击