View的事件分发

2018-04-23  本文已影响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不能去掉。

上一篇下一篇

猜你喜欢

热点阅读