Android中对于点击事件的深度梳理(三)

现在我们的ACTION_DOWN事件终于来到了button,我们来看button是怎样处理的。

button的dispatchTouchEvent

源码中呢,Button类和TextView类没有重写dispatchTouchEvent方法,但是都集成自View,直接看View的dispatchTouchEvent方法:

java 复制代码
//View.java
//返回true或者false
public boolean dispatchTouchEvent(MotionEvent event) {
    // If the event should be handled by accessibility focus first.
    if (event.isTargetAccessibilityFocus()) {//无障碍服务 略过
        // We don't have focus or no virtual descendant has it, do not handle the event.
        if (!isAccessibilityFocusedViewOrHost()) {
            return false;
        }
        // We have focus and got the event, then use normal event dispatch.
        event.setTargetAccessibilityFocus(false);
    }
    boolean result = false;//默认为false

    if (mInputEventConsistencyVerifier != null) {//启动非用户机器,略过
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {//执行
        // Defensive cleanup for new gesture
        stopNestedScroll();
    }

    if (onFilterTouchEventForSecurity(event)) {
        //enable的可滚动组件
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {//不进入
            result = true;
        }
        //noinspection SimplifiableIfStatement
//如果设置了onTouchListener且onTouch返回了true
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        //如果前面都返回的false,那么查看onTouchEvent
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }

    // Clean up after nested scrolls if this is the end of a gesture;
    // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
    // of the gesture.
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}

上面的代码中,主要是做了两件事情

  • 1.检查动作的环境,是否无障碍模式,是否落在了可滚动的view上等,

我们的例子是button。所以不考虑

  • 2.先执行View的onTouchListener,再执行onTouchEvent,流程如下

是否设置了onTouchListener

├─ false ===> View.OnTouchEvent

└─true ===> OnTouchListerner.onTouch

├─ 返回true 直接return

└─ 返回false ====> View.OnTouchEvent

这里呢,假设button没有设置onTouchListener,那么直接进入onTouchEvent。

OnTouchEvent:

button并没有重写OnTouchEvent,但是TextView重写了这个方法

java 复制代码
//TextView.java

@Override
    public boolean onTouchEvent(MotionEvent event) {
        if (DEBUG_CURSOR) {
            logCursor("onTouchEvent", "%d: %s (%f,%f)",
                    event.getSequenceNumber(),
                    MotionEvent.actionToString(event.getActionMasked()),
                    event.getX(), event.getY());
        }
        if (!isFromPrimePointer(event, false)) {
            return true;
        }

        final int action = event.getActionMasked();
        if (mEditor != null) {//可编辑,对应Edittext等情况
            mEditor.onTouchEvent(event);

            if (mEditor.mInsertionPointCursorController != null
                    && mEditor.mInsertionPointCursorController.isCursorBeingModified()) {
                return true;
            }
            if (mEditor.mSelectionModifierCursorController != null
                    && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
                return true;
            }
        }

        final boolean superResult = super.onTouchEvent(event);//执行View的onTouchEvent方法
        if (DEBUG_CURSOR) {
            logCursor("onTouchEvent", "superResult=%s", superResult);
        }

        /*
         * Don't handle the release after a long press, because it will move the selection away from
         * whatever the menu action was trying to affect. If the long press should have triggered an
         * insertion action mode, we can now actually show it.
         */
        if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
            mEditor.mDiscardNextActionUp = false;
            if (DEBUG_CURSOR) {
                logCursor("onTouchEvent", "release after long press detected");
            }
            if (mEditor.mIsInsertionActionModeStartPending) {
                mEditor.startInsertionActionMode();
                mEditor.mIsInsertionActionModeStartPending = false;
            }
            return superResult;
        }

        final boolean touchIsFinished = (action == MotionEvent.ACTION_UP)
                && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();

        if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
                && mText instanceof Spannable && mLayout != null) {//对应文本为Spannable的情况
            boolean handled = false;

            if (mMovement != null) {
                handled |= mMovement.onTouchEvent(this, mSpannable, event);
            }

            final boolean textIsSelectable = isTextSelectable();
            if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
                // The LinkMovementMethod which should handle taps on links has not been installed
                // on non editable text that support text selection.
                // We reproduce its behavior here to open links for these.
                ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(),
                    getSelectionEnd(), ClickableSpan.class);

                if (links.length > 0) {
                    links[0].onClick(this);
                    handled = true;
                }
            }

            if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
                // Show the IME, except when selecting in read-only text.
                final InputMethodManager imm = getInputMethodManager();
                viewClicked(imm);
                if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {
                    imm.showSoftInput(this, 0);
                }

                // The above condition ensures that the mEditor is not null
                mEditor.onTouchUpEvent(event);

                handled = true;
            }

            if (handled) {
                return true;
            }
        }

        return superResult;
    }

