android基础 -View的事件分发机制

Posted 我叫白小飞

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android基础 -View的事件分发机制相关的知识,希望对你有一定的参考价值。

前言

view是我们经常使用的组件,无论是像button、textview还是viewgroup等,都是view的子类。在使用过程中,我们经常碰到的问题就是view的华东冲突,它的解决方法的理论基础就是view的事件分发机制,因此要掌握好view的分发机制是十分重要的。

1 点击事件的传递规则

所谓点击事件的事件分发其实就是对MotionEvent事件的分发过程。当MotionEvent产生后,系统需要将这个事件传递给一个具体的view去做处理,而这个传递的过程将就是分发过程。点击事件的分发过程由三个很重要的方法共同完成:dispathTouchEvent、onInterceptTouchEvent和onTouchEvent,下面我们介绍一下这几个方法:

1.1 public boolean dispatchTouchEvent(MotionEvent ev)

 /**
     * 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);
    

注释说明:调用以处理触摸屏事件。您可以覆盖此选项,以便在将所有触摸屏事件发送到窗口之前截获它们。对于应该正常处理的触摸屏事件,一定要调用此实现。 意思是这个事件是触摸事件的起点,这时的事件还未传带窗口,我们可以重写这个方法,做一些自己想要的处理。如果事件能够传递给当前view,那么此方法一定会被调用,返回结果受当前view和onTouchEvent 和 下级view的dispatchTouchEvent方法的影响,表示是否取消当前事件。

1.2 public boolean onInterceptTouchEvent(MotionEvent ev)

 /**
     * Implement this method to intercept all touch screen motion events.  This
     * allows you to watch events as they are dispatched to your children, and
     * take ownership of the current gesture at any point.
     *
     * <p>Using this function takes some care, as it has a fairly complicated
     * interaction with @link View#onTouchEvent(MotionEvent)
     * View.onTouchEvent(MotionEvent), and using it requires implementing
     * that method as well as this one in the correct way.  Events will be
     * received in the following order:
     *
     * <ol>
     * <li> You will receive the down event here.
     * <li> The down event will be handled either by a child of this view
     * group, or given to your own onTouchEvent() method to handle; this means
     * you should implement onTouchEvent() to return true, so you will
     * continue to see the rest of the gesture (instead of looking for
     * a parent view to handle it).  Also, by returning true from
     * onTouchEvent(), you will not receive any following
     * events in onInterceptTouchEvent() and all touch processing must
     * happen in onTouchEvent() like normal.
     * <li> For as long as you return false from this function, each following
     * event (up to and including the final up) will be delivered first here
     * and then to the target's onTouchEvent().
     * <li> If you return true from here, you will not receive any
     * following events: the target view will receive the same event but
     * with the action @link MotionEvent#ACTION_CANCEL, and all further
     * events will be delivered to your onTouchEvent() method and no longer
     * appear here.
     * </ol>
     *
     * @param ev The motion event being dispatched down the hierarchy.
     * @return Return true to steal motion events from the children and have
     * them dispatched to this ViewGroup through onTouchEvent().
     * The current target will receive an ACTION_CANCEL event, and no further
     * messages will be delivered here.
     */
    public boolean onInterceptTouchEvent(MotionEvent ev) 
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) 
            return true;
        
        return false;
    

此方法在viewgroup中,大概意思是说:实现此方法来截获所有触摸屏运动事件。这允许您在将事件发送给子类时监听这些事件,并在任何时候拥有当前手势的所有权。 这里可以判断是否拦截某个事件,如果当前view拦截了某个事件,那么在同一事件序列当中,就不会在调用此方法,返回结果表示是否拦截当前事件。

1.3 public boolean onTouchEvent(MotionEvent event)

 /**
     * Called when a touch screen event was not handled by any of the views
     * under it.  This is most useful to process touch events that happen
     * outside of your window bounds, where there is no view to receive it.
     *
     * @param event The touch screen event being processed.
     *
     * @return Return true if you have consumed the event, false if you haven't.
     * The default implementation always returns false.
     */
    public boolean onTouchEvent(MotionEvent event) 
        if (mWindow.shouldCloseOnTouch(this, event)) 
            finish();
            return true;
        

        return false;
    

次方的注释:当触摸屏事件未被其下的任何视图处理时调用。这对于处理发生在窗口边界之外的触摸事件非常有用,因为在那里没有视图可以接收它。 意思是这个拦截方法为最后的拦截方法,在它之前的拦截方法未做任何拦截的时候,才会调用它。

1.4 关系

其实这三个方法之间是有关系的,我们通过一个具体的栗子看一下,具体如下:
我们这样做一个布局

这个点击事件的传递过程如下图所示:


此图侵删!

这个看起来还是很繁琐的,但是我们不关心在灰色部分的事件传递,我们只关心绿色部分的,activity和我们的布局位置的事件传递。

那么这几个位置的事件关系是如何的呢?继续看图:

事件从activity传递到view的过程如上图所示,此图为不做任何处理时的事件传递过程,一路就会返回true,直到返回给顶层window。如果我们给view设置了onClickListener()事件,等于就是在view 中拦截了事件,做了处理,此时就不会view就不会返回false。如下图所示:

view在onClick时处理该事件,所以该view的onTouchEvent方法返回true。
通过上图我们大致了解了事件的传递过程,一下是对于事件传递机制的一些总结:

  1. 事件序列是指从手指接触屏幕开始到抬起,事件从down -> move -> … -> move -> up等的一系列事件。
  2. 正常情况下,一个事件序列智能呗一个view拦截消耗,因为如果该view拦截,那么序列里的所有事件会直接交给它处理,因此同一个事件序列不能分别由两个view同时处理。
  3. 某view如果拦截,那么它的onInterceptTouchEvent() 方法不会再被调用。
  4. 某一view如果拦截了事件,但是没有做任何处理,并且不消耗ACTION_DOWN事件,那么这个事件序列中的其他事件不会再交给它处理,直接调用父元素的onTouchEvent.
  5. 如果view不消耗除ACTION_DOWN外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent方法并不会被调用,并且当前view可以持续收到后续事件,最终这些点击事件会传递给activity处理。
  6. viewgroup默认不拦截任何事件。
  7. view没有 onInterceptTouchEvent 方法,事件传给它后会调用onTouchEvent方法。
  8. view的onTouchEvent方法默认都会消耗事件,除非它是不可点击的(clickable 和 longClickable 同时为false)。
  9. view的enable属性不影响onTouchEvent返回默认值。
  10. 事件的传递过程是由外向内的。

未完待续。。。。。

以上是关于android基础 -View的事件分发机制的主要内容,如果未能解决你的问题,请参考以下文章

Android基础教程第4版 PDF下载

Android 面试题总结之Android 基础

Android 面试题总结之Android 基础

Android面试题Java基础

Android 面试题总结之Android 基础

Android 面试题总结之Android 基础