View事件分发一 (源码解析)

2019-06-27  本文已影响0人  卢小白啊

注: 分析的是Android28的源码

一 Activity分发到顶层View

Activity#dispatchTouchEvent


public boolean dispatchTouchEvent(MotionEvent ev) {

    if (ev.getAction() == 0) {

        this.onUserInteraction();

    }

    //事件交给Activity附属的window来处理,

    //如果返回true,事件就结束了

    //如果返回false,表示没人处理,那么Activity自己来处理,即this.onTouchEvent

    return this.getWindow().superDispatchTouchEvent(ev) ? true : this.onTouchEvent(ev);

}

window是抽象类,superDispatchTouchEvent是抽象方法

Window#superDispatchTouchEvent


public abstract boolean superDispatchTouchEvent(MotionEvent var1);

我们找到window的唯一实现类PhoneWindow

PhoneWindow#superDispatchTouchEvent

public boolean superDispatchTouchEvent(MotionEvent event) {

    return this.mDecor.superDispatchTouchEvent(event);

}

mDecor是DecorView,

DecorView#superDispatchTouchEvent


public boolean superDispatchTouchEvent(MotionEvent event) {

    return super.dispatchTouchEvent(event);

}

DecorView是setContentView()中设置的布局的父布局。DecorView是FrameLayout的子类,但是FrameLayout并没有实现dispatchTouchEvent,于是实际调用的是ViewGroup的dispatchTouchEvent。

至此,事件已经从Activity传到window,再传到了DecorView

二 顶层View向下分发

ViewGroup#dispatchTouchEvent核心代码


@Override

public boolean dispatchTouchEvent(MotionEvent ev) {

    //2. 接收down事件时清空mFirstTouchTarget 
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        cancelAndClearTouchTargets(ev);
        resetTouchState();
    }

    //1. ViewGroup是否拦击
    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;
    }

    //3. ViewGroup不拦截,向下分发

    if (!canceled && !intercepted) {
        for (int i = childrenCount - 1; i >= 0; i--) {
            //3.1 子控件是否能接收点击事件
            if (!canViewReceivePointerEvents(child)
                    || !isTransformedTouchPointInView(x, y, child, null)) {
                ev.setTargetAccessibilityFocus(false);
                continue;
            }

            //3.2 调用子View的dispatchTouchEvent()
            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();

                //3.3 若子控件的dispatchTouchEvent()返回true
                //给mFirstTouchTarget 赋值
                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                alreadyDispatchedToNewTouchTarget = true;
                break;
            }
        }

    }


    //4. 若mFirstTouchTarget == null(无子控件处理事件),则自己来处理
    if (mFirstTouchTarget == null) {
        // No touch targets so treat this as an ordinary view.
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS);
    } else {
        ...
    }
}

2.1 ViewGroup是否拦截

2.1.1 ViewGroup有两种情况判断是否拦截当前事件

actionMasked == MotionEvent.ACTION_DOWN
或者
mFirstTouchTarget != null

2.1.2 从后面代码会了解到,当ViewGroup不拦截事件,并传递给子元素处理事件后,mFirstTouchTarget!=null,也就是说,一旦ViewGroup拦截了事件,那么ACTION_DOWN后续的事件(MOVE UP)都不会在判断是否需要拦截,也就不会执行onInterceptTouchEvent()

2.1.3 FLAG_DISALLOW_INTERCEPT标志位

这个标志位是在ViewGroup的子View中通过

//intercept: 若不希望父控件拦截事件,传入true
getParent().requestDisallowInterceptTouchEvent(boolean intercept);

设置的。但是这个设置并不能拦截父控件的down事件,因为ViewGroup接收到Down会重置这个状态,使标志位设置失效,具体请看2.2

2.2 接收Down事件重置状态

在接收到down事件时,会把mFirstTouchTarget 设置为null,并清空FLAG_DISALLOW_INTERCEPT标志位

2.3 ViewGroup不拦截事件,会遍历所有子控件,将事件分发给子控件

2.3.1 子控件是否能接收点击事件,若不能,直接continue。

下面的两个条件要同时满足

//一: 1. 可见 或者 2. 有动画
private static boolean canViewReceivePointerEvents(@NonNull View child) {
    return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
            || child.getAnimation() != null;
}

//二: 点击的坐标落在控件的区域内
protected boolean isTransformedTouchPointInView(float x, float y, View child,PointF outLocalPoint) {
    final float[] point = getTempPoint();
    point[0] = x;
    point[1] = y;
    transformPointToViewLocal(point, child);
    final boolean isInView = child.pointInView(point[0], point[1]);
    if (isInView && outLocalPoint != null) {
        outLocalPoint.set(point[0], point[1]);
    }
    return isInView;
}

2.3.2 调用子控件的dispatchTouchEvent

ViewGroup#dispatchTransformedTouchEvent

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {

        boolean handled;
        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            ...
            handled = child.dispatchTouchEvent(transformedEvent);
        }
        // Done.
        transformedEvent.recycle();
        return handled;       
}

3.3 子控件的dispatchTouchEvent返回true,那么会给mFirstTouchTarget赋值并跳出ViewGroup对子控件的for循环遍历 。

ViewGroup#addTouchTarget()

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

3.4 若无子控件处理事件,则自己来处理

注意 传入的第三个参数child是null

ViewGroup#dispatchTransformedTouchEvent

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

super.dispatchTouchEvent()即交给View来处理

三 View对事件的处理

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;
        //1. onTouchLisenter
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        //2. onTouchEvent() //3. onTouchEvent分析
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    ...
    return result;
}

3.1. 最先调用的是OnTouchListener的onTouch()

3.2. 是否执行onTouchEvent

若result为true, if(!result && onTouchEvent(event))的!result就是false了,由于&&短路原则,就不会执行onTouchEvent(event)了。

3.3. onTouchEvent分析

View#onTouchEvent()

public boolean onTouchEvent(MotionEvent event) {
    //1. 状态不可用的View也会消费事件,只是不响应事件
    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 (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                ...
                        // 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)) {
                                //2. 执行点击事件
                                performClickInternal();
                            }
                        }
                    }
                ...   
                break;
        }

        //3. 只要是clickable 就会return true
        return true;
}

3.3.1 状态不可用的View也会消费事件,只是不响应事件

3.3.2 执行点击事件

private boolean performClickInternal() {
    notifyAutofillManagerOnClick();
    return performClick();
}

public boolean performClick() {
    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        //5. onTouchEvent最终会调用onClick
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false;
    }
  ...
    return result;
}

3.3.3 onTouchEvent消费事件

View的onTouchEvent默认都会消费事件(返回true),除非他是不可点击的(clickable和longclickable都是false).

longClickable默认是false;

clickable不同控件的情况不一样,如Button的clickable就是true。


水平有限,有错误欢迎指正!!

上一篇 下一篇

猜你喜欢

热点阅读