美文网首页
View的事件分发源码分析

View的事件分发源码分析

作者: Utte | 来源:发表于2018-07-28 09:09 被阅读40次

源码

View # dispatchTouchEvent()

public boolean dispatchTouchEvent(MotionEvent event) {
    // If the event should be handled by accessibility focus first.
    // 如果该事件需要先被处于焦点的View处理
    if (event.isTargetAccessibilityFocus()) {
        // We don't have focus or no virtual descendant has it, do not handle the event.
        // 判断当前View是否就是处于焦点的或是焦点View处于该View的下级。
        // boolean isAccessibilityFocusedViewOrHost() {
        //     return isAccessibilityFocused() || (getViewRootImpl() != null && getViewRootImpl()
        //             .getAccessibilityFocusedHost() == this);
        // }
        if (!isAccessibilityFocusedViewOrHost()) {
            // 如果不符合上面的判断,就直接返回false,结束此View的分发。
            return false;
        }
        // We have focus and got the event, then use normal event dispatch.
        // 如果满足上面的判断,就继续正常的分发过程,设置参数为false,避免下次重复判断。
        event.setTargetAccessibilityFocus(false);
    }
    boolean result = false;
    // 如果事件检查对象不为空,就检查这个事件,包括检查事件序列完整性等等。
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }
    // 获取到事件类型
    final int actionMasked = event.getActionMasked();
    // 如果是ACTION_DOWN
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Defensive cleanup for new gesture
        // 如果有滚动条,就停止滚动,并清除滚动条
        stopNestedScroll();
    }
    // public boolean onFilterTouchEventForSecurity(MotionEvent event) {
    //     //noinspection RedundantIfStatement
    //     检查是否需要过滤到该事件,如果该View要求当窗口遮挡时过滤事件,并且事件被遮挡了。
    //     if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
    //             && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
    //         // Window is obscured, drop this touch.
    //         就返回false,表示过滤该事件。
    //         return false;
    //     }
    //     否则返回true,不过滤。
    //     return true;
    // }
    if (onFilterTouchEventForSecurity(event)) {
        // 尝试当作操作滚动条的事件处理。
        //先判断View是否是可用的并且当前事件是为了操作滚动条的,如果是就先将返回值置为true。
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        // 尝试onTouch()处理。
        // 四个判断
        // 1. ListenerInfo不为空
        // 2. ListenerInfo的onTouchListener不为空,onTouchListener需要手动设置。
        // 3. 该View是enable的,很多View默认为enable
        // 4. onTouchListener的onTouch()返回true,这个方法需要看自己怎么实现onTouch()。
        // 都满足就先将返回值置为true。
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        // 尝试onTouchEvent()处理。
        // 如果经过了上面一系列的判断,result没有被设置为true,就调用onTouchEvent(),看是否能被消耗。
        // 如果上面已经设置为true了,那就不去尝试消耗。
        if (!result && onTouchEvent(event)) {
            // 如果该View消耗了该事件,将返回值置为true。
            result = true;
        }
    }
    // 如果经过上面的操作,事件还没有处理。
    if (!result && mInputEventConsistencyVerifier != null) {
        // 调用onUnhandledEvent(),这个事件的后序检查追踪不需要做了。
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }
    // Clean up after nested scrolls if this is the end of a gesture;
    // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
    // of the gesture.
    // 如果这个事件是ACTION_UP或ACTION_CANCEL或是未处理的ACTION_DOWN
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        // 就停止滚动。
        stopNestedScroll();
    }
    //将上面设置的result返回,表示是否消耗了该事件。
    return result;
}

View # onTouchEvent()

