观Android事件分发源码有感
前言
已经在工作android码农朋友一定都知道一件事,那么就是到一个公司无论是作为吉祥物还是作为公司的android架构师,面试的时候逃不掉的一个问题就是“Android 的事件分发机制”。并且在实际的抄袭网上代码的过程中,我们经常也会遇到各种事件冲突的问题。一般我们都是网上搜索一番,cv一些不知所云的代码进入工程了事。
但是恰好今日有空,我们不妨来细究一下Android的源码。看看这些所谓的事件到底是怎么回事。
MotionEvent.ACTION_DOWN
事件分发
通过一个日常的经验,我们可以发现,无论是点击事件,还是移动事件。所有事件的开端都是从用户按下开始的,所以我们现在来研究一下ACTION_DOWN事件。
Activity
在看源码之前,我想请大家接受一个不太正确的观念,那么就是用户在点击了手机屏幕以后,事件分发是从Activity的dispatchTouchEvent方法开始的。因为只有找到一个源头以后,我们才方便开始源码之旅。
好了接下来我们就看一下dispatchTouchEvent方法的源码:
//点击事件的分发
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//这里默认是一个空方法,如果码农有需要,可以自己去复写这个方法,
实现对于用户的按下屏幕事件的监听
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
//点击事件的消费
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
上面的代码,就是activity中关于事件分发和消费的所以代码,是不是非常简单?通过观察上面的代码,我们很容易就是找到函数中的关键代码就是
getWindow().superDispatchTouchEvent(ev)
根据我们的猜测,这句话应该就是实现了activity事件向布局的传递。那么这个getWindow又是什么玩意。我们接着往下看。
public Window getWindow() {
return mWindow;
}
原来是获取Activity所持有的一个Window对象,那么这个window又是何方神圣?经过我的一分寻找,发现了它被赋值的地方。
final void attach(
//省略具体参数
) {
//省略无关代码
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
//省略无关代码
}
原来这里的window是一个PhoneWindow的实现。如果大家看过activity和window以及DecorView的关系的话,一定能很快明白过来。这里我就不细讲了,有兴趣的小伙伴可以看下这篇博文
Activity 与 Window、PhoneWindow、DecorView 之间的关系详解
其实上面的整篇文章讲的就是一张图片。来上图!
从上图可以看出,我们的整个Activity就是持有了一个PhoneWindow,然后PhoneWindow持有了一个DecorView,最后DecorView持有了我们在setcontentView中塞进去的布局。
好了有了以上的知识,我们就可以转移阵地了,我们去看看PhoneWindow中收到消息究竟做了啥
PhoneWindow和DecorView
接下来,我们看看在Activity调用了getWindow().superDispatchTouchEvent(ev)之后的故事。
private DecorView mDecor;
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
好嘛!这个PhoneWindow也太会偷懒了,收到了事件以后,直接就将足球踢给了DecorView。那我们接着转移阵地。
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
//省略无关代码...
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
//省略无关代码...
}
这个DecorView不愧是PhoneWindow的好基友,对于事件的处理都是一脉相承,直接调用了父类的事件处理分发方法。而DecorView的父类就是大名鼎鼎的FrameLayout。所以,我们进入下一个章节。
ViewGroup
我们一路跟踪用户的点击事件,最终到了DecorView这一步,把锅甩给了我们熟悉的ViewGroup。ok!上代码!
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//省略无关代码。。。。
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction(); //获取事件类型
final int actionMasked = action & MotionEvent.ACTION_MASK; //多点触控相关
//省略无关代码。。。。
// 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;
}
}
//省略无关代码。。。。
return handled;
}
viewGroup中的dispatchTouchEvent方法很长,我简化了一下。通过观察上面的代码片段,发现event事件被onInterceptTouchEvent方法去处理。但是在调用这个方法前,有两个判断会影响onInterceptTouchEvent去处理event事件。首先是
if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
这句话,第一个条件很容易理解,就是必须是DOWN事件。而第二个条件mFirstTouchTarget,我们这里先留个悬念,后面会遇到的他。
第二个语句就是
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
这里的话mGroupFlags & FLAG_DISALLOW_INTERCEPT 是什么玩意,我们在viewgourp中全局搜索一下,发现了以下方法可以设置mGroupFlags & FLAG_DISALLOW_INTERCEPT 。
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
这其实就是viewgourp开发给其他地方的一个拦截标志位的设置,调用方可以选择是否拦截event事件。
不过除了requestDisallowInterceptTouchEvent可以操作该标志位,我们还发现了一个私有方法
/**
* Resets all touch state in preparation for a new cycle.
*/
private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
那么这方法在什么时候调用呢?一共有两处,其中一处很凑巧,就是在前面的dispatchTouchEvent方法中
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
看了上面代码, 我们会发现在MotionEvent.ACTION_UP和MotionEvent.ACTION_HOVER_MOVE的时候,会被重置,而MotionEvent.ACTION_HOVER_MOVE是鼠标相关的方法我们不用关注。所以也就是说,用户手机离开屏幕的时候mGroupFlags & FLAG_DISALLOW_INTERCEPT会被重置。
而且如果你现在就在对照源码看的话,你还会发现,其实在之前Viewgroup中的dispatchTouchEvent方法中,检测到如果event事件为Down的话,也会调用resetTouchState方法。所以mGroupFlags标志位,只要在用户已经触发点击事情之后,Up事件之前改变才有效果。
啊哦,好像扯的有点远了,我们接着回归主流程。继续观察ViewGroup.dispatchTouchEvent中的语句 intercepted = onInterceptTouchEvent(ev);
可以见到,事件被分发到了oninterceptTouchEvent方法。那我们接下来便来观察这个方法的源码。
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
可以见到这个方法,对于我们来说并没有什么实际的内容。重点则是应该放在这个方法的返回值方面。在ViewGroup.dispatchTouchEvent方法中onInterceptTouchEvent返回值被保存到了变量intercepted方法中。而我们在ViewGroup.dispatchTouchEvent中发现了下面的源码
if (!canceled && !intercepted) {
//ACTION_DOWN 为屏幕第一次用手指按下,ACTION_POINTER_DOWN为多指触控时,
//在已经有一个手指没有释放屏幕的时候,再次用另外一个手指按下
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount; //获取子控件的数量
if (newTouchTarget == null && childrenCount != 0) {
//省略若干代码
for (int i = childrenCount - 1; i >= 0; i--) {
//省略若干代码
//获取子元素的target。如果已经在链表中存储过,则直接返回,否则返回null。主要多指的时候多同一个view多次点击
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.
//如已经子元素已经在链表中查找到,则不在遍历viewgroup的子元素,直接跳出,并更改pointerIdBits。
//此处pointerIdBits,可以用来更改分发到事件的类型。例如如果两个view重叠到一起,第一次down事件被下面的view消费,
//第二次down事件的时候被上面的view消费。这次的down事件分发到下面的view的时候就会变成move事件。
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { //将event时间向下级的view分发
// 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;
}
//省略若干代码
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
//省略若干代码
}
}
在这个方法里面我们终于发现了事件向下方法的相关代码就是dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)这句话。而这条语句之前的前提是intercepted为false,也就是onInterceptTouchEvent方法返回值为flase,也就是不会被拦截。所以我们可以通过复写onInterceptTouchEvent方法来实现对于event事件的拦截。
那么接下来我们趁热打铁来看一下dispatchTransformedTouchEvent的源码
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
//这里要特别注意一件事,就是android的事件分发并不是简单意义上的直接把事件向下方法,而是会根据子view的不同状态方法不同的事件。
//这里我举一个例子,假如我们有两个view重叠在一起,我们把上面的view叫做v1,下面的称作v2。
//我们在第一次点击这个view的时候,v1不对事件做处理,v2消费事件。这样的话,v1收到的是DOWN事件,v2收到的也是DOWN
//这时候我们不放开第一个手指,再用另一个手指点击v1的时候,v1同样不做处理,v2消费事件。在此次事件中,你就会发现一个很有趣的现象
//就是v1收到的事件是DOWN,因为v1从来没有消费过Down事件。而v2收到事件是ACTION_POINTER_DOWN。
//因为v2已经有一个DOWN事件了。而这里的改变事件的“罪魁祸首”就是下面的代码了!
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
//省略部分代码
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
transformedEvent.recycle();
return handled;
}
这个方法的逻辑相信大家应该都能看懂,很简单的。就是如果child不为空的话。那么就调用child的dispatchTouchEvent的方法,如果child为空,那么就调用父类的dispatchTouchEvent方法,而我们都知道ViewGroup的父类是view,所以也就是调用view的dispatchTouchEvent方法。所以接下来我们就要细究一下view的dispatchTouchEvent方法。
View
既然viewgroup中调用了view的dispatchTouchEvent方法,那么老规矩dispatchTouchEvent源码奉上。
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)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
...
return result;
}
这里的代码逻辑就是相当简单了,如果view设置了OnTouchListener,并且mOnTouchListener返回为true的话,那么就不在执行自身的onTouchEvent方法。否则的话将事件传递到onTouchEvent。
接下来我们就查看一下onTouchEvent的相关源码
算了不看了,太长了,基本上就是对event事件的处理,并没有再次分发。
事件冒泡
原谅我在这里创建一个名词,事件冒泡。但是你想想,当事件走到了我们的view的onTouchEvent以后,完成onTouchEvent以后,再完成view.dispatchTouchEvent事件,以此类推,像不像冒泡。
view
这里我们假设view.dispatchTouchEvent返回值是true
public boolean dispatchTouchEvent(MotionEvent event) {
...
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;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
...
return result;
}
观察上面代码,当ontouchevent返回为true的时候,view.dispatchOntouchEvent并没有做其他事情,只是将result置为true并继续向上传递。所以接下来我们去看viewGroup的源码
viewGroup
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
...
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
...
}
额,好像也只是把结果上传,那我们继续冒泡
public boolean dispatchTouchEvent(MotionEvent ev) {
...
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
...
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
...
return handled;
}
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
在这里的时候,如果子元素的dispatchTransformedTouchEvent返回为true,则会给viewgroup的mFirstTouchTarget打上target标签。
但是这是dispatchTransformedTouchEvent返回为true的情况,如果在down事件的时候,返回是false呢?
那么mFirstTouchTarget肯定就为null了,这时候dispatchTransformedTouchEvent有以下代码
public boolean dispatchTouchEvent(MotionEvent ev) {
// 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);
} 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;
//这里如果前面已经分发过事件了则alreadyDispatchedToNewTouchTarget为true。
//而 target == newTouchTarget则是继续链表中其他事件的分发
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
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;
}
}
predecessor = target;
target = next;
}
}
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
....
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
在这里我们又看到了我们熟悉的方法dispatchTransformedTouchEvent。只不过和之前不同的是,这次的子元素为null,所以直接调用了viewgroup自身的dispatchTouchEvent的方法。
好了那么总结以下,我们可以写出以下伪代码
if(dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)){
return true;
}else{
handled = super.dispatchTouchEvent(transformedEvent);
return handled;
}
好了,这里搞定,我们技术往上看。
DecorView和PhoneWindow
DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
PhoneWindow
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
啥也木有,继续往上
Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
好嘛,逻辑也很简单了,如果之前一直都木有人处理,那么走到Activity这里的时候,就会走到activity的onTouchEvent方法。
MotionEvent.ACTION_UP
其中我忽略了move事件的处理,因为move事件的处理和up差不多。而且我们在平常的开发中对于move事件处理的不多,所以这里就暂时忽略了move事件的处理。
事件分发
这里由于activity和phoneWindow 以及DecorView对于事件并没有区分是什么是什么事件,所以处理方式和前面并没有什么区别,这里暂时就不看了。
ViewGroup
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
...
// Check for interception.
final boolean intercepted;
...
// 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);
} 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;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
...
}
...
return handled;
}
这里可以看到UP事件走的逻辑和DOWN几乎完全不同。而且如果viewgroup的之前没有子元素的dispatchTouchEvent返回true的话,那么intercepted将会将为true。
并且由于mFirstTouchTarget==null,将会直接调用
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
语句,然后去调用自身的dispatchTouchEvent方法。也就是说如果viewgroup的子元素没有在down事件,dispatchTouchEvent方法的时候去返回true那么up事件将不再向下传递给子元素,而是直接由viewgroup本身处理。
那么我们假设之前viewgroup的子元素,有处理过down事件,那么和down事件一样,将调用
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits))
这条语句,继续将事件向子元素传递。只不过这里有一点和down不一样。down事件是通过遍历子元素,然后判断能否被点击,再去把事件传递。而在up事件的时候,没有进行遍历,而是直接调用之前保存的子元素,向他传递。
这里我们需要注意一点,那么就是viewgroup保存执行了down事件的子元素的时候,用的是一个链表。这就意味着,up事件是可以同时向多个子元素传递,只要之前消费down事件,那么都会收到viewgroup分发的事件。只不过viewgroup会根据每个子元素不同的状态方法不同的事件。这里需要大家多多注意,我也在这里困了一天之久!
View
按照惯例,接下来我们来观察view里面的dispatchTouchEvent方法。
好了你不用观察了,我帮你观察过了,和Down相比,没有任何区别。所以我们还是去看看onTouchEvent方法吧。
public boolean onTouchEvent(MotionEvent event) {
...
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) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
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;
}
return false;
}
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;
}
哦了,这里代码也没啥。相信大家仔细看下就都能看懂。其中performClick就是用来去调用listener的监听。
总结
看了android关于事件分发的源码之后,我除了豁然开朗的感觉。还有对于google工程师的由衷的敬佩,自己看他们的代码都看了很久很久,才理清楚一点点的头绪,真是不能想象他们是如何写出如此优美的代码。自己真的要再修行,再修行!