View的事件分发机制源码解析

35 篇文章 1 订阅

1.点击事件的传递规则

点击事件的分发就是对MotionEvent事件的分发过程,即当一个MontionEvent产生以后,系统需要把这个事件传递给一个具体的View,传递的过程就是分发过程。
点击事件的分发由三个方法共同完成dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent。

1).public boolean dispatchTouchEvent(MotionEvent ev)

用来进行事件的分发,如果事件能够传递给当前View,此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent影响,表示是否消耗当前事件。

2).public boolean onInterceptTouchEvent(MotionEvent ev)

用来判断是否拦截某个时间,如果当前view拦截了某个事件,那么在同一个事件序列中,此方法不会被再次调用,返回结果表示是否拦截当前事件。

3).public boolean onTouchEvent(MotionEvent ev)

在dispatchTouchEvent方法中调用,用来处理点击事件,只有onInterceptTouchEvent方法返回true才会调用,返回结果表示是否消耗当前事件,如果不消耗,则在同一事件序列中,当前View无法再接收到事件。

对于一个根ViewGroup,点击事件产生后,首先会传递给它调用他的dispatchTouchEvent,如果这个ViewGroup的onInterceptTouchEvent方法返回true表示该ViewGroup要拦截当前事件,接着事件会交给这个ViewGroup处理,调用它的onTouchEvent,如果onInterceptTouchEvent返回false则表示它不拦截当前事件,则该事件会传递给它的子元素,并且调用子元素的dispatchTouchEvent开始下一轮分发流程。

如果一个View需要处理事件时,如果设置了onTouchListener,则会回调onTouchListener中的ouTouch方法。如果onTouch返回false,则当前view的onTouchEvent会被调用,如果返回true,则onTouchEvent不会被调用。所以onTouchListener的优先级比onTouchEvent高,如果执行了onTouchEvent,如果设置有onClickListener 那么onClick方法会被调用,所以onClickListener优先级最低,处于事件传递的尾端。

点击事件传递过程:Activity–>Window–>View ,事件总是先传给Activity,再传给Windows,最后Windows再传给顶层View,顶层View接到事件后,就会按照事件分发机制分发事件。如果一个View的onTouchEvent返回false,就会调用它的父容器的onTouchEvent,以此类推,如果所有元素都不处理这个事件,那么该事件最终会传递给Activity处理,会调用Activity的onTouchEvent。

结论:
(1)同一事件序列指手指接触屏幕的那一刻起,到手指离开屏幕的那一刻所产生的一系列事件,以down事件开始,中间含有不定量的move时间,最终以up时间结束。

(2)正常情况下,一个事件序列只能被一个View拦截且消耗,一旦一个元素拦截了某事件,那么同一事件序列内的所有事件都会交由该元素处理。通过特殊手段可以将同一事件序列中的时间交给不同的view同时处理,比如通过一个view的onTouchEvent将时间传递给其他view处理。

(3)某个View一旦决定拦截,那么这个事件序列都只能由它来处理,并且它的onInterceptTouchEvent不会再被调用。当一个View决定拦截一个事件后,系统会把一个事件序列内的其他方法都直接交给他来处理,因此就不会在调用onInterceptTouchEvent来询问他是否要拦截。

(4)如果某个view通onTouchEvent开始处理事件,但是他不消耗ACTION_DOWN事件,即onTouchEvent返回了false,那么该view就不会再接收到事件,并且事件将重新交给他的父元素去处理,即调用父元素的onTouchEvent。即事件一旦交给一个View处理,他就必须消耗掉,否则同一时间序列中剩下的事件就不会再交给他处理。

(5)如果view只消耗ACTION_DOWN事件,那么这个点击事件会消失,并且父元素的onTouchEvent并不会被调用(事件的开始ACTION_DOWN已被消耗),并且当前view可持续受到后续的时间,最终这些消失的点击事件会传递给Activity处理。

(6)ViewGroup默认不拦截任何事件,源码中的ViewGroup的onInterceptTouchEvent方法默认返回false

(7)View没有onInterceptTouchEvent,一旦有点击事件传递给它,他的onTouchEvent就会被调用。

(8)View的onTouchEvent默认都会消耗事件(即返回true),除非他是不可点击的(clickable和longClickable同是为false),view的longClickable默认为false,clickable则要分情况。