public boolean onTouchEvent(MotionEvent event) {
    // 获取时间相对于当前View的坐标
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();
    // 判断该View是否是可点击的,通过三个标识,CLICKABLE LONG_CLICKABLE CONTEXT_CLICKABLE判断。
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
    // 如果View是disabled的
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        // 如果是ACTION_UP时间并且是按下状态,就将状态设回false。
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            // setPressed()会设置PRESSED状态,
            // 并且在需要重新绘制时调用refreshDrawableState()进行重新绘制。
            setPressed(false);
        }
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
        // A disabled view that is clickable still consumes the touch
        // events, it just doesn't respond to them.
        // 一个disabled的View还是能消耗事件,只是不会做出反馈。
        // 不做任何处理,直接返回是否能够点击。只要能点击就默认消耗了该事件。
        return clickable;
    }
    // 判断是否设置了委派
    // 大概功能是这样的,假设有两个View,分别是v1,v2,
    // 我们可以通过v1的setTouchDelegate(bounds, v2)来委派触摸事件,
    // 其中bounds是一个Rect。v1中,落在这个范围的TouchEvent都会传给v2。
    if (mTouchDelegate != null) {
        // 如果有就尝试交给委派的View来消耗事件。
        // 返回true说明委派对象处理了该事件,整个方法也返回true,说明该事件被委托View消耗。
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }
    // 如果之前两步操作没有消耗该事件,判断该View是可点击的或是可显示ToolTip时
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        // 分事件类型去操作
        switch (action) {
            case MotionEvent.ACTION_UP:
                // ......
                break;
            case MotionEvent.ACTION_DOWN:
                // ......
                break;
            case MotionEvent.ACTION_CANCEL:
                // ......
                break;
            case MotionEvent.ACTION_MOVE:
                // ......
                break;
        }
        // 只要进入了分事件类型处理,就返回true,表示消耗
        return true;
    }
    // 如果既没有在分情况之间消耗返回,也没有进入分情况处理,就返回未消耗
    return false;
}
  • ACTION_DOWN处理
case MotionEvent.ACTION_DOWN:
    // 判断了一下输入源是否为触控屏幕,然后给mPrivateFlags3赋值按下的状态。
    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
    }
    // 设置没有长按
    mHasPerformedLongPress = false;
    // 如果该View不可点击
    if (!clickable) {
        // 检查长按 然后直接跳出
        //  private void checkForLongClick(int delayOffset, float x, float y) {
        //     // 可长按或可可加入ToolTip
        //     if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOO
        //         // 先初始设置长按还没有发生
        //         mHasPerformedLongPress = false;
        //         // 初始化检查长按的runnable
        //         if (mPendingCheckForLongPress == null) {
        //             mPendingCheckForLongPress = new CheckForLongPress();
        //         }
        //         mPendingCheckForLongPress.setAnchor(x, y);
        //         mPendingCheckForLongPress.rememberWindowAttachCount();
        //         mPendingCheckForLongPress.rememberPressedState();
        //         // 加入执行队列,这个runnable执行后如果真的是长按,会将mHasPerformedLongPress置为true
        //         postDelayed(mPendingCheckForLongPress,
        //                 ViewConfiguration.getLongPressTimeout() - delayOffset);
        //     }
        // }
        // CheckForLongPress()中的run
        //  @Override
        // public void run() {
        //     if ((mOriginalPressedState == isPressed()) && (mParent != null)
        //             && mOriginalWindowAttachCount == mWindowAttachCount) {
        //         if (performLongClick(mX, mY)) {
        //             mHasPerformedLongPress = true;
        //         }
        //     }
        // }
        checkForLongClick(0, x, y);
        break;
    }
    // 这里是处理物理设备点击?
    if (performButtonActionOnTouchDown(event)) {
        break;
    }
    // Walk up the hierarchy to determine if we're inside a scrolling container.
    // 判断该View是否有一层父类是可滑动的
    boolean isInScrollingContainer = isInScrollingContainer();
    // For views inside a scrolling container, delay the pressed feedback for
    // a short period in case this is a scroll.
    if (isInScrollingContainer) {
        // 设置标识位,这个PREPRESSED会在ACTION_UP中用上
        mPrivateFlags |= PFLAG_PREPRESSED;
        // 检查单击初始化
        if (mPendingCheckForTap == null) {
            // CheckForTap的run()
            // @Override
            // public void run() {
            //     // 设置非预按下
            //     mPrivateFlags &= ~PFLAG_PREPRESSED;
            //     // 设置按下状态
            //     setPressed(true, x, y);
            //     // 也要检查长按
            //     checkForLongClick(ViewConfiguration.getTapTimeout(), x, y);
            // }
            mPendingCheckForTap = new CheckForTap();
        }
        mPendingCheckForTap.x = event.getX();
        mPendingCheckForTap.y = event.getY();
        // 将检查任务延时加入执行队列
        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
    } else {
        // Not inside a scrolling container, so show the feedback right away
        // 如果不在滑动嵌套内部,就直接不延时去设置按下状态
        setPressed(true, x, y);
        // 也要检查长按
        checkForLongClick(0, x, y);
    }
    break;
  • ACTION_MOVE处理
