从伪代码理解View事件分发过程

714 阅读3分钟

这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战

事件从起源

从手指从屏幕按下的瞬间,触摸事件经过一系列处理会来到ActivitydispatchTouchEvent中。

Activity.java

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    //getWindow().superDispatchTouchEvent(ev) 返回true代表消费了事件
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    //否则调用Activity的onTouchEvent
    return onTouchEvent(ev);
}

getWindow()实际返回的是PhoneWindow

PhoneWindow.java

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

getWindow().superDispatchTouchEvent(ev)实际会调用到mDecor.superDispatchTouchEvent(event)

DecorView.java

public class DecorView extends FrameLayout 
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
}

跟踪DecorView会发现DecorView继承自FrameLayout,因为FrameLayout没有重写dispatchTouchEvent方法,所以事件从Activity一路下来,最终事件的入口是ViewGroupdispatchTouchEvent

开发中,事件一般通过层层ViewGroup传递到View中,进行消费。一般View做真正的事件消费。

View的事件分发

View.java--伪代码

/**
 * view接收事件的入口,事件由ViewGroup分发过来
 */
public boolean dispatchTouchEvent(MotionEvent event) {
    boolean result = false;

    //如果设置了OnTouchListener,并且mOnTouchListener.onTouch返回了True,
    //设置Result为True,那么代表事件到这已经消费完成了。
    if (mOnTouchListener != null && mOnTouchListener.onTouch(this, event)) {
        result = true;
    }

    //没有设置OnTouchListener,或者mOnTouchListener.onTouch返回了false时,result为false
    //此时会回调View.onTouchEvent方法
    if(!result&& onTouchEvent(event)){
        result = true;
    }
    return result;
}

public boolean onTouchEvent(MotionEvent event) {
    //如果设置了onClickListener,那么返回True代表事件到这已经消费完成了。
    if (onClickListener != null) {
        onClickListener.onClick(this);
        return true;
    }
    return false;
}

dispatchTouchEvent是传入事件的入口,如果设置了mOnTouchListener,并且返回了true,那么dispatchTouchEvent就会返回true,代表事件被当前View消费了。如果没有设置,那么就会回调onTouchEvent方法,如果设置了onClickListener,那么onTouchEvent返回true,同理dispatchTouchEvent就会返回true,代表事件被当前View消费了.

从上面可以看出OnTouchListener先于onTouchEvent执行,onTouchEvent先于onClickListener执行。

ViewGroup的事件分发

ViewGroup.java--伪代码

    /**
     * onInterceptTouchEvent 拦截事件
     * @return  true 代表拦截当前事件,那么事件就不会分发给ViewGroup的child View ,会调用自身的 super.dispatchTouchEvent(event)
     *          false 代表不拦截当前事件,不拦截事件,那么在dispatchTouchEvent会遍历child View,寻找能消费事件的child View
     */
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
    }

    /**
     * @param event 事件
     * @param child 如果child 不为null,那么事件分发给它,否则,调用调用自身的 super.dispatchTouchEvent(event)
     * @return 是否消费了该事件
     */
    private boolean dispatchTransformedTouchEvent(MotionEvent event, View child) {
        boolean handled = false;
        if (child != null) {
            handled = child.dispatchTouchEvent(event);
        } else {
            handled = super.dispatchTouchEvent(event);
        }

        return handled;
    }


    public boolean dispatchTouchEvent(MotionEvent event) {
        boolean handled = false;
        //是否拦截当前事件
        boolean intercepted = onInterceptTouchEvent(event);
        //触碰的对象
        TouchTarget newTouchTarget = null;
        int actionMasked = event.getActionMasked();

        if (actionMasked != MotionEvent.ACTION_CANCEL && !intercepted) {
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                //ViewGroup child View 数组
                final View[] children = mChildren;
                //倒序遍历,最后的通常是需要处理事件的
                for (int i = children.length - 1; i >= 0; i--) {
                    View child = mChildren[i];
                    //isContainer 方法判断事件是否落在View中
                    if (!child.isContainer(event.getX(), event.getY())) {
                        continue;
                    }
                    //找到可以接收事件的View,把事件分发给他,
                    //如果dispatchTransformedTouchEvent返回了True代表消费了事件
                    if (dispatchTransformedTouchEvent(event, child)) {
                        handled = true;
                        //通过child包装成TouchTarget对象
                        newTouchTarget = addTouchTarget(child);
                        break;
                    }

                }
            }
        }
        //如果TouchTarget为null,那么事件就发就自己处理
        //mFirstTouchTarget == null 在onInterceptTouchEvent返回true时,或没有找到可以消费的child View时成立
        if (mFirstTouchTarget == null) {
            handled = dispatchTransformedTouchEvent(event, null);
        }
        return handled;
    }

dispatchTouchEvent是事件接收的入口,如果拦截事件(onInterceptTouchEvent方法返回true),那么就会走到dispatchTransformedTouchEvent方法,因为mFirstTouchTargetnull,所以最终调用super.dispatchTouchEvent(event),我们知道ViewGroup是继承View的,那么调用super.dispatchTouchEvent(event)等于调用ViewdispatchTouchEvent

View.java

/**
 * view接收事件的入口,事件由ViewGroup分发过来
 */
public boolean dispatchTouchEvent(MotionEvent event) {
    boolean result = false;

    //如果设置了OnTouchListener,并且mOnTouchListener.onTouch返回了True,
    //设置Result为True,那么代表事件到这已经消费完成了。
    if (mOnTouchListener != null && mOnTouchListener.onTouch(this, event)) {
        result = true;
    }

    //没有设置OnTouchListener,或者mOnTouchListener.onTouch返回了false时,result为false
    //此时会回调View.onTouchEvent方法
    if(!result&& onTouchEvent(event)){
        result = true;
    }
    return result;
}

如果不拦截,那么就会遍历当前ViewGroupchild view,找能消费事件的View,如果找到,调用dispatchTransformedTouchEvent(event, child),这里的child可以是ViewGroup或者是View,最后根据dispatchTransformedTouchEvent返回值判断是否消费了事件,如果返回false后,那么调用ViewGroupsuper.dispatchTouchEvent(event),这里有一点需要注意的是dispatchTouchEvent实现方式ViewGrpupView是不一样的。