Android事件分发机制流程详解(二)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/woshizisezise/article/details/79309310

前言:上一篇我们已经从事件分发执行流程入手,一起来了解并分析了事件分发的经过,大家应该从分析中能对事件分发的有个总体的认识,并且我相信应该也能自己分析出事件会如何执行,其实就那么点东西,弄明白了就不难了,但是今天我们还是要来看看activity,viewgroup,view的相关源码来学习一下他们的工作原理,那就开始吧!

首先来结合我们上一篇的工程情况来看看:
这里写图片描述

再次强调,1个点击事件发生后,事件先传到Activity、再传到ViewGroup、最终再传到 View,那么我们分别从Activity对点击事件的分发机制、ViewGroup对点击事件的分发机制、View对点击事件的分发机制来分析源码。

一、Activity对点击事件的分发机制

从我们上一天的分析可以看到,当一个新的点击事件发生时,首先是会进入到Activity的dispatchTouchEvent()方法内,那么我们先来看看Activity的dispatchTouchEvent()的源码:

public boolean dispatchTouchEvent(MotionEvent ev) {
    //由于每次点击事件第一个肯定是ACTION_DOWN,所以这里肯定是true
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        //如果getWindow().superDispatchTouchEvent(ev)是true的话
        //那么dispatchTouchEvent()返回的就是true,事件被消费,传递结束
        return true;
    }
    //否则就会执行onTouchEvent方法
    return onTouchEvent(ev);
}
  • activity的onUserInteraction方法:
/**
 * 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()
 */
public void onUserInteraction() {
}

这里返回的是一个空方法,当我们不知道这个方法是干啥用的时候,官方的注释就是最好的答案,也许你英语不好,但是我们有在线翻译不是么?这个方法主要是给用户提供告知和手机的一些交互的情况,要是用得到的话就实现它然后重写代码,这里我们用不到,不做了解。

  • activity的getWindow().superDispatchTouchEvent(ev)

通过鼠标点进去后发现,这是Window的抽象类

/**
 * Used by custom windows, such as Dialog, to pass the touch screen event
 * further down the view hierarchy. Application developers should
 * not need to implement or call this.
 *
 */
public abstract boolean superDispatchTouchEvent(MotionEvent event);

我们只能看看Window的实现类了
这里写图片描述
可以看到Window唯一的实现类只有PhoneWindow,那么我们进到PhoneWindow中查找superDispatchTouchEvent方法,代码如下:

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

接着点进mDecor中去看看,会发现进入了DecorView中:

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

继续往下找,发现进入了ViewGroup中:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ...具体的到ViewGroup中讲解
}

现在再来审视一下这张图
这里写图片描述
发现没有,事件通过getWindow().superDispatchTouchEvent(ev)往下一直传递到ViewGroup中,如果ViewGroup的dispatchTouchEvent返回了false,那么我们这里就会执行onTouchEvent方法,交由我们自己实现的onTouchEvent方法处理,如果ViewGroup的dispatchTouchEvent返回了true,那么我们这return true,事件就会往下执行,符合我们上一篇首图的逻辑。那么接下来我们顺着往下走来看看onTouchEvent方法。

  • activity的onTouchEvent()方法(系统默认的,如果未重写执行此流程)

来看看源码:

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

这段代码表示:onTouchEvent方法在触摸屏幕事件没有被任何视图处理时调用,即没有任何视图(ViewGroup、View)消费事件时,Activity的onTouchEvent方法会执行。如果你已经消费了这个事件,返回true,如果你没有,返回false,默认的实现总是返回false。即只有在点击事件在Window边界外才会返回true,一般情况都返回false。

下面我们来看看mWindow.shouldCloseOnTouch(this, event)方法。

  • Window的shouldCloseOnTouch()方法
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
   if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
            && isOutOfBounds(context, event) && peekDecorView() != null) {
        return true;
    }
    return false;
}

这个方法主要用来判断是否是边界外点击事件+是否是DOWN事件+event的坐标是否在边界内。如果是在边界外并且是DOWN事件,那么就意味着返回true,那么就是消费事件,如果不是则返回false,不消费事件,到这里Activity的事件分发已经执行完毕了。

因为我们在MainActivity中已经重写了onTouchEvent方法,所以上述过程当getWindow().superDispatchTouchEvent(ev)返回false的时候,事件就会交由我们MainActivity中的onTouchEvent方法处理,所以看看我们上一篇的测试3
这里写图片描述
并且运行结果也和我们上述分析一致:
这里写图片描述


二、ViewGroup对点击事件的分发机制

由于我的api较新,所以这里的代码是Android7.0的代码,并且由于ViewGroup中的dispatchTouchEvent方法较长,所以咱们这里只择出部分关键代码来分析如何运作的。

// Check for interception.
//定义一个变量是否拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {
    //如果是down事件并且传入对象不为空则进入
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept) {
        //如果不禁止拦截的话执行,是否拦截由自己的onInterceptTouchEvent方法决定
        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;
}
  • ViewGroup的onInterceptTouchEvent方法
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;
}

实现此方法拦截所有触摸屏运动事件,这允许我们观看事件,因为它们被分派给它的孩子,并在任何时候对当前的手势拥有所有权。使用这个函数需要一些注意,因为它是相当复杂的,您将在这里收到down事件,down事件将由这个viewgroup的子节点处理,或者由您自己的onTouchEvent()方法处理。这意味着您应该实现onTouchEvent()以返回true。同样,通过从onTouchEvent()返回true,您将不会在onInterceptTouchEvent()中接收任何后续事件,并且所有的触摸处理都必须在onTouchEvent()中发生,就像normal一样。只要您从这个函数返回false,每个后续事件(包括最终的up)将首先在这里传递,然后传递到目标的onTouchEvent()。
当前的目标将接收ACTION_CANCEL事件,并且不会在这里传递更多的消息。

