您的位置:首页 > 移动开发 > Android开发

Android 事件分发机制

2015-11-05 17:47 573 查看

Android 事件分发机制

先看一下网上其他人总结的内容:

/article/4917556.html

里面的返回值只的是在重写对应函数时,直接返回false,true还是super的意思。

/article/1361183.html

这一篇介绍了事件的传递流程。

通过了以上两篇博客,可以知道Android 事件分发过程主要是和以下几个函数相关:

dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()

涉及到的对象有Activity,ViewGroup,View

对象函数
ActivitydispatchTouchEvent( MotionEvent ev )
onTouchEvent( MotionEvent ev )
ViewGroupdispatchTouchEvent( MotionEvent ev )
onInterceptTouchEvent( MotionEvent ev )
onTouchEvent( MotionEvent ev )
ViewdispatchTouchEvent( MotionEvent ev )
onTouchEvent( MotionEvent ev )
接下来通过代码来看看为什么事件的传递过程是这样的。

Activity

/**
Activity派发touch事件从这个函数开始,如果重写了这个函数,需用调用super.dispatchTouchEvent(ev)才能保证touch事件会正常的往下传递
**/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {//先调用dispatchTouchEvent让viewGroup处理事件
return true;
}
return onTouchEvent(ev);//如果ViewGroup没有处理才让Activity的onTouch
}


这里调用window.superDispatchTouchEvent(ev)最终调用的就是Android顶层viewDecorView(继承自FrameLayout)的dispatchTouchEvent(ev),其实也就是ViewGroup的dispatchTouchEvent(ev)。

ViewGroup

//去掉了一部分无关的代码
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;//这一层的ViewGroup或它的子View是否消费了touch事件

final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
//收到down事件,重置标志,因为每一个手势都是从ACTION_DOWN开始的,以ACTION_UP结束
cancelAndClearTouchTargets(ev);
resetTouchState();
}

final boolean intercepted;//是否拦截了事件
//TouchTarget用来记录处理了事件view及其他相关信息。mFirstTouchTarget
//记录了由该ViewGroup传递的且被消费了的第一个TouchTarget
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//mFirstTouchTarget != null表示如果该ViewGroup没有消费ACTION_DOWN
//事件则表示其他事件它不过心,也就不用拦截了

//这个标志可以由子view调用父ViewGroup.requestDisallowInterceptTouchEvent
//设置,表示是否允许父view拦截事件
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);  //默认返回false,当然像ListView,ScrollView会重写这个函数拦截对应事件,
ev.setAction(action);
} else {
intercepted = false;
}
} else {
//不是down事件且之前的没有消费down事件,直接拦截,其它事件不会向下传递给子View
intercepted = true;
}

TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;

//以下是向子View传递   down事件  的逻辑
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {

final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
//for循环寻找 可能 处理该事件的子View
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder ?
getChildDrawingOrder(childrenCount, i) : i;
final View child = children[childIndex];
//判断child是否可以处理该事件,不符合条件跳出for循环继续寻找
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}

//找到了子View,调用child.dispatchTouchEvent()处理事件,如果子View也是ViewGroup
//就又会回到了这个函数里面来了,然后就层层往下,一直到具体控件的dispatchTouchEvent
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//子view消费了down事件,将处理该事件的TouchTarget放到List头,list保存
//的是处理过由该ViewGroup派发的事件的TouchTarget,这个可能跟多点触控有关。
newTouchTarget = addTouchTarget(child, idBitsToAssign);//这里会为mFirstTouchTarget赋值
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
}

//非ACTION_DOWN事件直接走下面逻辑
if (mFirstTouchTarget == null) {
//如果该ViewGroup拦截了ACTION_DOWN事件就不会走上面的逻辑,或者子View都不消费ACTION_DOWN,
//mFirstTouchTarget就一直为null,以后所有的事件都在这里由自己消费
//dispatchTransformedTouchEvent的第三个参数为null,最终就会调用ViewGroup.onTouchEvent,如果是View就会调用View.dispatchTouchEvent
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
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;
//子View.dispatchTouchEvent处理
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
}
return handled;
}


//默认返回false不拦截,应该ViewGroup一般作为View的容器并不处理touch事件。
//但是想ListView,GridView这些可以滑动的容器肯定是要重装载拦截的了
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}


ViewGroup.onTouchEvent(MotionEvent ev)就是继承自View的同一个函数。

View

//很简单如果设置了onTouchListener就先调用onTouch,否则调用onTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
if (onFilterTouchEventForSecurity(event)) {
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}

if (onTouchEvent(event)) {
return true;
}
}
return false;
}


onTouchEvent是事件最终消费的地方和派发过程无关就不分析了,这里主要处理View的press状态,点击事件,长按事件等。

总结

有了以上基础再来看事件的传递路径就更好理解了



这张图是相关函数的调用顺序,这里分析了一种路径,ViewGroup那里是调用了DispatchTouchEvent,然后

DispatchTouchEvent调用了onInterceptToucheEvent.

再来看看两张事件传递过程的图片



从Activity的dispatchTouchEvent开始传递给ViewGroup的dispatchTouchEvent,如果所有的View都不拦截也不消费最后就会传递到Activity的onTouchEvent这里。



这一张在最后的View例如Button消费了ACTION_DOWN事件,上层的控件的onTouchEvent就不会收到事件。参照上面的ViewGroup的函数就是

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//子view消费了down事件
newTouchTarget = addTouchTarget(child, idBitsToAssign);//这里会为mFirstTouchTarget赋值
alreadyDispatchedToNewTouchTarget = true;
break;
}


这样内层的ViewGroup的

alreadyDispatchedToNewTouchTarget == true

mFirstTouchTarget != null

newTouchTarget != null。

这样也就不会走以下的逻辑,也就不会调用内层ViewGroup的onTouchEvent事件,同时dispatchTouchEvent会返回true

//非ACTION_DOWN事件直接走下面逻辑
if (mFirstTouchTarget == null) {
//如果该ViewGroup拦截了ACTION_DOWN事件就不会走上面的逻辑,或者子View都不消费ACTION_DOWN,
//mFirstTouchTarget就一直为null,以后所有的事件都在这里由自己消费
//dispatchTransformedTouchEvent的第三个参数为null,最终就会调用ViewGroup.onTouchEvent,如果是View就会调用View.dispatchTouchEvent
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}


而外层的ViewGroup不会调用onTouchEvent是因为,内层的ViewGroup返回true了,表示我已经消费了这个事件。这里的逻辑就和内层ViewGroup不调用onTouchEvent一样。



这一张是消费了ACTION_DOWN后,后续的事件的传递过程。

为什么这里的事件也不会被传到上层去呢?

看dispatchTouchEvent最下面的逻辑

//非ACTION_DOWN事件直接走下面逻辑
if (mFirstTouchTarget == null) {
//因为之前已经拦截了ACTION_DOWN了,所以mFirstTouchTarget != null,这里的逻辑就不会执行。
//也就是子View消费了ACTION_DOWN事件,以后所有的事件都传给子View去处理,自身的onTouchEvent不处理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}




如果在中间有一个事件被拦截了,底层的View就会收到一个ACTION_CANCEL事件

也是看最后的逻辑

final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//cancelChild 为true,所以就传给下面的控件一个ACTION_CANCEL事件
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next; //这里mFirstTouchTarget变为null,以后的事件自己处理
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: