編輯:關於Android編程
自從開始負責公共控件模塊開始,我一直都想好好分析一下Android事件傳遞流程,相信網上有一大堆相關文章,但是我個人覺得作為一個專業的控件開發人員,如果只是知道一下大概,而不知其所以然,則不算一個合格的公共控件人員,感謝我曾經一位同事,在我剛開始接觸控件的時候帶著我,很耐心的教會我控件的內在,下面我個人從源碼角度來分析Android事件傳遞流程,基於Android5.0的代碼,如果有錯誤的地方,還望指出。
一,根視圖內部消息派發過程
對於上層代碼來說,最先處理事件的是viewRootImpl,下面先看一下viewRootImpl與TouchEvent相關的內容:
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
mKeyEventStatus = INPUT_DISPATCH_STATE_VIEW_POST_IME_STAGE;
return processKeyEvent(q);
} else {
mMotionEventStatus = INPUT_DISPATCH_STATE_VIEW_POST_IME_STAGE;
// If delivering a new non-key event, make sure the window is
// now allowed to start updating.
handleDispatchDoneAnimating();
final int source = q.mEvent.getSource();
//判斷為屏幕點擊事件或者鼠標點擊事件
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
return processPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
return processTrackballEvent(q);
} else {
return processGenericMotionEvent(q);
}
}
}
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
mAttachInfo.mUnbufferedDispatchRequested = false;
boolean handled = mView.dispatchPointerEvent(event);
if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
mUnbufferedInputDispatch = true;
if (mConsumeBatchedInputScheduled) {
scheduleConsumeBatchedInputImmediately();
}
}
return handled ? FINISH_HANDLED : FORWARD;
}
從上面代碼我們可以看出事件出傳遞到mView的dispatchPointerEvent方法裡面,通過追蹤mView,不難發現mView其實是PhoneWindow.DecorView。
而dispatchPointerEvent()是View裡面的方法,如果當前事件是Touch事件則會調用當前View的dispatchTouchEvent(),再看一下PhoneWindow.DecorView dispatchTouchEvent():
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Callback cb = getCallback();
//DecorView繼承自FrameLayout,但FrameLayout沒有重寫dispatchTouchEvent,cb為空則直接執行ViewGroup.dispatchTouchEvent()
return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
: super.dispatchTouchEvent(ev);
}
//CallBack 是Window的一個內部Interface,作用是可以讓用戶在事件的分發,菜單的構建過程中進行攔截。其中dispatchTouchEvent正是用於攔截觸控事件,Activity實現了這個方法
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
從上面代碼可以看出,getWindow().superDispatchTouchEvent(ev)為false的時候,自身的onTouchEvent()函數才會被回調。這就是為什麼每次事件的都是從Activity的dispatchTouchEvent 開始,最終又會傳回Activity的onTouchEvent。另外這裡的getWindow()實際上返回的是PhoneWindow,下面再看看PhoneWindow相關的代碼:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
boolean handled = mDecor.superDispatchTouchEvent(event);
return handled;
}
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
從上面代碼可看出,事件最後傳到mDecor.superDispatchTouchEvent(),而mDecor.superDispatchTouchEvent()單單執行了超類的dispatchTouchEvent()也就是ViewGroup的dispatchTouchEvent(),之後事件被分發到布局中的View中去。上述流程圖如下:

