事件分发机制整理

2019-10-22  本文已影响0人  _浮生若梦

事件分发机制

1、事件分发、拦截与消费

事件分发用到三个相关的方法,dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent。
对于java层,事件是从Activity的dispatchTouchEvent开始的,再往上属于C和C++,这里不做记录。
以下表格只是展示一下这三个方法所在的位置,方便后面讲解查看。

类型 相关方法 Activity ViewGroup View
事件分发 dispatchTouchEvent
事件拦截 onInterceptTouchEvent × ×
事件消费 onTouchEvent ×

2、事件处理

事件处理相对比较简单,所以先说一下事件处理逻辑
在activity中为一个view设置点击事件和触摸事件

view.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.e(TAG,"onClick");
        }
    });
    
view.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            Log.e(TAG,"onTouch"+event.getAction());
            return false;
        }
    });

当onTuoch返回false时,onClick方法会执行,返回为true时,onClick不执行。
这里只分析事件处理,所以回到View中的dispatchTouchEvent(),以下只贴出关键代码:

public boolean dispatchTouchEvent(MotionEvent event) {
    ...
    boolean result = false;
    ...
    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //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;
}

代码中可以看到共有四个判断条件:

li=mListenerInfo,在activity中view调用了setOnTouchEvent方法,代码如下

public void setOnTouchListener(OnTouchListener l) {
    getListenerInfo().mOnTouchListener = l;
}
ListenerInfo getListenerInfo() {
    if (mListenerInfo != null) {
        return mListenerInfo;
    }
    mListenerInfo = new ListenerInfo();
    return mListenerInfo;
}

由上代码可以看出mListenerInfo是个单例,不为null,所以li!=null成立;
li.mOnTouchListener就是在activity中调用setTouchListener()设置的OnTouchListener对象,所以li.mOnTouchListener!=null成立;
(mViewFlags & ENABLED_MASK) == ENABLED,view可以按,所以也成立;
前三个判断条件都为true,所以接下来执行mOnTouchListener中的onTouch()方法,所以onTouch决定了result的返回值

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

所以,当result为false(result默认为false),即onTouch返回值为false时,执行onTouchEvent()方法,由最开始测试结果:当onTuoch返回false时,onClick方法会执行,返回为true时,onClick不执行,猜测onClick可能会在onTouchEvent()中执行,接下来看一下onTouchEvent()方法做了些什么,按照惯例还是贴出关键代码

public boolean onTouchEvent(MotionEvent event) {
    ...
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                ...
                    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)) {
                            //1
                                performClickInternal();
                            }
                        }
                    }
                ...
                mIgnoreNextUpEvent = false;
                break;
                ...
        }

        return true;
    }

    return false;
}

private boolean performClickInternal() {
    ...
    //2
    return performClick();
}

public boolean performClick() {
    // We still need to call this method to handle the cases where performClick() was called
    // externally, instead of through performClickInternal()
    notifyAutofillManagerOnClick();

    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        //3
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false;
    }

    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

    notifyEnterOrExitForAutoFillIfNeeded(true);

    return result;
}

上面注释中,

到这里事件处理执行完毕,应该明白了为什么当onTuoch返回false时,onClick方法会执行,返回为true时,onClick不执行。

总结:如果view同时设置了OnTouchListener和OnClickListener,当onTouch返回为false时,事件的执行顺序是:OnTouchListener.onTouch ---> View.onTouchEvent ---> View.performClickInternal() ---> View.performClick() --- > OnClickListener.onClick();

2、事件分发

事件分发总流程
  1. Activity#dispatchTouchEvent()
  2. PhoneWindow#superDispatchTouchEvent()
  3. DecorView#superDispatchTouchEvent()
  4. ViewGroup#dispatchTouchEvent()
  5. View#dispatchTouchEvent()
  6. View#onTouchEvent()

在Java层面,事件分发从Activity的dispatchTouchEvent开始,再之前数据C、C++的范畴,不做深究。
首先看下Activity中的dispatchTouchEvent做了些什么:

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

public Window getWindow() {
    return mWindow;
}

Window是一个抽象类,从Window中的注释可以看到Window只有一个实现类为PhoneWindow,

/ * <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/

从Activity的attach方法也能看出,mWindow就是PhoneDow的实例:

final void attach(Context context, ActivityThread aThread,
    ...
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    ...
}

所以,Activity的dispatchTouchEvent其实是调用得PhoneWindow的superDispatchTouchEvent:

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

PhoneWindow的构造函数中可以知道mDecor为DecorView的实例:

public PhoneWindow(Context context, Window preservedWindow,
        ActivityConfigCallback activityConfigCallback) {
    ...
    if (preservedWindow != null) {
        mDecor = (DecorView) preservedWindow.getDecorView();
        ...
}

所以,调用的是DecorView的superDispatchTouchEvent方法:

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

DecorView是ViewGroup的子类,所以继续调用ViewGroup的dispatchTouchEvent,这里完成了从Activity到ViewGroup的事件分发,ViewGroup的dispatchTouchEvent方法比较复杂,分段来看:

public boolean dispatchTouchEvent(MotionEvent ev) {
    ...
        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;
        }

        ...
}

private void resetTouchState() {
    clearTouchTargets();
    resetCancelNextUpFlag(this);
    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    mNestedScrollAxes = SCROLL_AXIS_NONE;
}

private void clearTouchTargets() {
    TouchTarget target = mFirstTouchTarget;
    if (target != null) {
        do {
            TouchTarget next = target.next;
            target.recycle();
            target = next;
        } while (target != null);
        mFirstTouchTarget = null;
    }
}

接下来看后续代码:

public boolean dispatchTouchEvent(MotionEvent ev) {
    ...
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x = ev.getX(actionIndex);
                    final float y = 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 there is a view that has accessibility focus we want it
                        // to get the event first and if not handled we will perform a
                        // normal dispatch. We may do a double iteration but this is
                        // safer given the timeframe.
                        if (childWithAccessibilityFocus != null) {
                            if (childWithAccessibilityFocus != child) {
                                continue;
                            }
                            childWithAccessibilityFocus = null;
                            i = childrenCount - 1;
                        }

                        if (!child.canReceivePointerEvents()
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }

                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            // Child is already receiving touch within its bounds.
                            // Give it the new pointer in addition to the ones it is handling.
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }

                        resetCancelNextUpFlag(child);
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // 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);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }

                        // The accessibility focus didn't handle the event, so clear
                        // the flag and do a normal dispatch to all children.
                        ev.setTargetAccessibilityFocus(false);
                    }
                    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;
                }
            }
        }
        ...
}

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    ...
    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        ...
        handled = child.dispatchTouchEvent(transformedEvent);
    }

}

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

可见child为null,调用super.dispatchTouchEvent,ViewGroup继承View,所以调用View的dispatchTouchEvent,完成了ViewGroup到View的事件分发。
handled即为View的dispatchTouchEvent的返回结果。

返回Activity中的dispatchTouchEvent,如果handled为false,继续执行Activity中的onTouchEvent方法。

上一篇下一篇

猜你喜欢

热点阅读