Android事件分发机制

675 阅读4分钟

总述

事件分发是将点击事件(MotionEvent)传递到某个具体的View/ViewGroup然后被处理的过程。事件大致传递的流程是从Activity -> ViewGroup -> View

其中事件类型MotionEvent分为以下几种类型,

事件类型 具体动作
MotionEvent.ACTION_DOWN 按下手指(所有事件的开始)
MotionEvent.ACTION_UP 抬起手指
MotionEvent.ACTION_MOVE 滑动手指
MotionEvent.ACTION_CANCEL 非人为的原因结束事件

事件分发涉及到的核心方法,

方法 作用 被调用的地方
dispatchTouchEvent 分发事件 点击事件能够传递给当前View时,该方法被调用
onTouchEvent 处理点击事件 dispatchTouchEvent()内部被调用
onInterceptTouchEvent 判断是否拦截该点击事件(ViewGroup独有方法) ViewGroup.dispatchTouchEvent()中被调用

源码分析

Activity

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    	onUserInteraction();
    }
    //getWindow().superDispatchTouchEvent(ev)方法最终会传递到ViewGroup中的dispatchTouchEvent里
    if (getWindow().superDispatchTouchEvent(ev)) {
    	return true;
    }
    return onTouchEvent(ev);
}

//Activity中这个方法无论分发按键事件、触摸事件或者轨迹球事件都会被调用
//可以复写它做一些特殊需求,例如长时间用户未点击则应用锁屏
public void onUserInteraction() {
}

//这里顺带提一下onUserLeaveHint方法,在用户手动离开当前Activity时,会调用该方法
//比如用户主动切换任务,短按home进入桌面等。系统自动切换activity不会调用此方法,如来电,灭屏等。
//应用场景是可以做统一的监听,比如要监听用户点了Home键跳回到桌面后,APP需要自动跳转到解锁界面,我们可以在这里实现逻辑
protected void onUserLeaveHint() {
}

//mWindow.shouldCloseOnTouch用于判断触摸事件是否滑出了屏幕以外,
//在窗口范围外事件是没法被被接受的,该事件没有视图可以接收,所以返回true消费掉
public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) {
    	finish();
    	return true;
    }
    return false;
}

ViewGroup

/**
 * ViewGroup.dispatchTouchEvent
 * 简化过的代码
 */
public boolean dispatchTouchEvent(MotionEvent ev) {
	...
    // 调用onInterceptTouchEvent()询问是否拦截事件
    if (disallowIntercept || !onInterceptTouchEvent(ev)) {

        // disallowIntercept 表示 是否禁用事件拦截的功能(默认为false)
        // 可通过requestDisallowInterceptTouchEvent()方法修改

        ev.setAction(MotionEvent.ACTION_DOWN);
        final int scrolledXInt = (int) scrolledXFloat;
        final int scrolledYInt = (int) scrolledYFloat;
        final View[] children = mChildren;
        final int count = mChildrenCount;

        // 遍历当前ViewGroup下的所有子View
        for (int i = count - 1; i >= 0; i--) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                    || child.getAnimation() != null) {
                child.getHitRect(frame);

                // 判断当前遍历的View是否是被点击的View,从而找到当前被点击的View
                if (frame.contains(scrolledXInt, scrolledYInt)) {
                    final float xc = scrolledXFloat - child.mLeft;
                    final float yc = scrolledYFloat - child.mTop;
                    ev.setLocation(xc, yc);
                    child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;

                    // 内部调用了该View的dispatchTouchEvent()
                    // 从而实现了事件从ViewGroup到子View的传递
                    if (child.dispatchTouchEvent(ev)) {
                        mMotionTarget = child;
                        return true;
                        // 调用子View的dispatchTouchEvent后是有返回值的
                        // 若该控件可点击,那么点击时,dispatchTouchEvent的返回值必定是true,因此会导致条件判断成立
                        // 于是给ViewGroup的dispatchTouchEvent()直接返回了true,即直接跳出
                        // 即将ViewGroup的点击事件拦截掉
                    }
                }
            }
        }
    }
    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
            (action == MotionEvent.ACTION_CANCEL);
    if (isUpOrCancel) {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }
    final View target = mMotionTarget;

    // 若点击的是空白处(即无任何View接收事件)
    // 拦截事件(手动复写onInterceptTouchEvent(),从而让其返回true)
    if (target == null) {
        ev.setLocation(xf, yf);
        if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
            ev.setAction(MotionEvent.ACTION_CANCEL);
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
        }

        return super.dispatchTouchEvent(ev);
        // 调用ViewGroup父类的dispatchTouchEvent(),即View.dispatchTouchEvent()
        // 因此会执行ViewGroup的onTouch() ->> onTouchEvent() ->> performClick() ->> onClick()
    }
}

