View的事件分发机制(二)--View的源码分析

dispatchTouchEvent()源码

public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        // 事件可以获取焦点
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            // 判断当前View是否是处于焦点或是焦点View是该View的下级
            if (!isAccessibilityFocusedViewOrHost()) {
            // 结束此view的分发
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            // 如果满足上面的判断,就继续正常的分发过程,避免下次重复判断
            event.setTargetAccessibilityFocus(false);
        }

        boolean result = false;
        // 如果检查对象不空就检查事件(包括检查事件序列完整性)在ViewGroup中也有,传入的嵌套级别为1
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            // 如果有滚动条,就停止滚动,并清除滚动条
            stopNestedScroll();
        }
        // 如果不过滤该事件
        if (onFilterTouchEventForSecurity(event)) {
            // 如果View是可用的并且当前事件是为了操作滚动条的
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            // ListenerInfo不为空
            // ListenerInfo的onTouchListener不为空(需手动设置)
            // 该View是可见的(很多View默认是可见的)
            // OnTouchListener的onTouch()返回true
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            
            // 如果result没有被设置为true,就调用onTouchEvent()看是否能被消耗
            // 如果OnTouchListener中的onTouch方法返回true,那么onTouchEvent就不会被调用,可见OnTouchListener的优先级高于onTouchEvent(方便在外界处理点击事件)
            if (!result && onTouchEvent(event)) {
                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();
        }

        return result;
    }

onTouch()与onTouchEvent()区别

onTouch()

是获取某一控件的触摸事件,必须使用setOnTouchListener绑定到控件,然后才能监听该控件的触摸事件,当view有touch事件触发时,就会调用onTouch方法

onTouchEvent()

是手机屏幕事件的处理方法,对屏幕的各种操作比如左右滑动,点击返回按钮等,属于一个宏观的屏幕触摸监控。onTouchEvent方法是override 的Activity的方法。重写了Activity的onTouchEvent方法后,当屏幕有touch事件时,此方法就会被调用。

onTouchEvent()源码

