Android的事件分发机制学习

345 阅读6分钟

1.概括

Android的事件分发机制总结一句话就是:责任链模式,事件层层传递,直到被消费。其流程为:Activity->ViewGroup->View

2.事件的分发、拦截、消费

类型 事件 Activity ViewGroup View
事件分发 dispatchTouchEvent
事件拦截 onInterceptTouchEvent
事件处理 onTouchEvent

3.源码分析

  • Activity
/**
     * 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.
     * 返回true事件才会被消费,返回false将不会触发任何事件
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //第一步:当手指按下屏幕,调用onUserInteraction()方法
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        
        
        //第二步:调用window的superDispatchTouchEvent()方法,如果返回true,则分发事件。
        if (getWindow().superDispatchTouchEvent(ev)) {
            //实际上这里调用的PhoneWindow的superDispatchTouchEvent()方法
            //PhoneWindow是Window的唯一实现类,我们知道PhoneWindow中持有一个decorView,
            //window.decorView是一个FrameLayout,它是内容视图的真正根布局,
            //我们再来看看getWindow().superDispatchTouchEvent(ev)具体实现
            /**
            *@Override
            *public boolean superDispatchKeyEvent(KeyEvent event) {
            *    //最终调用到了decorView的superDispatchKeyEvent()方法
            *    //从而分发到ViewGroup中
            *    return mDecor.superDispatchKeyEvent(event);
            *}
            */
            return true;
        }
        //否则调用Activity的onTouchEvent()消费事件
        return onTouchEvent(ev);
    }
    /**
     * Called whenever a key, touch, or trackball event is dispatched to the
     * activity.  Implement this method if you wish to know that the user has
     * interacted with the device in some way while your activity is running.
     * This callback and {@link #onUserLeaveHint} are intended to help
     * activities manage status bar notifications intelligently; specifically,
     * for helping activities determine the proper time to cancel a notfication.
     *
     * <p>All calls to your activity's {@link #onUserLeaveHint} callback will
     * be accompanied by calls to {@link #onUserInteraction}.  This
     * ensures that your activity will be told of relevant user activity such
     * as pulling down the notification pane and touching an item there.
     *
     * <p>Note that this callback will be invoked for the touch down action
     * that begins a touch gesture, but may not be invoked for the touch-moved
     * and touch-up actions that follow.
     *
     * @see #onUserLeaveHint()
     * 翻译:
     * 当你的Activity正在运行的时候,如果你想知道用户和设备正在交互,可以实现这个方法。
     * 不管什么时候,按键、触摸、trackball事件都会被分发到Activity
     */
     public void onUserInteraction() {//空方法
    }
  • ViewGroup

ViewGroup的dispatchTouchEvent()方法代码非常多,下面咱们只看关键代码

 override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
    // Check for interception.
    final boolean intercepted;
    if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) {
        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;
    }
    ------------------上面这段代码是检查是否需要拦截事件------------------
    
    ------------------核心代码-----------------------------
    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
        // Child wants to receive touch within its bounds.
        //子控件想在范围内接收触摸事件。核心方法dispatchTransformedTouchEvent()
        //下面我们来看dispatchTransformedTouchEvent()方法的实现
       --------省略很多代码--------
    }
    return handled
}

 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.
        // ACTION_CANCEL动作是一个特例。我们不需要执行任何转换
        // 或过滤。重要的是动作本身,而不是内容。
        //MotionEvent.ACTION_CANCEL:表示当前手势已经终止
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {//如果Child View为空,则只分发到ViewGroup中
                handled = super.dispatchTouchEvent(event);
            } else {//否则分发到Child View
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

        // Calculate the number of pointers to deliver.
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        // If for some reason we ended up in an inconsistent state where it looks like we
        // might produce a motion event with no pointers in it, then drop the event.
        if (newPointerIdBits == 0) {
            return false;
        }

        // If the number of pointers is the same and we don't need to perform any fancy
        // irreversible transformations, then we can reuse the motion event for this
        // dispatch as long as we are careful to revert any changes we make.
        // Otherwise we need to make a copy.
        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }

        // Perform any necessary transformations and dispatch.
        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }

            handled = child.dispatchTouchEvent(transformedEvent);
        }

        // Done.
        transformedEvent.recycle();
        return handled;
    }
  • View

通常来说View是没有事件分发的,因为它处在事件分发的最低端,但事实上View也是有事件分发的,只不过你的它的事件分发是用来区分setOnTouchListener、 onTouchEvent、setOnLongClickListener、setOnClickListener

