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

View事件分发源码

作者: 李die喋 | 来源:发表于2019-10-04 22:46 被阅读0次

    view事件分发源码

    public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        //--->分析一
        //判断事件是否具有焦点可访问性(是否可以获取焦点)(isFocusable isFocusableInTouchMode)
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            //当前view和它的子view都不可以获取焦点 返回false
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            //取消FLAG_TARGET_ACCESSIBILITY_FOCUS标志 进行正常的事件分发
            event.setTargetAccessibilityFocus(false);
        }
    
        boolean result = false;
        //如果事件检查对象不为空,就检查这个事件,包括检查事件序列完整性等等。
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }
    
        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            //停止嵌套滑动
            //当嵌套滑动当前不在进行时调用本方法是无害的
            //NestedScrolling
            stopNestedScroll();
        }
        //--->分析二
        //安全检查当前事件
        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不为空
            //3.该view是ENABLED的
            //OnTouchListener的OnTouch()返回true
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            //--->分析四
            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.
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }
        return result;
    }
    

    分析一

    焦点分为两种:

    • 硬键盘焦点focusable

    通过硬键盘(或物理键盘:蓝牙键盘等)上下左右操作焦点,点击,输入。

    • 触摸焦点focusableInTouchMode

    针对触屏情况下,点击屏幕的控件,先触发 OnFocusChangeListener,获取控件焦点,然后执行点击事件。EditText是默认有触摸获取焦点功能的,并将第一抢先获取焦点,因此页面有EditText的时默认有光标,键盘弹出。

    设置一个视图是否可以获取焦点可以通过以下方法:

    //设置视图是否可以获得焦点
    public void setFocusable(boolean focusable) 
    //获取视图是否可以获取焦点
    public final boolean isFocusable()
    

    触摸设备来说,一个视图在触摸下是否可以成为焦点视图:

    //设置视图是否在触摸模式下可以获得焦点 
    public void setFocusableInTouchMode(boolean focusableInTouchMode) 
    //获取视图是否在触摸模式下获得焦点
    public final boolean isFocusableInTouchMode()  
    

    一个视图要想获得焦点必须要setFocusable和setFocusableInTouchMode同时为true时才可以获取焦点。

    下面两个方法用来判断某个视图是否是焦点视图以及是否获取了焦点:

    //是否当前视图就是焦点视图
    public boolean isFocused() 
    //当前视图是否是焦点视图,或者子视图里面有焦点视图。
    public boolean hasFocus() 
    
    • event.isTargetAccessibilityFocus()
    /** @hide */
    public final boolean isTargetAccessibilityFocus() {
        final int flags = getFlags();
        return (flags & FLAG_TARGET_ACCESSIBILITY_FOCUS) != 0;
    }
    

    Android在系统级别引入了辅助功能选项来帮助有障碍的用户使用系统,所以一个事件带有FLAG_TARGET_ACCESSIBILITY_FOCUS标志,说明这是一个特殊的辅助功能事件,需要进行特殊处理。

    • ev.setTargetAccessibilityFocus(false)
    /** @hide */
    public final void setTargetAccessibilityFocus(boolean targetsFocus) {
        final int flags = getFlags();
        nativeSetFlags(mNativePtr, targetsFocus
                ? flags | FLAG_TARGET_ACCESSIBILITY_FOCUS
                : flags & ~FLAG_TARGET_ACCESSIBILITY_FOCUS);
    }
    

    取消标志,进行普通分发。

    分析二 过滤触摸事件以应用安全策略

    public boolean onFilterTouchEventForSecurity(MotionEvent event) {
        //视图窗口被遮挡且接收到运动事件的窗口是部分的或完全被遮挡(安全敏感程序 恶意应用程序)
        if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
                && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
            // Window is obscured, drop this touch.
            return false;
        }
        return true;
    }
    

    分析三 ListenerInfo

    Listener used to dispatch focus change events.
    This field should be made private, so it is hidden from the SDK.
    (监听器用于发送焦点变化事件。
    此字段应设为私有字段,对SDK隐藏。)

    分析四 onTouch和onTouchEvent

    • onTouch方法:

    onTouch()是OnTouchListener接口的方法,它是获取某一个控件的触摸事件,因此使用时,必须使用setOnTouchListener绑定到控件,然后才能鉴定该控件的触摸事件。当一个View绑定了OnTouchLister后,当有touch事件触发时,就会调用onTouch方法。

    textview.setOnTouchListener(new OnTouchListener){
        public boolean onTouch(View v,MotionEvent event){
            switch(event.getAction()){
                case MotionEvent.ACTION_DOWN:
                    break;
                ...
                default:
                    break;
            }
            return true;
        }
    }
    
    • onTouchEvent()方法

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

    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) {
                //是UP事件且view状态为按下(FLAG_PRESSED)
                if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                    //重置当前view的FLAG_PRESSED状态
                    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;
            }
            //扩大点击事件的范围
            if (mTouchDelegate != null) {
                if (mTouchDelegate.onTouchEvent(event)) {
                    return true;
                }
            }
            //可点击或者TOOLTIP(此视图可以在悬停或者长按时显示工具提示)
            if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
                switch (action) {
                    case MotionEvent.ACTION_UP:
                        ···
                        break;
                    ```
                }
                return true;
            }
        return false;
    }
    

    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();
        }
        //
        dispatchSetPressed(pressed);
    }
    

    TouchDelegate(扩展控件的关键类)

    扩大view事件的点击范围

    public TouchDelegate(Rect bounds, View delegateView)
    
    TouchDelegate touchDelegate = new TouchDelegate(delegateArea, myButton);
    if (View.class.isInstance(myButton.getParent())) {
          ((View) myButton.getParent()).setTouchDelegate(touchDelegate);
    }
    
    • ACTION_DOWN
    //输入设备的输入源是触摸屏幕指示设备
    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
    }
    //是否长按
    mHasPerformedLongPress = false;
    //view不可点击
    if (!clickable) {
        //检查长按后结束
        checkForLongClick(0, x, y);
        break;
    }
    //在DOWN事件期间 执行与按钮相关的操作(鼠标右键)
    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) {
        //在滚动容器内 设置PFLAG_PREPRESSED标志位
        mPrivateFlags |= PFLAG_PREPRESSED;
        //若单击为null
        if (mPendingCheckForTap == null) {
            mPendingCheckForTap = new CheckForTap();
            //CheckForTap的run()方法
            <!--@Override-->
            <!--public void run() {-->
                    //设置非预按下状态
            <!--    mPrivateFlags &= ~PFLAG_PREPRESSED;-->
                    //设置FLAG_PRESSED状态和drawableHotspotChanged(x,y)
            <!--    setPressed(true, x, y);-->
            <!--   
                   //检查长按
                   checkForLongClick(ViewConfiguration.getTapTimeout(), x, y);-->
            <!--}-->
        }
        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);
    }
    
    private void checkForLongClick(int delayOffset, float x, float y) {
        //长按或TOOLTIP
        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
            //设置长按为false
            mHasPerformedLongPress = false;
            //初始化长按 执行runnable
            if (mPendingCheckForLongPress == null) {
                mPendingCheckForLongPress = new CheckForLongPress();
            }
            mPendingCheckForLongPress.setAnchor(x, y);
            mPendingCheckForLongPress.rememberWindowAttachCount();
            mPendingCheckForLongPress.rememberPressedState();
            //将长按Runnable加入执行队
            postDelayed(mPendingCheckForLongPress,
                    ViewConfiguration.getLongPressTimeout() - delayOffset);
        }
    }
    //CheckForLongPress中的run
    @Override
    public void run() {
        if ((mOriginalPressedState == isPressed()) && (mParent != null)
                && mOriginalWindowAttachCount == mWindowAttachCount) {
            if (performLongClick(mX, mY)) {
                mHasPerformedLongPress = true;
            }
        }
    }
    
    • ACTION_MOVE
    //如果可以点击
    if (clickable) {
        //修改热点状态
        drawableHotspotChanged(x, y);
    }
    
    // Be lenient about moving outside of buttons
    //x,y在本地坐标中且x,y+slop扩展的区域在视图区域范围内
    if (!pointInView(x, y, mTouchSlop)) {
        <!--public boolean pointInView(float localX, float localY, float slop) {-->
        <!--    return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&-->
        <!--            localY < ((mBottom - mTop) + slop);-->
        <!--}-->
        // Outside button
        // Remove any future long press/tap checks
        //超出范围 移除点击、长按线程
        removeTapCallback();
        removeLongPressCallback();
        //恢复未按下状态
        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
    }
    
    • ACTION_CANCLE
    //恢复未按下状态
    if (clickable) {
        setPressed(false);
    }
    //移除点击或长按线程
    removeTapCallback();
    removeLongPressCallback();
    //初始化状态
    mInContextButtonPress = false;
    mHasPerformedLongPress = false;
    mIgnoreNextUpEvent = false;
    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
    
    • ACTION_UP
    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
    //如果是tooltip(工具提示 用户长按视图或者鼠标悬停在试图上时 消息显示在视图附近)
    if ((viewFlags & TOOLTIP) == TOOLTIP) {
        //--->分析1
        handleTooltipUp();
    }
    //若是tooltip 恢复状态后结束
    if (!clickable) {
        removeTapCallback();
        removeLongPressCallback();
        mInContextButtonPress = false;
        mHasPerformedLongPress = false;
        mIgnoreNextUpEvent = false;
        break;
    }
    //在嵌套滑动视图的DOWN事件中标记过
    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
    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获得焦点,获取后才出发点击事件。
        //--->分析2
        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
            //尝试获得焦点 设置focusTaken
            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事件还没有显示
            setPressed(true, x, y);
            <!--private void setPressed(boolean pressed, float x, float y) {-->
            <!--    if (pressed) {-->
                        //更新视图热点
            <!--        drawableHotspotChanged(x, y);-->
            <!--    }-->
            <!--    setPressed(pressed);-->
            <!--}-->
        }
        //没有执行长按且不会忽略虚假次UP事件
        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
            // This is a tap, so remove the longpress check
            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 将点击事件加入消息队列来让其他视图在单击操作开始之前进行视图更新
                --->分析3
                if (mPerformClick == null) {
                    mPerformClick = new
                    PerformClick();
                    //performClick()调用onClick()方法
                }
                --->分析4
                if (!post(mPerformClick)) {
                    //如果加入失败,就直接调用performClick()
                    performClickInternal();
                }
            }
        }
        //如果还没有设置回未按下状态
        if (mUnsetPressedState == null) {
            mUnsetPressedState = new UnsetPressedState();
            <!--private final class UnsetPressedState implements Runnable {-->
            <!--    @Override-->
            <!--    public void run() {-->
            <!--        setPressed(false);-->
            <!--    }-->
            <!--}-->
        }
        //如果外层有滑动父view
        if (prepressed) {
            //延时发送设置未按下的操作
            postDelayed(mUnsetPressedState,
                    ViewConfiguration.getPressedStateDuration());
        } else if (!post(mUnsetPressedState)) {
            // If the post failed, unpress right now
            //如果父类没有滑动 直接发送设置未按下状态的操作到执行队列
            //如果没有加入成功 就直接调用run()方法
            mUnsetPressedState.run();
        }
        //移除tap的runnbale
        removeTapCallback();
    }
    //不忽略接收下次的ACTION_UP事件
    mIgnoreNextUpEvent = false;
    

    1.handleTooltipUp()

    private void handleTooltipUp() {
        if (mTooltipInfo == null || mTooltipInfo.mTooltipPopup == null) {
            return;
        }
        //移除隐藏的runnable
        removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
        //将隐藏的runnable加入消息队列 经过指定的时间 runnable将在主界面运行
        postDelayed(mTooltipInfo.mHideTooltipRunnable,
                ViewConfiguration.getLongPressTooltipHideTimeout());
    }
    

    2.判断view是否可以获得焦点

    //返回当前view是否能够获得焦点
    public final boolean isFocusable() {
        return FOCUSABLE == (mViewFlags & FOCUSABLE);
    }
    //当视图可聚焦时 在触摸模式下可能不想聚焦
    //返回当前view在触摸模式下是否能够获得焦点
    public final boolean isFocusableInTouchMode() {
        return FOCUSABLE_IN_TOUCH_MODE == (mViewFlags & FOCUSABLE_IN_TOUCH_MODE);
    }
    //视图有焦点 返会true
    public boolean isFocused() {
        return (mPrivateFlags & PFLAG_FOCUSED) != 0;
    }
    

    3.PerformClick

    private final class PerformClick implements Runnable {
        @Override
        public void run() {
            performClickInternal();
        }
    }
    private boolean performClickInternal() {
        notifyAutofillManagerOnClick();
        return performClick();
    }
    public boolean performClick() {
        // We still need to call this method to handle the cases where performClick() was called
        // externally, instead of through performClickInternal()
        notifyAutofillManagerOnClick();
    
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            //触发声效
            playSoundEffect(SoundEffectConstants.CLICK);
            //调用onClick()
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }
        //发送辅助功能事件
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    
        notifyEnterOrExitForAutoFillIfNeeded(true);
    
        return result;
    }
    

    post(mPerformClick)

    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;
    }
    

    总结

    view事件分发流程图 onTouchEvent()方法流程图

    参考文章

    超级详细

    相关文章

      网友评论

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

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