Android事件分发机制

2019-10-16  本文已影响0人  椰子

主要方法:

1.dispatchTouchEvent(MotionEvent ev):用来进行事件分发
2.onInterceptTouchEvent(MotionEvent ev):判断是否拦截事件(只存在于ViewGroup中)
3.onTouchEvent(MotionEvent ev):处理点击事件

一、事件分发:Activity

Activity.java
/**
     * 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();
        }
        //如果返回false,就走下边的onTouchEvent()
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

/**
     * 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) {
        //超出可触摸边界就直接finish掉
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }

        return false;
    }

传递流程:
Activity:dispatchTouchEvent--->PhoneWindow:superDispatchTouchEvent--->DecorView:superDispatchTouchEvent--->ViewGroup:dispatchTouchEvent
如果子view没有消费触摸事件,最终就是走了Activity的onTouchEvent()的事件分发

二、事件分发:ViewGroup:dispatchTouchEvent()

step1: 在DOWN的时候,先调用了cancelAndClearTouchTargets(ev);和resetTouchState();清楚掉之前的点击状态并重置标记位;

ViewGroup--line:2559
// Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

step2:检测是否需要拦截。这里定义了一个boolean intercepted来记录是否需要拦截

line:2567
// Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
              //用来禁止viewGroup拦截除了down以外的事件,
              //通过子view调用requestDisallowInterceptTouchEvent()方法来控制
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }

step3:判断不是DOWN事件后,通过一个for循环,拿到子view,又进行了一个判断canViewReceivePointerEvents(),判断能否接收到点击事件。

line:2647
//如果不可见或在执行动画或不在可触摸区域范伟之内,就不可响应触摸事件,continue
if (!canViewReceivePointerEvents(child) || 
!isTransformedTouchPointInView(x, y, child, null)) {
      ev.setTargetAccessibilityFocus(false);
      continue;
 }

line:2940
/**
     * Returns true if a child view can receive pointer events.
     * 如果是可见的且没有在执行动画,就可以接收点击事件
     * @hide
     */
    private static boolean canViewReceivePointerEvents(@NonNull View child) {
        return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                || child.getAnimation() != null;
    }

当可以接收点击事件,调用该方法,传入子view,当child不为空的时候,调用子view的dispatchTouchEvent(),交给子view处理。

line:2662
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {...}

line:2988
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
        ...
}

走完step1—step3,就完成了viewGroup到view的事件传递,并返回了一个handled的布尔值,表示子view消耗了点击事件。
小结:dispatchTouchEvent()是用来分发的,最终的处理都是在onTouchEvrent()

ViewGroup_1.png ViewGroup_2.png ViewGroup_3.png ViewGroup_4.png

三 、View:dispatchTouchEvent()

