View事件分发笔记

前言

难得有点时间,那就把前面学习的知识点总结一下下。

分发流程

当你动动手指头点了一下界面,点击事件就开始传递了,底层处理我们就不考虑了,首先从Activity到Window,再到我们熟悉的View。事件来到了根ViewGroup的dispatchTouchEvent方法,如果ViewGroup拦截了,那么会调用View的dispatchTouchEvent进行处理;如果 ViewGroup 不拦截了,将传递给子 View 中的 dispatchTouchEvent。以此类推,事件就这样传递下去了。如果子 View 不处理,那么 ViewGroup 就自己处理了,如果 ViewGroup 自己都不处理了,那么 Activity 的 onTouchEvent 会被调用。View 的dispatchTouchEvent方法 会处理一些监听事件优先级,如果设置了setOnTouchListener,那么onTouch会被调用,如果onTouch 返回false或者没有设置OnTouchListener,那么onTouchEvent被调用,如果设置了setOnClickListener那么onClick将会被回调。

这是简短的流程总结,当然这里有一些不足的地方,比如ViewGroup拦截不单单与onInterceptTouchEvent有关,还与事件类型,是否设置FLAG_DISALLOW_INTERCEPT标志位有关。

源码分析

#Activity

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

事件产生,传递给了当前的Activity,在上面的方法中进行了分发。交给了window的superDispatchTouchEvent方法处理,接下来看下实现类PhoneWindow。

#PhoneWindow

1828    @Override
1829    public boolean superDispatchTouchEvent(MotionEvent event) {
1830        return mDecor.superDispatchTouchEvent(event);
1831    }

mDecor就是DecorView,继续追踪。

#DecorView

439    public boolean superDispatchTouchEvent(MotionEvent event) {
440        return super.dispatchTouchEvent(event);
441    }

这里调用的是父类的dispatchTouchEvent方法,接下来看下ViewGroup的dispatchTouchEvent。

#ViewGroup --->dispatchTouchEvent

先看下拦截这部分的源码

2567            // Check for interception.
2568            final boolean intercepted;
2569            if (actionMasked == MotionEvent.ACTION_DOWN
2570                    || mFirstTouchTarget != null) {
2571                final boolean disallowIntercept = (mGroupFlags &         FLAG_DISALLOW_INTERCEPT) != 0;
2572                if (!disallowIntercept) {
2573                    intercepted = onInterceptTouchEvent(ev);
2574                    ev.setAction(action); // restore action in case it was changed
2575                } else {
2576                    intercepted = false;
2577                }
2578            } else {
2579                // There are no touch targets and this action is not an initial down
2580                // so this view group continues to intercept touches.
2581                intercepted = true;
2582            }

当事件类型为MotionEvent.ACTION_DOWN或者 mFirstTouchTarget != null时,如果没有设置FLAG_DISALLOW_INTERCEPT标志位,那么是否拦截由onInterceptTouchEvent(ev)返回值决定,如果设置了标志位,则不拦截。如果事件类型不为DOWN并且mFirstTouchTarget 为空,那么表示拦截此次事件。mFirstTouchTarget 是在什么时候不为空呢?当事件由ViewGroup的子View成功处理后,那么mFirstTouchTarget 会被赋值并且跟该子View相关联。

当ViewGroup不拦截此次事件时,ViewGroup会将事件继续分发到他的孩子。

if (!canceled && !intercepted) {
    ....
    if (newTouchTarget == null && childrenCount != 0) {
        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);
            
            //判断子View是否能够接受事件,View是否可见动画是否为空,点击事件位置是否落在View的范围内。
            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null))             {
                                ev.setTargetAccessibilityFocus(false);
                continue;
            }
            //过滤完条件后,会调用此方法,进行分发到子View
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
            
                ...
                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                alreadyDispatchedToNewTouchTarget = true;
                break;
            
            }            


        } 
        

    }
}

如果ViewGroup不拦截,满足条件!canceled && !intercepted,那么将会遍历子view,当然会进行过滤,如果子View满足条件,那么事件将分发到子View身上了,dispatchTransformedTouchEvent方法会被调用,需要注意这里有布尔类型的返回值,还有参数chlid,返回值表示是否处理此次事件,接着看一下这个方法大概做了什么。

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
    
    ...
    
    if (child == null) {
        handled = super.dispatchTouchEvent(event);
    } else {
        handled = child.dispatchTouchEvent(event);
    }
    ...

}

重点大概就是这两句代码了,我们前面传进来的child不为空,即调用的是孩子的dispatchTouchEvent方法,这样事件就到了孩子身上了,注意孩子可能也是ViewGroup。

如果子View处理了这次事件,那么mFirstTouchTarget会被赋值,看下addTouchTarget方法。

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
    final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    mFirstTouchTarget = target;
    return target;
      
}

如果子View不处理,那么会交给ViewGroup,即mFirstTouchTarget 为空,下面的代码将会被调用。

// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
    TouchTarget.ALL_POINTER_IDS);
 }

很清楚,又回到了dispatchTransformedTouchEvent方法,这次child参数传的是null,回到上面的代码,这里将调用的是super.dispatchTouchEvent(event)。如果ViewGroup不处理此次事件,那么事件将交给上级(Activity)去处理。这里就完成了一次事件的传递。从Activity到ViewGroup,再进行分发到子View。

下面再补充一些,如果事件类型为ACTION_DOWN,还会进行重置FLAG_DISALLOW_INTERCEPT标志位。

            // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

#View --->dispatchTouchEvent

接下来分析当ViewGroup调用了super.dispatchTouchEvent(event),或者是事件来到了直接继承于View的情况。这里是事件的处理过程,没有事件的分发,所以比较简单。

 public boolean dispatchTouchEvent(MotionEvent event) {
    ...
    //noinspection SimplifiableIfStatement
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnTouchListener != null
        && (mViewFlags & ENABLED_MASK) == ENABLED
        && li.mOnTouchListener.onTouch(this, event)) {
        result = true;
        }
    
    //先判断上面的result,然后在调用onTouchEvent,
    if (!result && onTouchEvent(event)) {
        result = true;

    }
    
}

由上面的代码可以看出,如果设置了OnTouchListener,那么onTouch将会被回调,如果返回结果为false,onTouchEvent才会被调用。由此可见,onTouchEvent的优先级比OnTouchListener低。

当View是DISABLED的,但是是可点击的,那么此次事件也会被消耗。

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.
    return clickable;
}

如果事件类型为ACTION_UP并且View是可点击的,performClick()会被调用。如果设置了OnClickListener那么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;
        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);

        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }

到这里,View的事件分发就大概讲完了,接下来回顾一下三个重要的方法。

public boolean dispatchTouchEvent(MotionEvent ev) 
用于事件的分发,返回结果表示是否消耗此事件,返回结果受当前super.dispatchTouchEvent和子View的dispatchTouchEvent方法影响。



public boolean onInterceptTouchEvent(MotionEvent ev)
ViewGroup中的方法,表示是否拦截此次事件。



public boolean onTouchEvent(MotionEvent event)
此方法是用于处理事件,表示是否消耗此次事件,在dispatchTouchEvent中被调用

到此,大概流程就讲完了,以此记录学习。

 

 

 

 

 

 
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值