Android事件分发过程(笔记)

2022-04-12  本文已影响0人  红鲤鱼与绿鲤鱼与驴与鱼

之前都是在网上看别人的文章,很容易忘掉,今天重新翻一下源码并简单记录一下
先简单看一下dispatchTouchEventonInterceptTouchEventonTouchEvent这三个方法在Activity、ViewGroup、View中是怎么个流程。如下图(有些地方可能画的不太准确)

事件分发流程.png

源码

根据上图我们再梳理一下源码就容易很多了。

Activity事件传递

先从 ActivitydispatchTouchEvent看起

    public boolean dispatchTouchEvent(MotionEvent ev) {
        //判断是否是   MotionEvent.ACTION_DOWN  事件
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            //这是一个空的方法 
            onUserInteraction();
        }
        //调用到了 Window 中的  superDispatchTouchEvent
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

上面的getWindow().superDispatchTouchEvent(ev)句话调用了抽象类Window 中的方法, PhoneWindowWindow 的唯一子类,所以我们看一下 子类PhoneWindow中的superDispatchTouchEvent方法

    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

ViewGroup事件分发

上面这个方法调用到了 DecorView 中的方法,而他是继承了 FrameLayout , FrameLayout又继承了ViewGroup,这里调用到了 ViewGroupdispatchTouchEvent方法

    //DecorView
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
    //ViewGroup
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        // If the event targets the accessibility focused view and this is it, start
        // normal event dispatch. Maybe a descendant is what will handle the click.
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }
        //该方法的最终返回值
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {//过滤触摸事件以确保安全
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.   处理 ACTION_DOWN 事件
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                //在开始一个新的Touch之前先清空所有的状态
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // Check for interception.//  onInterceptTouchEvent 方法的结果
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                //禁用拦截
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    //调用 onInterceptTouchEvent()方法,方法返回false,
                    intercepted = onInterceptTouchEvent(ev);//该方法决定是否拦截事件
                    //恢复Action 防止他被改变
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }
            ...
        //默认情况下canceled 和  intercepted 为false
        if (!canceled && !intercepted) {
            ...
          //判断是否是ACTION_DOWN 事件,如果是那么表示这是一个系列事件的开始
          if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            ...
            
            //这个方法是对事件进行分发(分发给子View)
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                // Child wants to receive touch within its bounds.
                mLastTouchDownTime = ev.getDownTime();
                if (preorderedList != null) {
                    // childIndex points into presorted list, find original index
                    for (int j = 0; j < childrenCount; j++) {
                        if (children[childIndex] == mChildren[j]) {
                            mLastTouchDownIndex = j;
                            break;
                        }
                    }
                } else {
                    mLastTouchDownIndex = childIndex;
                }
                mLastTouchDownX = ev.getX();
                mLastTouchDownY = ev.getY();
                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                alreadyDispatchedToNewTouchTarget = true;
                break;
            }
         }     
        }
            if (mFirstTouchTarget == null) {
                //如果没有子View消费事件,这里最终会调用ViewGroup的onTouchEvent()
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
              ...
          }

        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }

ViewGroup中没有自己的onTouchEvent方法,ViewGroup是继承在View的,最终是通过dispatchTransformedTouchEvent方法将事件分发到View进行事件消费的。接下来看下这个方法是如何进行事件分发的

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        ...
        
        if (child == null) {
            //如果 子View 为 null时说明事件不需要继续往下分发,调用本身父类(ViewGroup的父类是View)的dispatchTouchEvent,
            //也就是说调用了View类中的dispatchTouchEvent方法
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            ...
            //如果子View不为null,则调用 子View的 dispatchTouchEvent 方法
            handled = child.dispatchTouchEvent(transformedEvent);
        }
        ...
         // Done.
        transformedEvent.recycle();
        return handled;
    }

上面调用到View类中的 dispatchTouchEvent 方法,事件就是从这个方法中消费的。
对于上面 super.dispatchTouchEventchild.dispatchTouchEvent(transformedEvent); 这两个要分清,第一个是 ViewGroup 父类中的方法,第二个是 子View(比如 TextView、Button等) 的方法,这两个都是为了去调用 onTouchEvent 方法消费事件
从Activity——>ViewGroup到这算是结束了,接下来就是View中消费事件

View消费事件

    public boolean dispatchTouchEvent(MotionEvent event) {
        ...
        boolean result = false;
         ...
        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }

        if (onFilterTouchEventForSecurity(event)) {
            //按钮是不是Enable的
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //ListenerInfo  监听的合集,OnFocusChangeListener、OnScrollChangeListener、OnClickListener等等
           //如果他不为null说明我们设置了监听
            //判断是否设置了onTouchListener 监听
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {//这里会执行OnTouchListener 的 onTouch 方法
                result = true;
            }
            //如果设置了onTouchListener的监听并且返回为true时就不会调用onTouchEvent方法
            //如果上面没有处理就会调用onTouchEvent
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
         ...
        return result;
    }

上面这个方法可以看出onTouchListeneronTouchEvent 的优先级;onTouchListener—>onTouchEvent
还有一点 就onTouchListeneronTouchEvent这两个方法而言,如果没有 setOnTouchListener那么一定会执行onTouchEvent方法,如果设置了setOnTouchListener那就要看onTouch的返回值了
那么我们的setOnClickListener是什么时候调用的呢,接下来看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();
        //是否可以点击
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    ...
                        //mHasPerformedLongPress是否已经执行了长按事件,true已经执行了,false未执行
                        // 还未执行长按事件时才能进行
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            //先移除还未被执行的长按事件
                            removeLongPressCallback();
                            // 只有在我们处于按下状态时才执行点击操作
                            if (!focusTaken) {
                                 //使用Runnable 处理这个点击事件,而不是直接执行点击。目的就是在单击操作开始之前可以更新View的状态
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                     //真正执行Click的方法,内部调用了performClick这个方法
                                    performClickInternal();
                                }
                            }
                        }

                    break;

                case MotionEvent.ACTION_DOWN:
                    ...
                    if (isInScrollingContainer) {
                      ...
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        ...
                          //长按事件,是一个延迟的消息,
                          //刚调用checkForLongClick方法时 mHasPerformedLongPress=false,
                          //当长按事件的延迟消息被执行后 mHasPerformedLongPress = true
                        checkForLongClick(
                                ViewConfiguration.getLongPressTimeout(),
                                x,
                                y,
                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                    }
                    break;
                case MotionEvent.ACTION_CANCEL:
                    ...省略代码
                    break;
                case MotionEvent.ACTION_MOVE:
                    ...省略代码
                    break;
              ... 省略代码 
        }

最后看一下performClick这个方法,这个方法中还是使用LinstenerInfo 判断是否设置了 onClickListener监听,如果用户手动设置了setOnclickListener(), 就调用 onClick

    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;
        //ListenerInfo 各种监听的合集
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            //执行用户设置的 onClickListener  中的 onClick方法
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }

到这整个流程就算是走完了,onTouchListeneronTouchEventonClick 的优先级也比较清楚了
onTouchListener—>onTouchEvent—>onClick

下面贴出事件分发的注意事项:

事件分发注意的问题.png
上一篇下一篇

猜你喜欢

热点阅读