可以看到,虽然TextView重写了这个方法,但是只有可编辑模式下,比如EditText (没错,EditText也是TextView的子类😂)才会单独处理。

其余情况下依然是直接执行父类(也就是View)的OnTouchEvent.

View的OnTouchEvent

java 复制代码
//View.java
public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();

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

        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    .....
                    .....//篇幅原因,暂时省略
                    break;

                case MotionEvent.ACTION_DOWN:
                    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                    }
                    mHasPerformedLongPress = false;

                    if (!clickable) {
                        checkForLongClick(
                                ViewConfiguration.getLongPressTimeout(),
                                x,
                                y,
                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                        break;
                    }

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            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(
                                ViewConfiguration.getLongPressTimeout(),
                                x,
                                y,
                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    if (clickable) {
                        setPressed(false);
                    }
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    break;

                case MotionEvent.ACTION_MOVE:
                    //.....
                    //....

                    break;
            }

            return true;
        }

        return false;
    }

可以看出在switch---case这段代码中,始终返回true,而进入这段代码的一个重要条件就是clickable。

当前例子中,View是button,看下这个clickable的设置。

复制代码
在view的初始话方法中,有这样一段
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
......
case com.android.internal.R.styleable.View_clickable:
    if (a.getBoolean(attr, false)) {
        viewFlagValues |= CLICKABLE;
        viewFlagMasks |= CLICKABLE;
    }
    break;
.......
}

可以看出,如果在style布局中,clickable为true的话,就是可以的。

打开platforms/android-31/data/res/values/styles.xml,可以看到系统设置的属性

复制代码
<style name="Widget.Button">
    <item name="background">@drawable/btn_default</item>
    <item name="focusable">true</item>
    <item name="clickable">true</item>
    <item name="textAppearance">?attr/textAppearanceSmallInverse</item>
    <item name="textColor">@color/primary_text_light</item>
    <item name="gravity">center_vertical|center_horizontal</item>
</style>

由此可知,clickable为true, 也就是直接进入了switch case代码段中的down事件

复制代码
case MotionEvent.ACTION_DOWN:
    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
    }
    mHasPerformedLongPress = false;

    if (!clickable) {
        checkForLongClick(
                ViewConfiguration.getLongPressTimeout(),
                x,
                y,
                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
        break;
    }

    if (performButtonActionOnTouchDown(event)) {
        break;
    }

    // Walk up the hierarchy to determine if we're inside a scrolling container.
    boolean isInScrollingContainer = isInScrollingContainer();

    // For views inside a scrolling container, delay the pressed feedback for
    // a short period in case this is a scroll.
    if (isInScrollingContainer) {
        mPrivateFlags |= PFLAG_PREPRESSED;
        if (mPendingCheckForTap == null) {
            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(
                ViewConfiguration.getLongPressTimeout(),
                x,
                y,
                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
    }
    break;

这个方法主要是针对鼠标事件、滚动容器和长按时间进行了预处理。当前实例中的button有关系的就是讲pressed状态设置为true.

不过重要的是:这里的onTouchEvent是返回了true.

onTouchEvent方法终于执行完毕,回到本文最开始的代码片段

回归父类的dispatchTouchEvent:

java 复制代码
//View.java
//返回true或者false
public boolean dispatchTouchEvent(MotionEvent event) {
        ......
        //如果前面都返回的false,那么查看onTouchEvent
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

   ......
    return result;
}

可知这个方法,在执行完button的down时间后返回为true.

接合上篇文章流程图,这个dispatchTouchEvent的执行时机是在上层LinearLayout的dispatchTouchEvent中调用的,返回去看看拿到为true的结果后会怎样。

java 复制代码
//ViewGroup.java

public boolean dispatchTouchEvent(MotionEvent ev) {
        

        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) {
                // 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();
            }

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

            .....
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            if (!canceled && !intercepted) {
                .....

                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x =
                                isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
                        final float y =
                                isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);
                            if (!child.canReceivePointerEvents()
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                continue;
                            }

                            newTouchTarget = getTouchTarget(child);
                            ....
                            .....
/*******前面的内容,上篇文章讲了,这篇重点从这里开始看********/                            
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {//此处为true,执行此if
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    // childIndex points into presorted list, find original index
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);//重要(配合mFirsrTaeget)
                                alreadyDispatchedToNewTouchTarget = true;
                                break;//跳出循环
                            }

                            ......
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }

                    if (newTouchTarget == null && mFirstTouchTarget != null) {//不成立,跳过
                        // Did not find a child to receive the event.
                        // Assign the pointer to the least recently added target.
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }

            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {//执行此if
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;//改为mFirstTouchTarget
                    target = next;//null 跳出循环
                }
            }

            ......
        return handled;
    }