case MotionEvent.ACTION_MOVE:
    // 如果是可点击
    if (clickable) {
        // 调用去修改热点状态,
        // 还能自己实现dispatchDrawableHotspotChanged(x, y)实现分发变化
        drawableHotspotChanged(x, y);
    }
    // Be lenient about moving outside of buttons
    // 如果事件是否在View加上最小滑动距离的范围内
    if (!pointInView(x, y, mTouchSlop)) {
        // Outside button
        // Remove any future long press/tap checks
        // 将检查的runnable移除
        removeTapCallback();
        removeLongPressCallback();
        // 如果存在按下状态,就取消按下状态
        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
    }
    break;
  • ACTION_CANCEL处理
case MotionEvent.ACTION_CANCEL:
    // 如果是可点击的就恢复之前未按下的状态
    if (clickable) {
        setPressed(false);
    }
    // 将几个runnable移除 mPendingCheckForTap mPendingCheckForLongPress
    // 都是在之前事件中加入的,此事件通知取消,
    // 所以哪怕没来得及执行runnable也需要移除
    removeTapCallback();
    removeLongPressCallback();
    // 恢复状态
    mInContextButtonPress = false;
    mHasPerformedLongPress = false;
    mIgnoreNextUpEvent = false;
    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
    break;
  • ACTION_UP处理
