android reforit 简书,Android事件分发精髓总结篇

本篇对自己目前所理解事件分发做一个阶段性的总结,其中是一些重要的也是必须要理解的部分,共分为三个部分。我相信耐心理解了这些片段,一定能解决事件分发的大部分问题。

一、事件分发的起点:

首先事件传到Activity的dispatchTouchEvent:

public boolean dispatchTouchEvent(MotionEvent ev) {

if (ev.getAction() == MotionEvent.ACTION_DOWN) {

onUserInteraction();

}

if (getWindow().superDispatchTouchEvent(ev)) {

return true;

}

return onTouchEvent(ev);

}

然后是getWindow()返回的superDispatchTouchEvent,这里实际上是PhoneWindow

public boolean superDispatchTouchEvent(MotionEvent event){

retuen mDecor.suerDispatchTouchEvent(event);

}

这里的mDecor就是DecorView,就是平常看到的Activity的顶级View。Activity的setContentView设置的就是DecorView的content。

且DecorView继承自FrameLayout,也就是ViewGroup,由此开始了事件由Activity传递到ViewGroup。

值得注意的是,这几个地方都是同步执行,并非异步。也就是说事件到Activity的dispatchTouchEvent后,通过

getWindow().superDispatchTouchEvent(ev)

这一句开始后面所有的分发过程,里面后续的调用流程都一样,层层同步返回,并最终对返回值做判断,如果为true,则结束分发,如果为false则事件最终传递到Activity的onTouchEvent。

二、ViewGroup对事件的拦截逻辑

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;

}

详细阐述一下:

1)当事件为ActionDown或者事件不为ActionDown但已经有子View(mFirstTouchTarget != null成立,这个变量的赋值操作会在下面讲到)要处理当前事件序列的前面的事件的时候,会判断disallowIntercept标志,

2)如果disallowIntercept为false,那么就将拦截标志intercept的值赋值为onInterceptTouchEvent方法执行的返回结果。

3)如果disallowIntercept为true,就将intercept赋值为false。

4)最后,如果当前事件不为ActionDown并且没有找到子View要处理当前事件序列(mFirstTouchTarget != null不成立),那么也将intercept赋值为false。

其实通过以上几点可以看出,这段代码最终都在操作intercept这个标志,这个标志到底有什么用呢?不要着急,它的用处在下面的代码片段,下面会说到,而且下面的代码主要逻辑都会依据这个标志。

不过这里有个问题还需要注意,

if (actionMasked == MotionEvent.ACTION_DOWN

|| mFirstTouchTarget != null)

当if中的第一个条件成立,也就是说当前事件时ActionDown的时候,

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

这个disallowIntercept 是恒为false的,因为在源码中,每当事件为ActionDown的时候,总会清除这个标志,如下:

// 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来寻找要处理事件的子View

if (!canceled && !intercepted) {

//此处忽略一些无关紧要的代码...

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

//...

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;

}

}

}

这段代码的主要作用就是遍历子View,寻找要处理当前事件的那一个。在寻找的过程中会将事件传递到子View的dipatchTouchEvent和onEvent中。下面分要点详细阐述一下:

1)可以看到此片段的第一句有个条件就是!intercepted成立。为什么要这样判断呢?很好理解:如果intercepted为false就代表ViewGroup不拦截这个事件,当然就需要遍历子View去寻找一个View来处理此事件,这也就是上面关于intercepted标志作用的解答。

2)重点方法

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {

记住,dispatchTransformedTouchEvent这个方法的返回值代表参数child是否要处理消耗这个事件。 也就说如果此次循环的child如果要消耗这个事件再执行方法体内容。这个方法内部重要逻辑如下,

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;

}

}

可以看到,这里的child参数肯定是不为null的,会执行child.dispatchTouchEvent(event)方法,这里相当于就是执行的View的dispatchTouchEvent方法了,这里已经传递到子View中去了,但还没完,下面接着看。

3)接着看View的dispatchTouchEvent方法,重要逻辑如下:

