美文网首页
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