Android----View事件分发机制(二)

版权声明:本文为博主原创文章,转载请注明原帖地址,谢谢 https://blog.csdn.net/AooMiao/article/details/70158977
上一章主要说了View分发的简单流程,下面通过源码来看清楚分发机制

科普:当一个点击事件发生后,最开始的传递过程是这样的:Activity–Window–顶级View,当顶级View收到事件后,就会按分发机制把事件分发下去

当事件传递到顶级View(我们在Activity通过setContentView那个,是个ViewGroup来的),调用dispatchTouchEvent方法(拦截部分和分发事件部分)

拦截部分

            // 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;
            }

可以看到有个拦截属性intercepted,有两种情况要判断(有一个为true就调用onInterceptTouchEvent询问自己是否要拦截)

  • actionMasked == MotionEvent.ACTION_DOWN:当前点击事件为按下时,ViewGroup总是会问自己是否要拦截事件(调用onInterceptTouchEvent方法,默认不拦截)
  • mFirstTouchTarget != null:如果不拦截Down事件,在分发过程中,若有子View成功消费Down事件,则mFirstTouchTarget 指向那个子View

结论:若上面两个条件都为false,即在一个事件序列中ViewGroup拦截Down事件或者没有子View想要处理Down事件,则后面的Move,Up事件来到ViewGroup都会直接跳过onInterceptTouchEvent方法(既然子View都不要down事件了,更何况Move,Up事件),把intercepted 赋值为true

特殊情况:子View可以通过requestDisallowInterceptTouchEvent(boolean)方法来设置父ViewGroup无法拦截出了down以外的其它事件(设置父类标志位FLAG_DISALLOW_INTERCEPT实现),因为ViewGroup在Down事件来到时会对FLAG_DISALLOW_INTERCEPT进行重置,所以父类肯定能收到Down事件

事件分发部分(前提是ViewGroup不拦截):

    final View[] children = mChildren;
    for(int i = childrenCount - 1;i >=0;i--){
        final int childIndex = getAndVerifyPreorderedIndex(
                childrenCount, i, customOrder);
        final View child = getAndVerifyPreorderedView(
                preorderedList, children, childIndex);
        if (!canViewReceivePointerEvents(child)
                || !isTransformedTouchPointInView(x, y, child, null)) {
            ev.setTargetAccessibilityFocus(false);
            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();
            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;
        }
    }

遍历ViewGroup可以接受点击事件的子View(子View是否在播放动画和点击事件是否落在子View区域上),调用它们的dispatchTouchEvent,若有一个子View的dispatchTouchEvent返回true,则ViewGroup的mFirstTouchTarget 指向该View,跳出遍历,后面的点击事件都交给该子View处理

当事件传递给View时,调用dispatchTouchEvent方法

    public boolean dispatchTouchEvent(MotionEvent event) {
        boolean result = false;
        ......
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //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;
            }
        }
        ......
        return result;
    }
View的dispatchTouchEvent比较简单,首先会检查是否设置了OnTouchListener
  • 有的话就去调用onTouch方法,如果onTouch返回true,则给result赋值true,导致它的onTouchEvent方法执行不了,所以onTouch方法优先级比onTouchEvent方法高,如果onTouch返回false,则继续执行onTouchEvent
  • 没有的话就去执行onTouchEvent方法
    下面是View的部分onTouchEvent方法:
......
    if(((viewFlags &CLICKABLE)==CLICKABLE ||
            (viewFlags &LONG_CLICKABLE)==LONG_CLICKABLE)||
            (viewFlags &CONTEXT_CLICKABLE)==CONTEXT_CLICKABLE){
        switch (action) {
            case MotionEvent.ACTION_UP:
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    ......
                    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
                        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();
                            }
                        }
                    }
                ......
                break;
            case MotionEvent.ACTION_DOWN:
                ......
                break;
            case MotionEvent.ACTION_CANCEL:
                ......
                break;
            case MotionEvent.ACTION_MOVE:
                ......
                break;
        }
        return true;
    }
......
解析:主要有两点
  • 最上面的判断语句中如果View的CLICKABLE或LONG_CLICKABLE中有一个为true的话,则onTouchEvent方法就会返回true,消费事件,注意所有view的LONG_CLICKABLE默认为false,CLICKABLE的话看具体的控件,button默认为true,textview默认为false
  • 在UP事件中,会调用performClick(),如果view设置了OnClickListener,那么在performClick()里面会调用onClick方法

perfirnClick()方法如下:

    public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

到此View的事件分发机制就分析结束了,在这里总结一下:

  1. 当点击事件发生传递到顶级View时(根ViewGroup),调用它的dispatchTouchEvent方法,然后在调用拦截方法询问是否拦截
    1. 拦截,调用自己onTouchEvent,后面的同一序列事件来到时,不在调用拦截方法,直接调用自己onTouchEvent方法
    2. 不拦截,遍历子View看谁需要处理事件,调用子View的dispatchTouchEvent方法(看2)
  2. 调用View的dispatchTouchEvent方法时
    1. 如果设置onTouch方法,则先调用onTouch,若返回true,事件消费,onTouchEvent无法被执行;若返回false,onTouchEvent执行
    2. 如果没有设置onTouch方法,则调用onTouchEvent,返回true则消费,后面其它同一序列事件都给这个View处理,若返回false则让父ViewGroup继续遍历其它子View
  3. 调用onTouchEvent方法时:
    • 如果该View的CLICKABLE或LONG_CLICKABLE中有一个为true的话,则返回true消费事件
    • 如果有设置OnClickListener的话,会在performClick()中调用该View的onClick()

这两篇关于View的事件分发机制部分出自书籍《Android开发艺术探索》,目前我在看本书看的不能自拔,这里也建议大家感兴趣的话可以去看一看。最后谢谢大家观看,希望大家看到这篇文章都能有收获

猜你喜欢

转载自blog.csdn.net/AooMiao/article/details/70158977