触摸事件的处理流程

前言

用户通过触摸屏幕与手机交互,所以触摸事件成为了交互中相对重要的因素。比如我们想要点外卖,就要通过手指触摸屏幕打开外卖软件,选择喜爱的菜品下单等待外卖小哥送餐,然而在等待过程中,我们还可以查看当前外卖的状态,这些行为我们都需要触摸屏幕来完成。那么问题来了,应用软件是如何知道手指触摸的是哪一块区域呢?

接下来让我们带着问题来分析触摸事件是如何处理的。

触摸事件

当触摸事件产生时,最先传递给Activity,那么我们就从Activity开始剖析。

1.Activity分发事件

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback, WindowControllerCallback,
        AutofillManager.AutofillClient {

    /**
     * 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)) { // 1
            return true;
        }
        return onTouchEvent(ev); // 2
    }
}

从注释1处可以看到getWindow().superDispatchTouchEvent(ev)如果返回true,dispatchTouchEvent(MotionEvent ev)就直接返回true,代表该事件已经被处理,Activity不处理该事件,否则会调用注释2处的onTouchEvent(ev)方法处理事件,继承自Activity的子类可以重写onTouchEvent(ev)方法处理事件。

下面我们来看一下getWindow():

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback, WindowControllerCallback,
        AutofillManager.AutofillClient {

    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        .......
    }

    /**
     * Retrieve the current {@link android.view.Window} for the activity.
     * This can be used to directly access parts of the Window API that
     * are not available through Activity/Screen.
     *
     * @return Window The current window, or null if the activity is not
     *         visual.
     */
    public Window getWindow() {
        return mWindow;
    }
}

从以上代码可以看出getWindow()返回的是mWindow,mWindow在attach()方法中通过new PhoneWindow(this, window, activityConfigCallback)创建的,Window是抽象类,PhoneWindow是Window的实现类,所以getWindow().superDispatchTouchEvent(ev)实则是调用的PhoneWindow的superDispatchTouchEvent(ev)方法。

2.PhoneWindow分发事件

public class PhoneWindow extends Window implements MenuBuilder.Callback {

    // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;

    public PhoneWindow(Context context, Window preservedWindow,
            ActivityConfigCallback activityConfigCallback) {
        this(context);
        // Only main activity windows use decor context, all the other windows depend on whatever
        // context that was given to them.
        mUseDecorContext = true;
        if (preservedWindow != null) {
            mDecor = (DecorView) preservedWindow.getDecorView(); // 1
            mElevation = preservedWindow.getElevation();
            mLoadElevation = false;
            mForceDecorInstall = true;
            // If we're preserving window, carry over the app token from the preserved
            // window, as we'll be skipping the addView in handleResumeActivity(), and
            // the token will not be updated as for a new window.
            getAttributes().token = preservedWindow.getAttributes().token;
        }
        // Even though the device doesn't support picture-in-picture mode,
        // an user can force using it through developer options.
        boolean forceResizable = Settings.Global.getInt(context.getContentResolver(),
                DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0;
        mSupportsPictureInPicture = forceResizable || context.getPackageManager().hasSystemFeature(
                PackageManager.FEATURE_PICTURE_IN_PICTURE);
        mActivityConfigCallback = activityConfigCallback;
    }

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

从superDispatchTouchEvent(MotionEvent event)中可以看到,调用了mDecor.superDispatchTouchEvent(event),mDecor是在PhoneWindow的构造函数中通过preservedWindow.getDecorView()初始化的,详见注释1。

3.DecorView分发事件

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {

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

从superDispatchTouchEvent(MotionEvent event)中可以看到,调用了super.dispatchTouchEvent(event),DecorView继承自FrameLayout,FrameLayout又继承自ViewGroup,所以super.dispatchTouchEvent(event)实则是调用了ViewGroup的dispatchTouchEvent(event)方法。

4.ViewGroup分发事件

public abstract class ViewGroup extends View implements ViewParent, ViewManager {

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        // If the event targets the accessibility focused view and this is it, start
        // normal event dispatch. Maybe a descendant is what will handle the click.
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) { // 1
                /**
                 * 当手指按下时重置所有状态,为之后的除了down之外的一系列事件做准备
                 */
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            /**
             * 当触摸事件为down或者触摸事件不为down且mFirstTouchTarget不为空的情况下,
             * 去检查是否需要拦截事件。
             */
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) { // 2
                /**
                 * 此处判断当前父控件是否不干预拦截事件,默认为false,
                 * 会调用onInterceptTouchEvent(ev)判断是否拦截事件。
                 * 一旦disallowIntercept等于true,就会直接走else,不拦截事件,
                 * 也就不会调用onInterceptTouchEvent(ev)判断是否拦截事件。
                 *
                 * 子类可以通过requestDisallowInterceptTouchEvent()方法设置mGroupFlags。
                 *
                 * onInterceptTouchEvent(ev)默认返回false不拦截事件,
                 * 继承自ViewGroup的子类可以重写该方法决定是否拦截事件,
                 * 如果子控件调用了父控件的requestDisallowInterceptTouchEvent()方法,
                 * 父控件就不会调用onInterceptTouchEvent(ev)方法了,
                 * 此时的onInterceptTouchEvent(ev)是无效的。
                 *
                 * 如果拦截事件,intercepted会置为true,
                 * 否则不拦截事件,intercepted会置为false。
                 */
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    inte
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值