二,ViewGroup內部消息派發過程
1.ViewGroup中的dispatchTouchEvent
ViewGroup中的dispatchTouchEvent()在View中的事件傳遞中承擔了比較重要的角色,也是精華部分。它主要承擔了是三個工作:1.攔截子View事件(調用自身的onInterceptTouchEvent()) 2.找出可以接受事件的子View,並把事件傳遞下去 2.把交由自身的TouchEvent()處理事件。下面貼出ViewGroup中的dispatchTouchEvent()的代碼:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
/*
ACTION_DOWN是一些列事件的開端,因此需要做一些初始化工作。這裡主要實現了兩點:
1.將mFirstTouchTarget設置為null
2.清除FLAG_DISALLOW_INTERCEPT,讓事件可以被攔截
*/
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();
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
/*
1.disallowIntercept 由FLAG_DISALLOW_INTERCEPT標志位所決定,應用可以通過調用View.requestDisallowInterceptTouchEvent(boolean disallowIntercept)設置。默認為false
2.disallowIntercept為false時,onInterceptTouchEvent()會被調用,其返回值將決定事件是否會分發到其子view
3.TouchTarget是ViewGroup的內部靜態類,鏈式結構,有相關的回收機制,用於記錄當前被點擊的View,以及點擊的Pointer, ViewGroup的mFirstTouchTarget總指向TouchTarget的最前一個。
4.mFirstTouchTarget != null說明已經找到可以接受事件的View,通過onInterceptTouchEvent()同樣可以對其進行攔截
*/
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;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// 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;
if (!canceled && !intercepted) {
// If the event is targeting accessiiblity focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
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);
full_meizu6795_lwt_l1-userdebug
final int childrenCount = mChildrenCount;
/*
1.從這裡開始遍歷子View,遍歷能采取兩種方式:
①當isChildrenDrawingOrderEnabled()返回true,根據getChildDrawingOrder(int childCount, int i)函數的順序遍歷
從這裡可以看出,應用可以通過重寫isChildrenDrawingOrderEnabled()以及getChildDrawingOrder(int childCount, int i)來指定子View的遍歷順序,isChildrenDrawingOrderEnabled()默認返回false
②默認情況下,采用倒序遍歷子View
2.遍歷過程中,會調用dispatchTransformedTouchEvent(),把事件傳到當前子View的dispatchTouchEvent()中
3.遍歷過程中,假如對某個子view執行dispatchTransformedTouchEvent()返回true,遍歷被中斷。同時把當前的View以及當前的TouchEvent的pointerIndex記錄在mFirstTouchTarget 中
*/
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 ArrayList preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
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;
}
// 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();
}
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;
}
}
}
// Dispatch to touch targets.
/*
mFirstTouchTarget == null表明沒有找到可以消費事件的子view,
然後通過this.dispatchTransformedTouchEvent()——>View.dispatchTransformedTouchEvent()——>this.onTouchEvent()
流程把事件分發到自身的onTouchEvent()
*/
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.
/*
當mFirstTouchTarget !=null時可能是以下幾種情況:
(1).滿足條件(alreadyDispatchedToNewTouchTarget && target == newTouchTarget)也就是當前事件是Action_Down,並且通過上面的遍歷找到可以消費事件的子View,則直接返回true;
(2).不滿足1條件,但是事件通過需要被攔截,或者當前可消費事件的子View暫時不可見時,向該子View派發Action_Cancel事件,如上面的case5
(3).向當前可消費事件的子View分發事件。通常是這樣的情況:找到可消費事件的子View,當前又非Action_Down事件並且當前的ViewGroup沒有進行攔截。
*/
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;
}
//cancelChild為true,說明child收到攔截,因此清理與該child相關的TouchTarget
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;
}
其處理流程圖如下:

2.ViewGroup的dispatchTransformedTouchEvent()
下面先對dispatchTransfZ喎?/kf/ware/vc/" target="_blank" class="keylink">vcm1lZFRvdWNoRXZlbnSjqKOp1LTC67340NC31s72o7o8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;">
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.
//上面分析提及到,在事件被攔截時,子View可能會接收到CANCEL事件,在這裡可以看到event被直接傳到子類的dispatchTouchEvent()沒有作坐標轉換,因此在ACTION_CANCEL事件裡 面我們不應該去event的相關坐標進行計算。
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;
}
// Calculate the number of pointers to deliver.
//過濾觸控點
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// Motion事件沒有對應點,則丟棄這個Motion
if (newPointerIdBits == 0) {
return false;
}
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
//這裡將當前的坐標重新轉換到child的坐標系中
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);
}
.....
}
從上述源碼可知,dispatchTransformedTouchEvent()主要有兩個作用:
1.當child為空時,把事件傳遞到超類也就是View的dispatchTouchEvent()中。
2.當child不為空時,事件傳遞到child的dispatchTouchEvent()中,但是傳遞之前首先把事件的對應的坐標重新轉化為child坐標系中的坐標,而轉換的的方法是:
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
為什麼要這樣轉換?這裡涉及兩種坐標的概念:
1.視圖坐標:視圖坐標沒有邊界,它取決於View本身的大小,而不受屏幕大小的限制。
2.布局坐標:大小受限制,是指父視圖給子視圖分配的布局(layout)大小,超過這個大小的區域將不能顯示到父視圖的區域中。
上面兩種坐標的關系如下圖:

