(一)Android事件分发机制 - View篇

2017-08-28  本文已影响0人  DevWang

本文适用于对Android事件分发机制有一定基础的开发者阅读,主要是通过对View类中的事件分发、事件消费方法的源代码进行解析以达到完全理解其原理的目的

我们知道View中包含dispatchTouchEventonTouchEvent方法,接下来我们通过源代码(基于Android6.0)看看这些方法内部到底做了哪些事情。

1、View#dispatchTouchEvent源码解析

public boolean dispatchTouchEvent(MotionEvent event) {
    ...
    boolean result = false;
     // 当前View是否可见(未被其他窗口遮盖住且未隐藏)
    if (onFilterTouchEventForSecurity(event)) {
        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! = null
    第二个条件:li.mOnTouchListener != null;
    第三个条件:(mViewFlags & ENABLED_MASK) == ENABLED;
    第四个条件:li.mOnTouchListener.onTouch(this, event);
第一个条件:li! = null
第二个条件:li.mOnTouchListener != null
// mOnTouchListener是在View类的setOnTouchListener方法里赋值的
public void setOnTouchListener(OnTouchListener l) {
    // 只要我们给控件注册了Touch事件,mOnTouchListener就一定被赋值(不为空)
    getListenerInfo().mOnTouchListener = l;
}
第三个条件:(mViewFlags & ENABLED_MASK) == ENABLED
第四个条件:mOnTouchListener.onTouch(this, event)

回调注册的Touch事件的onTouch方法:


onTouch和onTouchEvent的区别

2、View#onTouchEvent源码解析

public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();

    // 如果当前View是DISABLED状态且是可点击/可长按则会消费掉事件,不让它继续传递
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        ...
        return (((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
    }
    // 如果设置了mTouchDelegate,则会将事件交给代理者处理,直接return true,如果大家希望自己的View增加它的touch范围,可以尝试使用TouchDelegate
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) { // / 如果有TouchDelegate的话,优先交给它处理
            return true; // 处理成功返回true,否则接着往下走
        }
    }
    // 如果view可点击/可长按,则最终一定return true
    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
            (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
        switch (action) {
            case MotionEvent.ACTION_UP: // 抬起操作
                ... 
                break;

            case MotionEvent.ACTION_DOWN: // 按下操作
                ...
                break;

            case MotionEvent.ACTION_CANCEL: // 取消操作
                ...
                break;
        }
        return true;
    }
    return false;
}
MotionEvent.ACTION_DOWN
switch (action) {
    ...
    case MotionEvent.ACTION_DOWN:
        // 设置mHasPerformedLongPress = false 表示长按事件还未触发;
        mHasPerformedLongPress = false;

        if (performButtonActionOnTouchDown(event)) {
            break;
        }

        // 判断当前View是否在滑动控件里面
        boolean isInScrollingContainer = isInScrollingContainer();

        // 如果当前View在一个可滑动的父View中,我们触摸它时需要延迟一小段时间用于判断是否为屏幕滚动事件
        if (isInScrollingContainer) {
            mPrivateFlags |= PFLAG_PREPRESSED; // 给mPrivateFlags设置一个PREPRESSED的标识
            if (mPendingCheckForTap == null) {
                mPendingCheckForTap = new CheckForTap();
            }
            mPendingCheckForTap.x = event.getX();
            mPendingCheckForTap.y = event.getY();
            // 发送一个延迟为100ms的点击(非滑动)延迟消息,到达延时时间后会执行CheckForTap()里面的run方法
            postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
        } else {
            // Not inside a scrolling container, so show the feedback right away
            setPressed(true, x, y);
            checkForLongClick(0);
        }
        break;
    ...
}
CheckForTap
private final class CheckForTap implements Runnable {
    public float x;
    public float y;

    @Override
    public void run() {
        mPrivateFlags &= ~PFLAG_PREPRESSED; // 取消mPrivateFlags的PREPRESSED
        setPressed(true, x, y); // 设置PRESSED标识为true,刷新背景
        checkForLongClick(ViewConfiguration.getTapTimeout());
    }
}
checkForLongClick
private void checkForLongClick(int delayOffset) {
    if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { // 支持长按
        mHasPerformedLongPress = false;

        if (mPendingCheckForLongPress == null) {
            mPendingCheckForLongPress = new CheckForLongPress();
        }
        mPendingCheckForLongPress.rememberWindowAttachCount();
        // 发送一个延迟消息,到达延迟时间后会执行CheckForLongPress()里面的run方法         
        postDelayed(mPendingCheckForLongPress,
                ViewConfiguration.getLongPressTimeout() - delayOffset);
    }
}
CheckForLongPress
private final class CheckForLongPress implements Runnable {
    private int mOriginalWindowAttachCount;

    @Override
    public void run() {
        // 用户从DOWN触发开始算起,向消息队列插入的长按响应消息如果在延迟时间内没有被取消则触发长按
        if (isPressed() && (mParent != null)
                && mOriginalWindowAttachCount == mWindowAttachCount) {
            if (performLongClick()) { // 根据长按的返回结果来设置mHasPerformedLongPress值
                mHasPerformedLongPress = true;
            }
        }
    }

    public void rememberWindowAttachCount() {
        mOriginalWindowAttachCount = mWindowAttachCount;
    }
}
performLongClick
public boolean performLongClick() {
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);

    boolean handled = false;
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnLongClickListener != null) {
        handled = li.mOnLongClickListener.onLongClick(View.this);
    }
    if (!handled) { // 长按返回结果为false - 不拦截事件
        // ContextMenu是Android的context menu上下文菜单,比如EditeText就可以通过长按来弹出拥有“cut”,"copy","paste"等项的ContextMenu。
        // 是否弹出context menu上下文菜单 - 是则拦截事件
        handled = showContextMenu();
    }
    if (handled) { // 如果弹出了context menu上下文菜单
        performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
    }
    return handled;
}
简单总结一下,ACTION_DOWN中都做什么:


