美文网首页
Android事件分发过程(笔记)

Android事件分发过程(笔记)

作者: 红鲤鱼与绿鲤鱼与驴与鱼 | 来源:发表于2022-04-12 16:47 被阅读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

    相关文章

      网友评论

          本文标题:Android事件分发过程(笔记)

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