// view的LONG_CLICKABLE属性默认为false,CLICKABLE属性和具体的view有关
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是否是可点击的,通过三个标识,CLICKABLE LONG_CLICKABLE CONTEXT_CLICKABLE判断。
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
        // 如果view是不可用的
        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.
            // 一个不可用的View还是能消耗事件,只是不会做出反馈。
            // 不做任何处理,直接返回是否能够点击。只要能点击就默认消耗了该事件。
            return clickable;
        }
        // 判断是否设置了委派
        // 假设有两个View,分别是v1,v2,
        // 我们可以通过v1的setTouchDelegate(bounds, v2)来委派触摸事件,
        // 其中bounds是一个Rect。v1中,落在这个范围的TouchEvent都会传给v2。
        if (mTouchDelegate != null) {
            // 如果有就尝试交给委派的View来消耗事件
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        // 如果View是可点击的或是可显示ToolTip(此视图可以在悬停或者长按时显示工具提示),它就会消耗这个事件
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    // 如果有TOOLTIP
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        // 如果相关ToolTip信息或弹窗为空,就直接返回。
                        // 如果不空,先移除隐藏ToolTip的runnable,
                        // 将隐藏的runnable加入消息队列 经过指定的时间 runnable将在主界面运行
                        // private void handleTooltipUp() {
                        //     if (mTooltipInfo == null || mTooltipInfo.mTooltipPopup == null) {
                        //         return;
                        //     }
                        //     removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
                        //     postDelayed(mTooltipInfo.mHideTooltipRunnable,
                        //             ViewConfiguration.getLongPressTooltipHideTimeout());
                        // }
                        handleTooltipUp();
                    }
                    // 如果不能点击
                    if (!clickable) {
                        // 将两个runnable移除
                        removeTapCallback();
                        removeLongPressCallback();
                        // 恢复状态
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                    // PREFLAG会在当该View存在一层父View为滑动容器时down事件中设置
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    // 如果该View设置了PFLAG_PRESSED或者处于按下状态
                    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并且还没有获得焦点时
                        // isFocusable()返回当前view是否能够获得普通焦点(通过物理键盘)
                        // isFocusableInTouchMode()返回当前view在触摸模式下是否能获得焦点,如果设置为true,点击后首先会让View获得焦点,获取后才触发点击事件
                        // isFocusable()一般为true;isFocusableInTouchMode()如果不设置setFocusableInTouchMode(true)的话,默认为false;isFocused()这个值需要注意下,此值意思是是否拥有焦点
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            // 尝试获得焦点,设置focusTaken为true
                            focusTaken = requestFocus();
                        }
                        // prepressed状态表示滚动容器中的点击检测还没有被消息队列执行,这个时候如果抬起手指说明是一个点击事件,调用setPressed显示反馈
                        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.
                                // 通过Runnable和post 将点击事件加入消息队列来让其他视图在单击操作开始之前进行视图更新
                                // 当当前view没有在获取焦点时才能触发点击事件,说明一个正在获取焦点的view是无法触发点击事件的
                                if (mPerformClick == null) {
                                // private final class PerformClick implements Runnable {
                                //     @Override
                                //     public void run() {
                                //         // performClick()去调用onClick()方法
                                //         performClickInternal();
                                //     }
                                // }
                                    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()
                                    // private boolean performClickInternal() {
                                    //      notifyAutofillManagerOnClick();
                                    //      return performClick();
                                    // }
                                    performClickInternal();
                                    // public boolean performClick() {
                                    //     final boolean result;
                                    //     final ListenerInfo li = mListenerInfo;
                                    //     // 如果OnClickListener存在
                                    //     if (li != null && li.mOnClickListener != null) {
                                    //         // 触发声效
                                    //         playSoundEffect(SoundEffectConstants.CLICK);
                                    //         // 如果view设置了OnClickListener,那么就会调用它的onClick方法
                                    //         li.mOnClickListener.onClick(this);
                                    //         result = true;
                                    //     } else {
                                    //         result = false;
                                    //     }
                                    //     // 发送辅助功能事件
                                    //     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();
                    }
                    // 不忽略接收下次up事件
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_DOWN:
                    // 判断了一下事件源是否为触控屏幕,然后给mPrivateFlags3赋值按下的状态。
                    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                    }
                    // 设置没有长按
                    mHasPerformedLongPress = false;
                    // 如果view不可点击
                    if (!clickable) {
                        // 检查长按然后跳出
                        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) {
                        // 设置标识位
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        // 检查单击
                        if (mPendingCheckForTap == null) {
                            // CheckForTap()的run()中也要检查长按
                            // 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;

                case MotionEvent.ACTION_CANCEL:
                    if (clickable) {
                        // 恢复之前未按下状态
                        setPressed(false);
                    }
                    // 将mPendingCheckForTap mPendingCheckForLongPress移除
                    removeTapCallback();
                    removeLongPressCallback();
                    // 恢复状态
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    break;

                case MotionEvent.ACTION_MOVE:
                    if (clickable) {
                        // 去修改热点状态
                        // 可以自己实现dispatchDrawableHotspotChanged(x, y)
                        drawableHotspotChanged(x, y);
                    }

                    // Be lenient about moving outside of buttons
                    // 扩展的区域是否在当前视图内
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        // Remove any future long press/tap checks
                        // 超出范围,移除点击、长按线程
                        removeTapCallback();
                        removeLongPressCallback();
                        // 如果存在按下状态,就取消按下状态
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            setPressed(false);
                        }
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    }
                    break;
            }

            return true;
        }

        return false;
    }

TouchDelegate

扩大view的点击范围

在这里插入图片描述

    // 创建TouchDelegate
    TouchDelegate touchDelegate = new TouchDelegate(bounds, view2);
    // 为View1设置TouchDelegate
     view1.setTouchDelegate(touchDelegate);

dispatchTouchEvent()流程图

在这里插入图片描述

onTouchEvent()流程图

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值