MotionEvent.ACTION_MOVE
switch (action) {
    ...
    case MotionEvent.ACTION_MOVE:
                drawableHotspotChanged(x, y);

                // 判断当然触摸点有没有移出我们的View
                if (!pointInView(x, y, mTouchSlop)) {
                    // 移除点按定时任务 标志重置为0
                    removeTapCallback();
                    // 判断是否包含PRESSED标识,因为可能已经超过100ms,此时mPrivateFlags标志为PRESSED
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        // 移除长按定时任务
                        removeLongPressCallback();
                        setPressed(false);
                    }
                }
                break;
    ...
}
简单总结一下,ACTION_MOVE中都做了什么:

检测触摸是否划出了控件,如果划出了:


MotionEvent.ACTION_UP
switch (action) {
        ...
        case MotionEvent.ACTION_UP:
            // 判断mPrivateFlags是否包含PREPRESSED
            boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
            // 如果mPrivateFlags包含PRESSED或者PREPRESSED
            if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                boolean focusTaken = false;
                if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                    focusTaken = requestFocus();
                }

                if (prepressed) {
                    setPressed(true, x, y);
                }
                // 没有执行长按操作
                if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                    // 移除长按消息
                    removeLongPressCallback();

                    // Only perform take click actions if we were in the pressed state
                    if (!focusTaken) {
                        // 执行点击
                        if (mPerformClick == null) {
                            mPerformClick = new PerformClick();
                        }
                        if (!post(mPerformClick)) {
                            performClick();
                        }
                    }
                }

                if (mUnsetPressedState == null) {
                    mUnsetPressedState = new UnsetPressedState();
                }

                if (prepressed) {
                    postDelayed(mUnsetPressedState,
                            ViewConfiguration.getPressedStateDuration());
                } else if (!post(mUnsetPressedState)) {
                    // If the post failed, unpress right now
                    mUnsetPressedState.run();
                }

                removeTapCallback();
            }
            mIgnoreNextUpEvent = false;
            break;
        ...            
}
performClick
public boolean performClick() {
    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK); // 声音
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false;
    }
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    return result;
}
最后总会进入执行到UnsetPressedState
private final class UnsetPressedState implements Runnable {
    @Override
    public void run() {
        setPressed(false); // 取消mPrivateFlags中的PRESSED标志
    }
}
setPressed方法
public void setPressed(boolean pressed) {
    final boolean needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED);

    if (pressed) {
        mPrivateFlags |= PFLAG_PRESSED;
    } else {
        mPrivateFlags &= ~PFLAG_PRESSED;
    }

    if (needsRefresh) {
        refreshDrawableState();
    }
    // 把setPress转发下去给所有的子View
    dispatchSetPressed(pressed);
}
简单总结一下,ACTION_UP中都做了什么:


setOnLongClickListener和setOnClickListener是否只能执行一个

不是的,只要setOnLongClickListener中的onLongClick返回false,则两个都会执行;返回true则会屏蔽setOnClickListener


我们总结一下:

结合下面这篇文章看更好理解哦:
Android触摸屏事件派发机制详解与源码分析一(View篇)

上一篇 下一篇

猜你喜欢

热点阅读