Android高级UI之事件传递之2(源码分析)

在这里插入图片描述
1.Activity对点击事件的分发过程

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

首先事件开始交给Activity所附属的Window进行分发,如果返回true,整个事件循环就结束了,返回false意味着事件没人处理,所有View的onTouchEvent都返回了false,那么Activity的onTouchEvent就会被调用。
由于Window类是个抽象类,故需要一个子类来实现superDispatchTouchEvent()方法,PhoneWindow类是Window类的唯一实现类,PhoneWindow类的superDispatchTouchEvent方法实现如下:

public void superDispatchTouchEvent(MotionEvent event){
   return mDecor.superDispatchTouchEvent(event)
}

PhoneWindow将事件直接传递给了DecorView(根视图/顶级视图),因此事件最终由Activity传递到了View里面

2.根视图(DecorView)点击事件的分发过程
结论
点击事件达到根视图(一般是一个ViewGroup)以后,会调用ViewGroup的dispatchTouchEvent方法,然后逻辑是这样的:如果根视图拦截事件(onInterceptTouchEvent)返回true,则事件由顶级ViewGroup处理,这时如果ViewGroup的mOnTouchListener被设置,则onTouch会被调用,否则onTouchEvent会被调用。在onTouchEvent中,如果设置了mOnCLickListener,则onClick会被调用。如果顶级ViewGroup不拦截事件,则事件会传递给它所在的点击事件链上的子View,这时子View的dispatchTouchEvent会被调用。到此为止,事件已经从顶级ViewGroup传递给了下一层View,接下来的传递过程和顶级ViewGroup是一致的,如此循环,完成整个事件的分发。

分析
首先看ViewGroup对点击事件的分发过程,其主要实现在ViewGroup的dispatchTouchEvent方法中。
方法比较长,分段说明。先看下面这一段(描述当前View是否拦截点击事件这个逻辑):

 // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    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;
            }

上述代码的意思是:ViewGroup在点击事件类型是ACTION_DOWN或者mFirstTouchTarget!=null(简单的讲,如果事件由ViewGroup的子元素处理,那么mFirstTouchTarget会被赋值指向这个子元素,如果事件是被ViewGroup自己处理,那么mFirstTouchTarget就永远为null)的时候有可能(为什么说有可能,因为还要根据标志位FLAG_DISALLOW_INTERCEPT共同来确定)会调用onInterceptTouchEvent方法,如果点击事件类型不是ACTION_DOWN并且mFirstTouchTarget不为空,那么ViewGroup的onInterceptTouchEvent方法不被调用,意味着同一事件序列中的其他事件都会默认交给它处理。
FLAG_DISALLOW_INTERCEPT标志位,一般是子View通过requestDisallowInterceptTouchEvent方法来设置的,简单的来讲,就是如果子View设置了这个标志位会导致上述代码中的disallowIntercept变量为true,即ViewGroup的onInterceptTouchEvent方法无法被调用。当然前提是拦截的事件类型不是ACTION_DOWN,如果是ACTION_DOWN类型,会调用resetTouchState方法重置这个标志位,这将导致子View中设置的这个标志位无效。这也就意味着当面对ACTION_DOWN事件时,ViewGroup总是会调用自己的onInterceptTouchEvent方法来询问自己是否要拦截事件。
接下来再看当ViewGroup不拦截事件的时候,事件会向下分发交由它的子View进行处理,源码如下:

 // Find a child that can receive the event.
                        // Scan children from front to back.
                        final View[] children = mChildren;

                        final boolean customOrder = isChildrenDrawingOrderEnabled();
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = customOrder ?
                                    getChildDrawingOrder(childrenCount, i) : i;
                            final View child = children[childIndex];
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                continue;
                            }

                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                // Child is already receiving touch within its bounds.
                                // Give it the new pointer in addition to the ones it is handling.
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                mLastTouchDownIndex = childIndex;
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                        }
     
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

首先,ViewGroup会遍历其内部的整个子View,然后把每次遍历得到的子view赋值给变量child,接着在dispatchTransformedTouchEvent方法中根据child是否是null来判断是将viewgroup的dispatchTouchEvent方法赋值给handled还是将该子view的dispatchTouchEvent方法赋值给handled。如果遍历所有的子元素后事件都没有被合适地处理(即dispatchTransformedTouchEvent方法永远返回false,这意味着没有子view消耗该事件),那么ViewGroup会自己处理点击事件。

3.View对点击事件地处理过程

 if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

从源码可知View对点击事件地处理过程,首先会判断有没有设置OnTouchListener,如果OnTouchListener中地的onTouch方法返回true,那么OnToucEvent就不会被调用。
接着分析onTouchEvent的实现。先看当View处于不可用状态下点击事件的处理过程,如下所示。不可用状态下的View照样会消耗点击事件。

if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
        }

从onTouchEvent接下来的源码中可知,只要CLICKABLE、LONG_CLICKABKE和CONTEXT_CLICKABLE有一个为true,那么就会消耗这个事件,即onTouchEvent方法返回true。当ACTION_UP事件发生时,会触发performClick方法,如果View设置了OnClickListener,那么performClick方法内部就会调用onClick方法。(由于源码太长就不贴出来了)

发布了61 篇原创文章 · 获赞 0 · 访问量 889

猜你喜欢

转载自blog.csdn.net/qq_36828822/article/details/103658941
今日推荐