因为上篇内容讲过了一部分,这一篇就从dispatchTransformedTouchEvent这个地方开始看(代码中有标注)

dispatchTransformedTouchEvent方法的返回值就是我们之前解析的button的dispatchtouchevent的返回值,也就是true.

复制代码
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
....
newTouchTarget = addTouchTarget(child, idBitsToAssign);//配置mFirstTouchTarget
alreadyDispatchedToNewTouchTarget = true;//标记点击事件已分发
break;
}

这个语句得以执行,主要做了三件事

1.给mFirstTouchTarget赋值 2.做分发标记 3.跳出循环

复制代码
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
    final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    mFirstTouchTarget = target;
    return target;
};

mFirstTouchTarget也由原来的null 变为 TouchTarget实例,其中 属性child : button,属性next : null

跳出循环后代码,进入了while循环

复制代码
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
            final TouchTarget next = target.next;
            if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {//执行
                handled = true;
            } else {
                .....
            }
            predecessor = target;//置为mFirstTouchTarget
            target = next;//置为null,跳出循环
        }
    }
.....
   }
.....
return handled;

最终,button所在的父布局(LinearLayout)的dispatchTouchEvent执行完毕,返回true.

反馈到内容容器FrameLayout,再次执行上述步骤后反馈到DecoreView, DecoreView同理反映到

Activity中

java 复制代码
//Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {//这里为true
            return true;
        }
        return onTouchEvent(ev);

返回true. Activity的onTouchEvent不予执行。

是不是感觉执行了一个寂寞,onclicklistener没有作用吗,不着急,后面这些自会浮出水面。

至此,ACTION_DOWN时间的分发执行完毕,下一篇分析ActionMove,就会简单一些了。

整理的ACTION_DOWN的流程图如下

相关推荐
电饭叔2 小时前
DataFrame和 Series 索引
android·python
lexiangqicheng2 小时前
【全网最全】React Native 安卓原生工程结构与构建机制深度解析
android·react native·react.js
数据蜂巢2 小时前
MySQL 8.0 生产环境备份脚本 (Percona XtraBackup 8.0+)
android·mysql·adb
jingling5553 小时前
uniapp | 基于高德地图实现位置选择功能(安卓端)
android·前端·javascript·uni-app
fatiaozhang95273 小时前
晶晨S905L/S905LB-通刷-slimbox 9.19-Mod ATV-安卓9-线刷固件包
android·电视盒子·刷机固件·机顶盒刷机
爱怪笑的小杰杰3 小时前
UniApp 桌面应用实现 Android 开机自启动(无原生插件版)
android·java·uni-app
符哥20083 小时前
Fresco2.X 框架完整使用详解(Android Kotlin)
android
TheNextByte13 小时前
如何在Android上恢复已删除的联系人
android
my_power5203 小时前
安卓面试题总结
android