原文参考[这里](http://blog.csdn.net/zizidemenghanxiao/article/details/50184295)
顶级View对点击事件的分发过程:
~~~
/**
* {@inheritDoc}
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
/*
* 当新的一轮点击到来的时候,从ACTION_DOWN开始的,做一些初始化的工作:
* */
// 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.
/*
* 至少我知道在这个函数中最终将mFirstTouchTarget设为null。
* mFirstTouchTarget代表的就是一个事件序列中第一个拦截的对象,
* 所以这里需要重置。
* */
cancelAndClearTouchTargets(ev);
/*
* 如果事件是ACTION_DOWN,
* ViewGroup就会在resetTouchState中重置下面的FLAG_DISALLOW_INTERCEPT标志位。
* 重置的方式是这样的:mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
* */
resetTouchState();
}
// Check for interception.
/*
* 这个标识很重要,因为它一旦被标志位true,意味着下面的各种if语句都进不去了,
* 意味着本ViewGroup拦截了该事件,并且后续的事件序列直接由该ViewGroup处理,
* 而不是进入各种if中判断是否需要拦截。
* */
final boolean intercepted;// 拦截标识
/*
* 这个if中需要满足两个条件:
* (1)actionMasked == MotionEvent.ACTION_DOWN:
* 该事件是否为点击下按事件时成立,就是说新的一轮事件到来
* (2)mFirstTouchTarget != null:
* 当ViewGroup不拦截事件并将事件交给子元素处理时,成立,mFirstTouchTarget指向这个子元素。
* 而且在ViewGroup中,默认onInterceptTouchEvent返回false,它是不拦截任何事件的,
* 但是在LinearLayout中可能就会拦截啊,可以改写啊。
* 而且,当第二个条件成立时,此时发生的事件序列就是ACTION_MOVE或者ACTION_UP,都会进入到这个if语句中。
* */
/*
* 所以说呢,当子元素成功拦截了事件或者下按事件发生的时候就会进入if语句。
* 所以说呢,如果子元素没有处理,并且是move和up发生的时候就无法进入该if语句。
* 但为什么这样设定呢,因为如果子元素没有处理的话,事件序列中的其他事件就会直接由ViewGroup来处理了,
* 不需要来这里来判断一下到底要不要拦截事件了。那如果是move和up也是同样的,不需要来这里来判断要不要拦截事件。
* */
/*
* 也就相当于说,一个事件,第一次因为ACTION_DOWN进入这里,然后ViewGroup判断是否来拦截。
* 之后在子元素成功处理后,因为子元素是可以通过FLAG_DISALLOW_INTERCEPT标志位来干预父元素的事件分发过程,所以又来这里来要看是否拦截。
* */
/*
* 为什么总说一旦父元素拦截ACTION_DOWN以后其他的事件序列就只能由父元素来处理呢?
* 是因为如果父元素拦截了ACTION_DOWN,那么mFirstTouchTarget == null
* 当ACTION_MOVE和ACTION_UP到来的时候,这条if语句就不会进入了,
* 然后intercepted = true;表示事件序列由父元素全拦截了。
* */
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
/*
* 通常事件传递过程是由外向内的,
* 但是通过 requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,
* 不过ACTION_DOWN事件除外。
* 干预表现在子元素已经拦截了事件,
* 但是可以通过requestDisallowInterceptTouchEvent来控制
* ACTION_MOVE和ACTION_UP能不能够进入到这里来。
* */
/*
* FLAG_DISALLOW_INTERCEPT一旦设置后,ViewGroup将无法拦截处理ACTION_DOWN以外的其他点击事件了。
* 因为在事件分发时,ACTION_DOWN会重置FLAG_DISALLOW_INTERCEPT标志位,表示另一次事件开始。
* */
/*
* 子View干涉ViewGroup的过程:
* 初始化:mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
* 在子View中FLAG_DISALLOW_INTERCEPT被重置,也就是要去干扰,
* 然后mGroupFlags & FLAG_DISALLOW_INTERCEPT为1
* 然后(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0 为true
* 然后disallowIntercept为true
* 然后导致if (!disallowIntercept)无法进入。
* */
/*
* FLAG_DISALLOW_INTERCEPT标志位有什么用呢?
* 当面对滑动冲突时,我们可以考虑用这种方法去解决问题。
* */
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
/*
* 所以说onInterceptTouchEvent并不是每次事件都会被调用的。
* 而dispatchTouchEvent却会在每次都调用。
* 对于原始的ViewGroup,onInterceptTouchEvent会返回false,
* 但是对于你自己写的LinearLayout,则可以修改这个函数,
* 让它对ACTION_DOWN、ACTION_MOVE、ACTION_UP做出不同的选择。
* */
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.
/*
* 就是说没有子元素mFirstTouchTarget,而且事件也不是ACTION_DOWN,
* 没人管那就只能自己拦截了。
* */
intercepted = true;
}
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
/*
* 当ViewGroup不拦截事件的时候,intercepted=false,事件会向下分发由它的子View进行处理
* 所以说一旦ViewGroup拦截了事件,intercepted=true,
* 意味着事件序列中的任何事件都不再会传给子元素了,由父元素全权处理。
* 所以intercepted=true一定要谨慎设置。
* */
if (!canceled && !intercepted) {
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) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final View[] children = mChildren;
final boolean customOrder = isChildrenDrawingOrderEnabled();
/*
* 遍历ViewGroup的所有子元素,判断子元素是否能够接收到点击事件。
* */
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder ?
getChildDrawingOrder(childrenCount, i) : i;
final View child = children[childIndex];
/*
* 判断子元素是否能够接收到点击事件:
* (1)canViewReceivePointerEvents:子元素是否在播动画。
* (2)isTransformedTouchPointInView:点击事件的坐标是否落在子元素的区域内。
* */
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
/*
* 如果上面那个if语句没有成立,说明这个子元素是可以拦截事件的,
* 所以新的TouchTarget出现了,就是这个子元素。
* */
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);
/*
* 这个子元素已经拦截该事件了,现在要子元素传递给它自己的子元素去分派这个事件了:
* dispatchTransformedTouchEvent实际上调用的就是子元素的dispatchTouchEvent方法。
* 下面的第三个参数中child一定不为null,所以child的dispatchTouchEvent一定会被调用。
* 子元素的dispatchTouchEvent返回true,
* 意味着dispatchTransformedTouchEvent也返回ture,
* 表示事件被子元素分发成功,并break跳出循环。
* */
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = childIndex;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
/*
* 分发成功后,在addTouchTarget会对mFirstTouchTarget进行赋值
* */
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
/*
* 分发成功,跳出循环
* */
break;
}
}
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
/*
* 有两种情况遍历所有的子元素后事件也没有处理:
* (1)ViewGroup根本没有子元素
* (2)子元素的dispatchTouchEvent都返回了false。
* 这种情况下只能ViewGroup自己来处理事件了。
* */
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
/*
* 注意第三个参数:null,在上面变量子元素的时候这里放的是child。
* 如果是null,dispatchTransformedTouchEvent内部就会调用:
* super.dispatchTouchEvent(event);
* 很显然,这里就转到了View的dispatchTouchEvent(event)方法,即点击事件开始交由View来处理。在View中有onTouchEvent。
* 其实父元素ViewGroup的onTouchEvent就是指的是View中的onTouchEvent方法,它自己这里是没有的。因为ViewGroup是继承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;
}
}
// 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);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
~~~
View对点击事件的处理过程
* View源码中的dispatchTouchEvent进行分析
~~~
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
/*
* 首先会判断有没有设置OnTouchListener。
* 如果OnTouchListener中的onTouch方法返回true,那么onTouchEvent方法就不会调用,
* 这样做的好处是方便外界处理点击事件。
* */
ListenerInfo li = mListenerInfo;
if (li != null
&& li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}
/*
* 优先级低于OnTouchListener
* */
if (onTouchEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
~~~
* View源码中的onTouchEvent方法进行分析
~~~
/**
* Implement this method to handle touch screen motion events.
* <p>
* If this method is used to detect click actions, it is recommended that
* the actions be performed by implementing and calling
* {@link #performClick()}. This will ensure consistent system behavior,
* including:
* <ul>
* <li>obeying click sound preferences
* <li>dispatching OnClickListener calls
* <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
* accessibility features are enabled
* </ul>
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
*/
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
/*
* 当View处于不可用状态下时,View照样会消耗点击事,
* 但它并不对事件做出任何的反映
* */
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
/*
* 如果View设置有代理,那么还会执行mTouchDelegate的onTouchEvent方法,
* 这个onTouchEvent的工作机制看起来和OnTouchListener类似,这里我们不做研究
* */
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
/*
* 这里是对点击事件的具体处理。
* 可以发现的是View的CLICKABLE和LONG_CLICKABLE只要有一个为true,
* 那么这个View就消耗这个事件,即onTouchEvent返回ture,不管他是不是DISABLE状态。
* 这个证明了前面(8)(9)(10)的结论。
* */
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
/*
* 当up事件发生时,就会触发performClick()方法。
* */
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);
}
if (!mHasPerformedLongPress) {
// 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)) {
/*
* 如果View设置了OnClickListener,
* 那么performClick()方法内部会调用它的onClick方法
* */
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true);
checkForLongClick(0);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
~~~
- 前言
- 第一章Activity的生命周期和启动模式
- 1.1 Activity生命周期全面分析
- 1.2 Activity的启动模式
- 1.3 IntentFilter的匹配规则
- 第二章IPC
- 转 chapter IPC
- 转IPC1
- 转IPC2
- Binder讲解
- binder
- Messenger
- 一、Android IPC简介
- 二、Android中的多进程模式
- 三、IPC基础概念介绍
- 四、Android中的IPC方式
- 五、Binder连接池
- 第三章
- 第九章四大组件的工作过程
- 第十章
- 第13章 综合技术
- 使用CrashHandler 来获取应用的crash 信息
- 使用Multidex来解决方法数越界
- Android的动态加载技术