(9)View的enable属性不影响onTouchEvent的默认返回值,就算一个view是disable状态的,只要他的clickable和longClickable有一个为true,则onTouchEvent就返回true。

(10)onClick会发生的前提是当前view是可以点击的,并且既收到了down又收到了up事件。

(11)事件的传递过程是由外到内传递的,事件总是先传递给父元素,然后在由父元素分发给子View,通过requestDisallowInterceptTouchEvent可以在子元素中干预父元素的事件分发(改变FLAG_DISALLOW_INTERCEPT的值,但是当事件类型是ACTION_DOWN时,总会重置该值,所以当ACTION_DOWN传递来时,总是会询问是否拦截),但是不能干预ACTION_DOWN。

2.事件分发源码分析

1)Activity对点击事件的分发

当一个点击操作发生时,事件最先传递给当前activity,由activity的dispatchTouchEvent来进行分发,具体工作有Activity内部的Windows完成。Windows会将事件传递给decor view,decor view一般就是当前界面的底层容器(即setContentView所设置的view的父容器),通过Activity.getWindows.getDecorView()可以获得。

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();   //空方法,若当前activity位于栈顶,且收到ACTION_DOWN事件时触屏点击按home,back,menu键等都会触发此方法。下拉statubar、旋转屏幕、锁屏不会触发此方法。
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;                 //调用windows的dispatchTouchEvent 如果windows的方法返回了true则说明有子View消耗了事件序列,则循环结束
    }
    return onTouchEvent(ev); //如果windows的dispatch返回false,则所有View的onTouchEvent都返回了false,则会调用activity的onTouchEvent
}

接下来看Window如何传递给ViewGroup,由源码可知,window是一个抽象类,window的superDispatchTouchEvent也是个抽象方法,PhoneWindow是Window的唯一实现类。

PhoneWindow中的superDispatchTouchEvent()源码

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
//mDecor是DecorView的实例
//DecorView是视图的顶层view,继承自FrameLayout,是所有界面的父类
}

DecorView继承自FrameLayout,所以实际上事件传递到了ViewGroup,执行了ViewGroup的dispatchTouchEvent,到这里事件已经传递到顶级View了,即在activity
setContentView所设置的View。顶级View一般来说都是ViewGroup。

2)顶级View对点击事件的分发过程

顶级View的分发流程:事件到达顶级View以后,会调用ViewGroup的dispatchTouchEvent,如果顶级ViewGroup拦截事件(ViewGroup的onInterceptTouchEvent默认为返回false,所以默认情况下不会拦截事件),即onInterceptTouchEvent返回true,则事件由ViewGroup处理,这是如果ViewGroup设置了onTouchlistener则会调用onTouch,onTouch返回true则不会调用onTouchEvent,如果返回false则调用onTouchEvent,即onTouchListener的优先级比onTouchEvent高。如果顶层ViewGroup不拦截事件,事件会传递给他所在点击事件链上的子View,调用子View的dispatchTouchEvent,接下来则循环顶层ViewGroup的分发流程。

// 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);
//当ACTION_DOWN事件到来时,会调用resetTouchState()对FLAG_DISALLOW_INTERCEPT进行重置
//因此子View调用disallow方法并不能影响ViewGroup对ACTION_DOWN的处理
resetTouchState();    
}
// Check for interception.
final boolean intercepted;
//通过这个if结构判断是否要进行拦截,当事件类型为ACTION_DOWN或mFirstTouchTarget  != null时会进入该if分支进行判断是否拦截。
//mFirstTouchTarget将会在ViewGroup的子元素成功处理事件时赋值并指向该子元素。所以一旦ViewGroup拦截事件,mFirstTouchTarget的值就
//为空,接下来的事件就不会进入if分支调用onInterceptTouchEvent。
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
 //是否禁止拦截事件的功能,默认不禁止,false。当事件类型为ACTION_DOWN时,就会充值这个标记位,所以当ACTION_DOWN事件传递过来时