public boolean dispatchTouchEvent(MotionEvent event) {

//...

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被设置了mOnTouchListener ,那么在View可用的情况下会调用mOnTouchListener 的onTouch方法,如果onTouch方法返回true那么就不再执行后面逻辑。否则,再继续执行onTouchEvent方法。

4)接着再看View的onTouchEvent方法的主要逻辑:

public boolean onTouchEvent(MotionEvent event) {

//...

final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE

|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)

|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

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 (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {

switch (action) {

case MotionEvent.ACTION_UP:

//...

if (mPerformClick == null) {

mPerformClick = new PerformClick();

}

if (!post(mPerformClick)) {

performClickInternal();

}

}

return true;

}

return false;

}

可以看到clickable 变量在CLICKABLE和LONG_CLICKABLE两个变量任意一个为true都会为true,并且如果clickable为true,那么最后onTouchEvent方法会返回true。一句话总结就是,如果CLICKABLE和LONG_CLICKABLE任意一个为true都会消费此次事件。

同时还有一个关注点,在ActionUp的时候,会执行performClickInternal方法,最终会执行到OnClickListener的onClick方法中,这个也好理解,我们有注意到,在设置了OnClickListener方法后,在手指按下并抬起的那一刻才会监听到onClick方法。

5)到此为止,已经把从ViewGroup的dispatchTransformedTouchEvent方法调用到子View的的整个事件分发相关的方法流程走了一遍。现在回到ViewGroup的dispatchTouchEvent方法中,当dispatchTransformedTouchEvent返回true,下面的逻辑

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {

//...

newTouchTarget = addTouchTarget(child, idBitsToAssign);

alreadyDispatchedToNewTouchTarget = true;

//...

}

而addTouchTarge方法也是将一开始if中的mFirstTouchTarget 进行了赋值操作。

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {

final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);

target.next = mFirstTouchTarget;

mFirstTouchTarget = target;

return target;

}

最后再总结一下此片段。此片段功能是遍历ViewGroup的子View,并寻找要处理消费此次事件的view。并会执行如下方法流程。dispatchTransformedTouchEvent->View.dispatchTouchEvent->View.onTouch或者View.onTouchEvent(内部调用onClick)。

整个方法流都是同步的,将boolean值层层返回,表示此View是否要处理消耗掉ViewGroup传来的这个事件。

如果当次循环的子View要处理消耗这个事件,那么对mFirstTouchTarget 进行赋值。

三、ViewGroup自己处理事件

if (mFirstTouchTarget == null) {

// No touch targets so treat this as an ordinary view.

handled = dispatchTransformedTouchEvent(ev, canceled, null,

TouchTarget.ALL_POINTER_IDS);

} else {

// Dispatch to touch targets, excluding the new touch target if we already

// dispatched to it. Cancel touch targets if necessary.

TouchTarget predecessor = null;

TouchTarget target = mFirstTouchTarget;

while (target != null) {

final TouchTarget next = target.next;

if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {

handled = true;

} else {

final boolean cancelChild = resetCancelNextUpFlag(target.child)

|| intercepted;

if (dispatchTransformedTouchEvent(ev, cancelChild,

target.child, target.pointerIdBits)) {

handled = true;

}

//...

}

}

//...

return handled;

详细阐述一下:如果经过上面的遍历没有找到一个子View来处理此次事件(if (mFirstTouchTarget == null)),那么就调用dispatchTransformedTouchEvent方法,view参数传为null。这个方法刚刚在上面也调用过,还记的里面的重要逻辑是如果参数child不为null就执行child的dispatchTouchEvent,如果为null就执行ViewGroup.super.dispatchTouchEvent,其实都是执行的View.dispatchTouchEvent.只不过这里child为null的时候,执行的是ViewGrouop自己的View中的dispatchTouchEvent。

也就是说,当没有子View处理此次事件,那么ViewGroup自己执行基类(View)的dispatchTouchEvent方法。而View的dispatchTouchEvent方法我们在上面已经走过一遍流程。

到此,事件分发中的重要逻辑点已经梳理完毕。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值