原文參考[這里](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的動態加載技術