/**
 * ViewGroup.onInterceptTouchEvent()
 * 作用:是否拦截事件
 * 说明:
 * 返回true = 拦截,事件停止往下传递(复写onInterceptTouchEvent(),使其返回true)
 * 返回false = 不拦截(默认)
 */
public boolean onInterceptTouchEvent(MotionEvent ev) {
    return false;
}

View

/**
 * View.dispatchTouchEvent()
 * 只有以下3个条件都为真,dispatchTouchEvent() 才返回true;否则执行onTouchEvent()
 * 
 * 1. mOnTouchListener != null (mOnTouchListener变量在View.setOnTouchListener()方法里赋值)
 * 
 * 2.(mViewFlags & ENABLED_MASK) == ENABLED (判断当前点击的控件是否enable,大多数控件默认都是enable的)
 * 
 * 3.mOnTouchListener.onTouch(this, event)
 * 回调mOnTouchListener.onTouch方法return true or false
 * (该方法需要在复写的时候手动赋值,如果不需要View执行onTouchEvent那么可以return true)
 */
public boolean dispatchTouchEvent(MotionEvent event) {

    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
            mOnTouchListener.onTouch(this, event)) {
        return true;
    }
    return onTouchEvent(event);
}

如果onTouch没有消费掉事件(return false),那么会调用View.onTouchEvent方法。

以下是View.onTouchEvent的源码分析,

/**
 * 源码分析:View.onTouchEvent()
 * 简化过的代码
 */
public boolean onTouchEvent(MotionEvent event) {
    ...
    
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        return (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
    }
    ...

    // 若该控件可点击,则进入switch判断中
    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {

        switch (event.getAction()) {

            // a. 抬起手指事件
            case MotionEvent.ACTION_UP:
                ...
                // 执行performClick()
                performClick();
                break;

            // b. 按下手指
            case MotionEvent.ACTION_DOWN:
                ...
                break;

            // c. 结束事件(非人为原因)
            case MotionEvent.ACTION_CANCEL:
                ...
                break;

            // d. 滑动事件
            case MotionEvent.ACTION_MOVE:
                final int x = (int) event.getX();
                final int y = (int) event.getY();

                int slop = mTouchSlop;
                if ((x < 0 - slop) || (x >= getWidth() + slop) ||
                        (y < 0 - slop) || (y >= getHeight() + slop)) {
                    // Outside button
                    removeTapCallback();
                    if ((mPrivateFlags & PRESSED) != 0) {
                        // Remove any future long press/tap checks
                        removeLongPressCallback();
                        // Need to switch from pressed to not pressed
                        mPrivateFlags &= ~PRESSED;
                        refreshDrawableState();
                    }
                }
                break;
        }
        // 若该控件可点击,就一定返回true
        return true;
    }
    // 若该控件不可点击,就一定返回false
    return false;
}

/**
 * performClick()
 */
public boolean performClick() {
    /* 
     只要为控件注册过点击事件setOnClickListener(),就会回调onClick() 并返回true
     */
    if (mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        mOnClickListener.onClick(this);
        return true;
    }
    return false;
}

核心流程总结