美文网首页
View的事件分发

View的事件分发

作者: 有兴不虚昧 | 来源:发表于2018-04-23 22:25 被阅读0次

    之前看过很多的事件分发博客,特别是看分析源码,一看到源码就头大,太菜没办法,就只能死记别人最后的结论。还自我安慰事件分发明白的差不多了,但一到项目中,很多时候还是要靠搜索引擎。痛定思痛,决定还是看源码,并写博客记录。

    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.
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }
        //默认返回值为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
           //down事件停止嵌套滚动
            stopNestedScroll();
        }
    
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
      //关键代码1:设置了onTouchListener,并且其onTouch返回true,dispatchTouchEvent直接return true,
      //不走下面if语句;如果onTouch()返回false,条件不成立,接着往下走
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
      //关键代码2:如果上面的if条件不成立,走到这里,onTouchEvent()返回值决定了dispatchTouchEvent()的返回值
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
    
        if (!result && mInputEventConsistencyVerifier != null) {
            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();
        }
      //上面2处关键的if语句都没有走进去,返回默认值false
        return result;
    }
    

    view的dispatchTouchEvent()代码不多,只有两处关键代码,在这里有个思考:为什么onTouchListener的onTouch()方法比onTouchEvent()方法先执行?因为onTouchListener()是暴露给开发者处理Touch事件的接口,而onTouchEvent()是系统默认的处理Touch事件的函数,所以如果开发者想自己定义Touch事件处理方式,就必须在系统的默认处理方式之前执行。

    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的Clickable或者LongClickable为true,那么clickable标记为true
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
      //如果当前view是disable,直接返回clickable的值
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
          //直接返回clickable的值
            return clickable;
        }
        //TouchDelegate是一个代理类,可以用来扩展某个view的触摸范围,需要手动设置
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        //如果clickable为true(手动设置了setClickable(true)或者setLongClickable(true), 或者设置了onClickListener(),
         //longClickListener(),clickable都为true)
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        handleTooltipUp();
                    }
                    if (!clickable) {
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                 //是否是预按下状态,只有在滚动的容器类View按下的时候才会添加这个标记
                    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;
                      //如果能获取焦点,并且没有过获取焦点
                        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.
                            setPressed(true, x, y);
                        }
    
                        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
                            //按下状态执行点击action
                            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.
                                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;
    
                case MotionEvent.ACTION_DOWN:
                    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                    }
                    mHasPerformedLongPress = false;
    
                    if (!clickable) {
                        //检测长按事件
                        checkForLongClick(0, x, y);
                        break;
                    }
    
                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }
    
                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    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) {
                            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
                      //设置按下状态,会添加PFLAG_PRESSED按下标记
                        setPressed(true, x, y);
                        checkForLongClick(0, x, y);
                    }
                    break;
    
                case MotionEvent.ACTION_CANCEL:
                    if (clickable) {
                        setPressed(false);
                    }
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    break;
    
                case MotionEvent.ACTION_MOVE:
                    if (clickable) {
                        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
                        removeTapCallback();
                        removeLongPressCallback();
                      //如果有按下标记,down的时候会添加这个标记
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            //移除按下标记
                            setPressed(false);
                        }
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    }
                    break;
            }
    
            return true;
        }
    
        return false;
    }
    

    通过源码可以看到:一个view的clickable决定了onTouchEvent()的返回值,如果一个view的clickable为false,down事件传递进来后,什么都不会执行,最后直接就返回false了,而且这一个Touch事件系列中的后续move,up事件都不会传递到该view,直到下一个Touch事件系列到来(一个Touch事件系列会一般经历down---->move----->up)。

    在这里还想补充:在实际开发中,view的dispatchTouchEvent()和onTouchEvent()的返回值并不是只有true或false。还有super.dispatchTouEvent()和super.onTouchEvent()。调用super.xxx就是调用父类的相关方法,父类再调用父类的,最终就是调用上面分析的相关方法,所以如果直接返回true或false,不调用super.xxx,那么Touch事件就不会通过系统默认的分发方式去分发,所以如果我们只想处理某些特殊情况,而一般情况还是需要正常分发事件,super.xxx不能去掉。

    相关文章

      网友评论

          本文标题:View的事件分发

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