View--line:12509
/**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     *
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    public boolean dispatchTouchEvent(MotionEvent event) {
        ...
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
      ...
        return result;
    }

由上面源码可以得出,onTouchEvent()和onTouch()方法优先级及控制关系:
①如果onTouch()方法返回值是true(事件被消费)时,则onTouchEvent()方法将不会被执行;
②只有当onTouch()方法返回值是false(事件未被消费,向下传递)时,onTouchEvent方法才被执行。

View---line:13718
public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();

        //clickable标记view是否可以被消费
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return clickable;
        }
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
      ...
  }

clickable标记view可否消费事件,哪怕view是disable得,但只要它可点击或长按,就仍然消费事件,只是说没有消费他们。

View--onTouchEvent--ACTION_UP line:13775
//如果设置了长按事件,且长按事件返回true,则不会执行onClick事件
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClickInternal();
                                }
                            }
                        }

View的长按事件在ACTION_DOWN中处理,UP中处理点击事件,如果500ms内没有触发UP事件,则长按事件会执行(设置了长按监听器)。如果设置了长按事件,且长按事件返回true,则不会执行onClick事件,返回的false,则长按点击事件都会执行。

由此可见,给View设置监听OnTouchListener时,重写的onTouch()方法,其优先级比onTouchEvent()要高,假如onTouch方法返回false,会接着触发onTouchEvent,反之onTouchEvent方法不会被调用。内置诸如click事件的实现等等都基于onTouchEvent,假如onTouch返回true,这些事件将不会被触发。OnClickListener,其优先级最低,即处于事件传递的尾端。
_
_
_
_

总结

类似领导向下属分配任务,逐级下传,中途遇到处理不了就报告上级。


事件分发流程 .png
  1. 一个事件序列从手指接触屏幕到手指离开屏幕,在这个过程中产生一系列事件,以DOWN事件开始,中间含有n个MOVE事件,以UP事件结束;
  2. 正常情况下,一个事件序列只能被一个View拦截并消耗;
  3. 某个View一旦决定拦截,那么这个事件序列都将由它的onTouchEvent处理,并且它的onInterceptTouchEvent不会再调用;
  4. 某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回false),那么同一事件序列中其他事件都不会再交给它处理。并且重新交由它的父元素处理(父元素onTouchEvent被调用);
  5. 事件的传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent反法可以在子View中干预父元素的事件分发过程,但ACTION_DOWN除外
  6. ViewGroup默认不拦截任何事件,即onInterceptTouchEvent默认返回false。View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就被调用;
  7. VIew的onTouchEvent默认会消耗事件(返回true),除非它是不可点击的(clickable和longClickable同时为false)。View的longClickable默认都为false,clickable要分情况,比如Button的clickable默认为true,TextView的clickable默认为false;
  8. View的enable属性不影响onTouchEvent的默认返回值。哪怕一个View是disable状态,只要它的clickable或者longClickable有一个为true,那么它的onTouchEvent就返回true;
  9. onClick会响应的前提是当前View是可点击的,并且收到了ACTION_DOWN和ACTION_UP事件,并且受长按事件影响,当长按事件返回true,onClick不会响应;
  10. onLongClick在ACTION_DOWN里判断是否进行响应,要想执行长按事件该View必须是longClickable的并且设置了OnLongClickListener;

onInterceptTouchEvent()方法返回值

一般重载这个函数地方就是你自定义了一个布局,extends LinearLayout等等布局,一般除了自己的业务处理外,返回值只有两种。
第一种:返回值为false
作用:让自定义布局上面的所有子view 例如button imageview 等可以被点击

第二种:直接返回true
作用:让自定义布局上面的所有子view不可以被点击

 @Override  
    public boolean onInterceptTouchEvent(MotionEvent ev) {  
        return true;  
    }

分分钟明白onTouchEvent事件分发

首先确定有三种,由内向外依次为:
1、View自己的onTouchEvent
2、ViewGroup的onTouchEvent,由于要管理它的子View的onTouchEvent,所以多了个onInterceptTouchEvent(鼓励重载这个而不是dispatchTouchEvent,因为后者是对ViewGroup共性的提取,前者才是针对个例)
3、Activity的onTouchEvent
三者都是先处理setOnTouchEvent的onTouch事件,返回true表示不想下传递,就不进入到onTouchEvent中了

先说ViewGroup的onInterceptTouchEvent,两种情况:
1.返回值为True,代表拦截这次事件,直接进入到ViewGroup的onTouchEvent中,就不会进入到View的onTouchEvent了
2.返回值为False,代表不拦截这次事件,不进入到ViewGroup的onTouchEvent中,直接进入到View的onTouchEvent中

再说三者的onTouchEvent
1.View的onTouchEvent返回为false表示view处理完onTouchEvent后不消费这次事件,那么这个事件就会继续传递到他的上一层ViewGroup的onTouchEvent事件中,返回true的话就传递完毕,进入不到ViewGroup的onTouchEvent中了

2.ViewGrop的onTouchEvent返回为false表示这个ViewGroup处理完onTouchEvent后不消费这次事件,这个事件就会继续传递到activity的onTouchEvent中,返回为true的话就传递完毕,进入不到activity的onTouchEvent中了

3.activity的onTouchEvent,就这样吧 `(∩_∩)′

总之,如果最里层的返回false就会交给他的上一层处理,否则就会消费这次事件,停止传递,over!

上一篇下一篇

猜你喜欢

热点阅读