//ViewGroup总会询问自己是否要拦截事件。
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的子元素成功处理时,mFirstTouchTarget会被赋值并指向该元素,所以如果ViewGroup不拦截事件并将事件传递给子元素处理时就会令mFirstTouchTarget != null成立。那么如果ViewGroup拦截事件,mFirstTouchTarget == null,当ACTION_MOVE和ACTION_UP事件到来时,if结构中(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)为false,就会导致intercepted置为true,且不会再调用onInterceptTouchEvent,同一事件序列的其他事件都会默认交给他处理。
FLAG_DISALLOW_INTERCEPT标记位是通过requestDisallowInterceptTouchEvent方法来设置的,一般用于子View中,表示是否禁用事件拦截的功能,默认为false(默认不禁用),一旦设置了之后,ViewGroup将无法拦截除了ACTION_DOWN以外的点击事件,因为ViewGroup在分发事件时,如果事件类型是ACTION_DOWN就会重置FLAG_DISALLOW_INTERCEPT,导致子View中设置的标记位无效。因此,当事件类型是ACTION_DOWN时,ViewGroup总会调用自己的onInterceptTouchEvent来询问自己是否拦截事件。

ViewGroup不拦截事件时,事件向下分发给子View的源码

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 there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
    if (childWithAccessibilityFocus != child) {
        continue;
    }
    childWithAccessibilityFocus = null;
    i = childrenCount - 1;
}

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)) {            //调用子View的dispatchTouchEvent
    // 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();
//如果子元素的dispatchTouchEvent返回了true,就会通过addTouchTarget给mFirstTouchTarget赋值并跳出遍历子View 的循环。如果
//子元素的dispatchTouchEvent返回了false,ViewGroup就会把事件分发给下一个满足条件的子元素。
    newTouchTarget = addTouchTarget(child, idBitsToAssign);
    alreadyDispatchedToNewTouchTarget = true;                
    break;
}

dispatchTransformedTouchEvent部分源码:

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);  //传递过来的child不为null,所以执行了child的dispatchTouchEvent。
}
event.setAction(oldAction);
return handled;
}

addTouchTarget()源码:给mFirstTouchTarget赋值

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

首先遍历ViewGroup的所有子元素,然后判断子元素是否能够接受到事件,判断条件:1。子元素是否在播动画2。点击事件的坐标是否落在子元素区内。如果子元素满足条件,事件就会传递给他处理。dispatchTransformedTouchEvent实际上就是调用了子元素的dispatchTouchEvent方法,这样事件就会交给满足条件的子元素处理了。如果子元素的dispatchTouchEvent返回了true,就会通过addTouchTarget给mFirstTouchTarget赋值并跳出遍历子View 的循环。如果子元素的dispatchTouchEvent返回了false,ViewGroup就会把事件分发给下一个满足条件的子元素。

两种情况下会发生遍历所有子元素后事件都没有被处理:1.ViewGroup没有子元素。2.子元素处理了点击事件,但是在dispatchTouchEvent中返回了false。这时,ViewGroup就会自己处理点击事件。源码如下,同样调用了dispatchTransformedTouchEvent
但是第三个参数child传了null,所以会调用super.dispatchTouchEvent(event),ViewGroup的父类是View,所以会调用View的dispatch,即点击事件会交给View来处理。

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

3)View对点击事件的处理

首先会判断有没有设置onTouchListener,如果onTouchListener中的onTouch返回true,那么onTouchEvent就不会被调用,所以ouTouchListener优先级高于onTouchEvent,

public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
    // We don't have focus or no virtual descendant has it, do not handle the event.
    if (!isAccessibilityFocusedViewOrHost()) {
        return false;
    }
    // We have focus and got the event, then use normal event dispatch.
    event.setTargetAccessibilityFocus(false);
}

boolean result = false;

.   .   .   .   .   .

if (onFilterTouchEventForSecurity(event)) {
    if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
        result = true;
    }
    //noinspection SimplifiableIfStatement
    ListenerInfo li = mListenerInfo;

//检查当前view是否设置了onTouchListener,如果设置了并且onTouch中返回了true,就会将result置true,下面的if判断就不会成
//立,就不会调用onTouchEvent。
    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;
}

onTouchEvent部分源码:

final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

//当View处于不可用状态下点击事件的处理过程,不可用状态下View照样会消耗点击事件。
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;
}
if (mTouchDelegate != null) {
    if (mTouchDelegate.onTouchEvent(event)) {
    return true;
    }
}

onTouchEvent对点击事件的具体处理:

final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//只要View的CLICKABLE 和 LONG_CLICKABLE有一个为true,clickable就是true,if条件就成立,则onTouchEvent就会返回true
//那么该View就会消耗该事件,
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
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;
}
    ...
    return true;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值