盗取网上一篇博客的其中的一张图结合分析的结论来看看
这里写图片描述

到这里相信大家对ViewGroup如何分发事件有了自己的理解了,下面接着来看看View是处理事件的。


三、View对点击事件的分发机制

鉴于Android7.0的代码关于View这块比较复杂,咱们还是看看Android2.0的代码吧,虽然代码简单,但是工作流程肯定是一样的,只不过后期做了优化,咱们了解原理就好了。

public boolean dispatchTouchEvent(MotionEvent event) {
   if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
            mOnTouchListener.onTouch(this, event)) {
        return true;
    }
    return onTouchEvent(event);
 }
  • mOnTouchListener

关于onTouchListener的赋值在这个方法里:

public void setOnTouchListener(OnTouchListener l) {
   mOnTouchListener = l;
}

可以看到,只要我们给view设置了setOnTouchListener,那么mOnTouchListener一定是有值的。

  • (mViewFlags & ENABLED_MASK) == ENABLED

这个条件是判断当前点击的控件是否enable,因为我们既然要给这个view设置点击事件的话,那么我们肯定会给它setOnClickListener,所以这个条件应该是成立的。

  • mOnTouchListener.onTouch(this, event)

这个事件需要我们注册了onTouchListener后进行回调返回结果,举个例子,在咱们上一篇中咱们也进行了注册。

view.setOnTouchListener(new View.OnTouchListener() {
   @Override
    public boolean onTouch(View v, MotionEvent event) {
        System.out.println("执行了onTouch(), 动作是:" + event.getAction());
        return false;
    }
});

我们已经分析过:
return false;那么上述的if条件不成立,那么就会执行onTouchEvent方法,和我们上一篇验证的结果一样。

return true;上述条件成立,事件传递结束,直接被消费掉,和我们验证的结果也一样。

接下来我们来看看onTouchEvent方法。

  • View.onTouchEvent()方法
public boolean onTouchEvent(MotionEvent event) {
    final int viewFlags = mViewFlags;

    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        // A disabled view that is clickable still consumes the touch
        // events, it just doesn't respond to them.
        return (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
    }

    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        //如果view是可点击的,包括长按,那么就进入switch条件中
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                if ((mPrivateFlags & PRESSED) != 0) {
                    // take focus if we don't have it already and we should in
                    // touch mode.
                    boolean focusTaken = false;
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                        focusTaken = requestFocus();
                    }

                    if (!mHasPerformedLongPress) {
                        // This is a tap, so remove the longpress check
                       if (mPendingCheckForLongPress != null) {
                           removeCallbacks(mPendingCheckForLongPress);
                        }

                        // Only perform take click actions if we were in the pressed state
                        if (!focusTaken) {
                            //执行这个方法,见详解
                            performClick();
                        }
                    }

                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }

                    if (!post(mUnsetPressedState)) {
                        // If the post failed, unpress right now
                        mUnsetPressedState.run();
                    }
                }
                break;

            case MotionEvent.ACTION_DOWN:
                mPrivateFlags |= PRESSED;
                refreshDrawableState();
                if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
                    postCheckForLongClick();
                }
                break;

            case MotionEvent.ACTION_CANCEL:
                mPrivateFlags &= ~PRESSED;
                refreshDrawableState();
                break;

            case MotionEvent.ACTION_MOVE:
                final int x = (int) event.getX();
                final int y = (int) event.getY();

                // Be lenient about moving outside of buttons
                int slop = ViewConfiguration.get(mContext).getScaledTouchSlop();
                if ((x < 0 - slop) || (x >= getWidth() + slop) ||
                        (y < 0 - slop) || (y >= getHeight() + slop)) {
                    // Outside button
                    if ((mPrivateFlags & PRESSED) != 0) {
                        // Remove any future long press checks
                        if (mPendingCheckForLongPress != null) {
                            removeCallbacks(mPendingCheckForLongPress);
                        }

                        // Need to switch from pressed to not pressed
                        mPrivateFlags &= ~PRESSED;
                        refreshDrawableState();
                    }
                } else {
                    // Inside button
                    if ((mPrivateFlags & PRESSED) == 0) {
                        // Need to switch from not pressed to pressed
                        mPrivateFlags |= PRESSED;
                        refreshDrawableState();
                    }
                }
                break;
       }
       //如果该控件可点击的话,那么一定是return true;
        return true;
    }
    //如果该控件不可点击,一定return false;
    return false;
}
  • 详解performClick()
public boolean performClick() {
   sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

    if (mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        mOnClickListener.onClick(this);
        return true;
    }

    return false;
}

如果我们给view.setOnClickListener(new View.OnClickListener() {});那么这里mOnClickListener一定不为null,所以return true;如果没设置click监听,那么就return false;然后事件根据返回结果进行传递或者消费。


总结:

通过上一篇各种情况下执行流程和本篇源码分析,我们应该能够清楚的了解了Android事件分发到底是个什么东西了,最好的学习方法还是写个demo,然后有不明白的地方点进去看看源码,不要求全部看懂,找找我们关心的重点,能大概分析清楚是怎么工作的即可,祝大家学习愉快~

猜你喜欢

转载自blog.csdn.net/woshizisezise/article/details/79309310
今日推荐