case MotionEvent.ACTION_UP:
    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
    // 如果能有ToolTip
    if ((viewFlags & TOOLTIP) == TOOLTIP) {
        // 处理ToolTip
        // 如果相关ToolTip信息或弹窗为空,就直接返回。
        // 如果不空,先移除隐藏ToolTip的runnable,
        // 再根据配置时间延时发送,执行隐藏的runnable
        // private void handleTooltipUp() {
        //     if (mTooltipInfo == null || mTooltipInfo.mTooltipPopup == null) {
        //         return;
        //     }
        //     removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
        //     postDelayed(mTooltipInfo.mHideTooltipRunnable,
        //             ViewConfiguration.getLongPressTooltipHideTimeout());
        // }
        handleTooltipUp();
    }
    // 如果不能点击
    if (!clickable) {
        // 将两个Callback移除,因为不能点击,它们的runnable就不应该生效。
        removeTapCallback();
        removeLongPressCallback();
        // 相关属性置为false
        mInContextButtonPress = false;
        mHasPerformedLongPress = false;
        mIgnoreNextUpEvent = false;
        break;
    }
    // PREFLAG会在当该View存在一层父View为滑动容器时设置。
    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
    // 如果该View处于按下状态或设置了PFLAG_PREFLAG
    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
        // take focus if we don't have it already and we should in
        // touch mode.
        boolean focusTaken = false;
        // 判断该View是否为可获得焦点且设置了focusableInTouchMode为true并且还没有获得焦点时
        // 设置focusable意思是控件是否能够获得普通焦点,这个可以理解为通过物理键盘,
        // 因为Android不仅仅针对手机,有可能在某些设备上存在上下键,
        // 如果focusable设置为true,那么键盘上下左右选中,焦点会随之移动。
        // focusableInTouchMode意思是控件是否能触摸获得焦点,
        // 如果设置为true,点击后首先会让View获得焦点,获取后才出发点击事件。
        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
            // 尝试获得焦点,设置focusTaken为true。
            focusTaken = requestFocus();
        }
        if (prepressed) {
            // The button is being released before we actually
            // showed it as pressed.  Make it show the pressed
            // state now (before scheduling the click) to ensure
            // the user sees it.
            // 处理还没来得及显示的按下状态,
            // 比如说down事件时延时显示按下状态,到了up时间还没有显示,
            // 就设置PFLAG_PRESSED为true,显示按下状态,如果状态更改还需要进行重绘。
            // private void setPressed(boolean pressed, float x, float y) {
            //     if (pressed) {
            //         drawableHotspotChanged(x, y);
            //     }
            //     setPressed(pressed);
            // }
            // public void setPressed(boolean pressed) {
            //     // 如果当前标识等于PFLAG_PRESSED,处于按下状态,传参为true则不需要重绘界面,传参false就需要。
            //     // 如果当前标识不等于PFLAG_PRESSED,未处于按下状态,传参为true就要重绘,false不需要。
            //     // 也就是更改状态时就需要重绘。
            //     final boolean needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED);
            //     // 根据传参设置mPrivateFlags的值。
            //     if (pressed) {
            //         mPrivateFlags |= PFLAG_PRESSED;
            //     } else {
            //         mPrivateFlags &= ~PFLAG_PRESSED;
            //     }
            //     // 更新界面
            //    if (needsRefresh) {
            //        refreshDrawableState();
            //    }
            //    // 将按下的状态分发给子View,在这里是空实现。
            //    dispatchSetPressed(pressed);
            // }
            setPressed(true, x, y);
        }
        // 如果没有执行长按并且会不忽略下次ACTION_UP事件
        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
            // This is a tap, so remove the longpress check
            // 已经ACTION_UP了还没有执行长按,
            // 所以移除的长按runnable。
            removeLongPressCallback();
            // Only perform take click actions if we were in the pressed state
            // 当没有通过判断进入上面获取焦点的操作,
            // 有可能是因为物理或触摸模式为false,也可能在之前已经获取到了焦点。
            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.
                // performClick()去调用onClick()方法。
                if (mPerformClick == null) {
                    // private final class PerformClick implements Runnable {
                    //     @Override
                    //     public void run() {
                    //         performClick();
                    //     }
                    // }
                    mPerformClick = new PerformClick();
                }
                // 将构造好的runnable放入消息队列。
                // public boolean post(Runnable action) {
                //     final AttachInfo attachInfo = mAttachInfo;
                //     if (attachInfo != null) {
                //         return attachInfo.mHandler.post(action);
                //     }
                //     // Postpone the runnable until we know on which thread it needs to run.
                //     // Assume that the runnable will be successfully placed after attach.
                //     getRunQueue().post(action);
                //     return true;
                // }
                if (!post(mPerformClick)) {
                    // 如果加入失败,就直接调用performClick()。
                    performClick();
                    // public boolean performClick() {
                    //     final boolean result;
                    //     final ListenerInfo li = mListenerInfo;
                    //     // 如果OnClickListener存在
                    //     if (li != null && li.mOnClickListener != null) {
                    //         // 触发声效
                    //         playSoundEffect(SoundEffectConstants.CLICK);
                    //         // 调用onClick()
                    //         li.mOnClickListener.onClick(this);
                    //         result = true;
                    //     } else {
                    //         // 如果成功执行了onClick()就会返回true,否则返回false。
                    //         result = false;
                    //     }
                    //     // 发送一些[辅助功能](https://blog.csdn.net/omnispace/article/details/70598515)通知
                    //     sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
                    //     notifyEnterOrExitForAutoFillIfNeeded(true);
                    //     return result;
                    // }
                }
            }
        }
        // 如果还没有设置回未按下状态
        if (mUnsetPressedState == null) {
            // 赋值
            // private final class UnsetPressedState implements Runnable {
            //     @Override
            //     public void run() {
            //         setPressed(false); // 设置未按下状态
            //     }
            // }
            mUnsetPressedState = new UnsetPressedState();
        }
        // 如果外层有滑动父View
        if (prepressed) {
            // 延时去发送设置未按下的操作
            postDelayed(mUnsetPressedState,
                    ViewConfiguration.getPressedStateDuration());
        } else if (!post(mUnsetPressedState)) {
            // 如果没有父类有滑动,那就直接发送设置未按下状态的操作到执行队列。
            // 如果没有加入成功,就直接调用方法。
            // If the post failed, unpress right now
            mUnsetPressedState.run();
        }
        // 将检察点击的runnable移除。
        removeTapCallback();
    }
    // 不忽略接收下次的ACTION_UP事件
    mIgnoreNextUpEvent = false;
    break;

框图

dispatchTouchEvent()
onTouchEvent()

相关文章

网友评论

      本文标题:View的事件分发源码分析

      本文链接:https://www.haomeiwen.com/subject/rajimftx.html