/**
 * 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) {
    ***省略代码***
    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //noinspection SimplifiableIfStatement
        //ListenerInfo 是一个静态类,里面持有各种监听事件的实现(当然默认是没有实现)
        ListenerInfo li = mListenerInfo;
        //最先判断OnTouchListener是否为空,不为空执行OnTouchListener.onTouch()
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        //然后判断执行onTouchEvent()方法
        //最后会在onTouchEvent()中判断是否执行长按事件和点击事件
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    ***省略代码****

    return result;
}

public boolean onTouchEvent(MotionEvent event) {
***省略代码***
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
    switch (action) {
        case MotionEvent.ACTION_UP://是否触发点击事件
        ***省略代码***
        boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
        if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
        
            ***省略代码***
            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.
                  //使用Runnable执行而不是直接调用它,
                  //这样能允许在单击动作开始之前更新视图的其他视觉状态。
                //PerformClick是Runnable的实现类,它执行的是performClickInternal(),
                //而performClickInternal()会调用performClick(),
                //最终performClick()中会调用OnClickListener.onClick()
         /*************************************************
                private final class PerformClick implements Runnable {
                    @Override
                    public void run() {
                        performClickInternal();
                    }
                }
                 private boolean performClickInternal() {
                    // Must notify autofill manager before performing the click actions to avoid scenarios where
                    // the app has a click listener that changes the state of views the autofill service might
                    // be interested on.
                    notifyAutofillManagerOnClick();
            
                    return performClick();
                }
                
                public boolean performClick() {
                    ******省略代码********
                    final boolean result;
                    final ListenerInfo li = mListenerInfo;
                    if (li != null && li.mOnClickListener != null) {
                        playSoundEffect(SoundEffectConstants.CLICK);
                        li.mOnClickListener.onClick(this);
                        result = true;
                    } else {
                        result = false;
                    }
                    ****省略代码*****
                    return result;
                }
                
       ************************************************/
                    if (mPerformClick == null) {
                        mPerformClick = new PerformClick();
                    }
                    if (!post(mPerformClick)) {
                        performClickInternal();
                    }
                }
            }
            ***省略代码***
        break;

    case MotionEvent.ACTION_DOWN://是否触发长按事件
        if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
            mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
        }
        mHasPerformedLongPress = false;
        //重点看checkForLongClick()方法
        /*******************************************************
       private void checkForLongClick(int delayOffset, float x, float y) {
        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
            mHasPerformedLongPress = false;
            //CheckForLongPress也是Runnable的实现类
            if (mPendingCheckForLongPress == null) {
                mPendingCheckForLongPress = new CheckForLongPress();
            }
            mPendingCheckForLongPress.setAnchor(x, y);
            mPendingCheckForLongPress.rememberWindowAttachCount();
            mPendingCheckForLongPress.rememberPressedState();
            //通过延迟发送实现长按事件
            postDelayed(mPendingCheckForLongPress,
                    ViewConfiguration.getLongPressTimeout() - delayOffset);
        }
    }
    private final class CheckForLongPress implements Runnable {
        private int mOriginalWindowAttachCount;
        private float mX;
        private float mY;
        private boolean mOriginalPressedState;

        @Override
        public void run() {
            if ((mOriginalPressedState == isPressed()) && (mParent != null)
                    && mOriginalWindowAttachCount == mWindowAttachCount) {
                //最终调用performLongClick(),而它又调用performLongClickInternal()
                //最后调用OnLongClickListener.onLongClick()方法
                //其实跟点击是一个路子
                if (performLongClick(mX, mY)) {
                    mHasPerformedLongPress = true;
                }
            }
        }

        public void setAnchor(float x, float y) {
            mX = x;
            mY = y;
        }

        public void rememberWindowAttachCount() {
            mOriginalWindowAttachCount = mWindowAttachCount;
        }

        public void rememberPressedState() {
            mOriginalPressedState = isPressed();
        }
    }
    public boolean performLongClick() {
        return performLongClickInternal(mLongClickX, mLongClickY);
    }    
    private boolean performLongClickInternal(float x, float y) {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);

        boolean handled = false;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLongClickListener != null) {
            handled = li.mOnLongClickListener.onLongClick(View.this);
        }
       ******省略代码******
        return handled;
    }    
   *********************************************************/
        if (!clickable) {
            checkForLongClick(0, x, y);
            break;
        }

        if (performButtonActionOnTouchDown(event)) {
            break;
        }
        if (isInScrollingContainer) {
            mPrivateFlags |= PFLAG_PREPRESSED;
            if (mPendingCheckForTap == null) {
                //CheckForTap也是Runnable的实现类,最终也会调用checkForLongClick()
                mPendingCheckForTap = new CheckForTap();
            }
            mPendingCheckForTap.x = event.getX();
            mPendingCheckForTap.y = event.getY();
            //通过延迟发送
            postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
        } else {
            // Not inside a scrolling container, so show the feedback right away
            setPressed(true, x, y);
            checkForLongClick(0, x, y);
        }
        break;

    case MotionEvent.ACTION_CANCEL:
       ***省略代码***
        break;

    case MotionEvent.ACTION_MOVE:
      ***省略代码***
        break;
    }

    return true;
}

return false;
}

由此我们可以知道View的监听事件的调用顺序为:setOnTouchListener-> onTouchEvent->setOnLongClickListener->setOnClickListener