從上圖可知道布局坐標轉化為視圖坐標只需要加上mScrollY/mScrollX即可。
通常我們通過event.getX()/event.getX()獲取到的坐標是布局坐標,而child.mLeft,child.mRight則是相對於視圖坐標而言的,因此在父視圖獲取獲取到event的坐標後必須先轉化為視圖坐標,再根據child在父視圖中的位置,進行坐標平移,即 event.offsetLocation(+mScrollX - offsetX, + mScrollY - offsetY).
3,View的dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent event) {
....
if (onFilterTouchEventForSecurity(event)) {
//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;
}
}
....
return result;
}
上面給出View.dispatchTouchEvent()的主要代碼,從上面代碼可以看出,當mOnTouchListener不為空時會先執行mOnTouchListener.onTouch(this, event),而當mOnTouchListener沒有消耗事件時onTouchEvent()才會被執行,這說明在同一個View,mOnTouchListener優先級會比onTouchEvent要高。
4,View的onTouchEvent(MotionEvent event)
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
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.
//盡管當前View是disable狀態,但是只要是CLICKABLE或者LONG_CLICKABLE仍然可以消費事件
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
//如果設置代理,則事件交由代理處理,如果代理消耗了事件則直接返回true
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
...
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();
}
//假如對view設置了OnClickListener,在這裡會被回調。
if (!post(mPerformClick)) {
performClick();
}
}
}
...
break;
case MotionEvent.ACTION_DOWN:
...
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
//檢查長按事件,假如設置了OnLongClickListener,在此有可能被回調
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
//檢查長按事件,假如設置了OnLongClickListener,在此有可能被回調
checkForLongClick(0);
}
...
break;
case MotionEvent.ACTION_CANCEL:
...
break;
case MotionEvent.ACTION_MOVE:
...
break;
}
return true;
}
return false;
}
從上面代碼可以總結出一下幾點:
1.盡管當前View是disable狀態但仍然具有消費事件能力
2.假如設置了代理,事件將先交由代理處理,假如代理消費了事件,則直接返回true
3.在沒有設置代理的情況下,假如標記位CLICKABLE LONG_CLICKABLE不為0,則事件必然在此被消費
4.OnLongClickListener和OnClickListener的回調函數都在View的onTouchEvent()裡面執行,但執行時機有區別, OnClickListener.onClick()在ACTION_UP時被回調。OnLongClickListener.onLongClick()則在ACTION_DOWN的時候被執行(其實也不一定在這個時機被執行,因為長按需要作一定的時延檢測)。另外我們在定義View時經常需要監 聽點擊事件,這裡不推薦通過setOnClickListener()的方式實現,因為這樣相當於占用了應用的監聽權利,假如此時應用在不知道代碼邏輯的情況下,繼續setOnClickListener(),那麼默認的監聽器被覆蓋。做成意想不到的後果。其實替代方案可以是重寫performClick()。長按事件同理。
談談Android裡的Context的使用實例
大家好,今天給大家分享一下Android裡的Context的一些用法,以前經常有人在群裡問我比如我在一個工具類裡的某個方法,或者View裡需要調用Context.但是工具
AndroidStudio 控制Git
版本控制是項目開發過程中必不可少的部分,不管是個人還是團隊,靈活的使用版本控制將會使項目開發變得更加輕松。Android Studio集成了版本控制系統,目前支持CVS、
Meta viewport (視口元信息標簽)
讀前須知:PPK寫這篇文章的時候,IPhone還沒有生產出4S之後的產品。所以,這篇文章中提到的IPhone,都是指IPhone4S及之前的手機。TOP This pag
Android應用中圖片浏覽時實現自動切換功能的方法詳解
先給最終效果圖:當我們在最下邊的gallery中切換圖片時,上面的大圖片會自動切換,切換時有動畫效果哦,很簡單的一個程序,有待完善更多的功能!activity代碼:pac