編輯:關於Android編程
package cc.aa;
import android.os.Environment;
import android.view.MotionEvent;
import android.view.View;
public class UnderstandDispatchTouchEvent {
/**
* dispatchTouchEvent()源碼學習及其注釋
* 常說事件傳遞中的流程是:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent
* 在這個鏈條中dispatchTouchEvent()是處在鏈首的位置當然也是最重要的.
* 在dispatchTouchEvent()決定了Touch事件是由自己的onTouchEvent()處理
* 還是分發給子View處理讓子View調用其自身的dispatchTouchEvent()處理.
*
*
* 其實dispatchTouchEvent()和onInterceptTouchEvent()以及onTouchEvent()的關系
* 在dispatchTouchEvent()方法的源碼中體現得很明顯.
* 比如dispatchTouchEvent()會調用onInterceptTouchEvent()來判斷是否要攔截.
* 比如dispatchTouchEvent()會調用dispatchTransformedTouchEvent()方法且在該方法中遞歸調用
* dispatchTouchEvent();從而會在dispatchTouchEvent()裡最終調用到onTouchEvent()
*
*
*
* 重點關注:
* 1 子View對於ACTION_DOWN的處理十分重要!!!!!
* ACTION_DOWN是一系列Touch事件的開端,如果子View對於該ACTION_DOWN事件在onTouchEvent()中返回了false即未消費.
* 那麼ViewGroup就不會把後續的ACTION_MOVE和ACTION_UP派發給該子View.在這種情況下ViewGroup就和普通的View一樣了,
* 調用該ViewGroup自己的dispatchTouchEvent()從而調用自己的onTouchEvent();即不會將事件分發給子View.
* 詳細代碼請參見如下代碼分析.
*
* 2 為什麼子view對於Touch事件處理返回true那麼其上層的ViewGroup就無法處理Touch事件了?????
* 這個想必大家都知道了,因為該Touch事件被子View消費了其上層的ViewGroup就無法處理該Touch事件了.
* 那麼在源碼中的依據是什麼呢??請看下面的源碼分析
*
* 參考資料:
www.2cto.com
* Thank you very much
*/
@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)
* 因為ACTION_DOWN是一系列事件的開端,當是ACTION_DOWN時進行一些初始化操作.
* 從源碼的注釋也可以看出來:清除以往的Touch狀態(state)開始新的手勢(gesture)
* cancelAndClearTouchTargets(ev)中有一個非常重要的操作:
* 將mFirstTouchTarget設置為null!!!!
* 隨後在resetTouchState()中重置Touch狀態標識
*/
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)
* 在dispatchTouchEvent(MotionEventev)這段代碼中
* 使用變量intercepted來標記ViewGroup是否攔截Touch事件的傳遞.
* 該變量在後續代碼中起著很重要的作用.
*/
final boolean intercepted;
// 事件為ACTION_DOWN或者mFirstTouchTarget不為null(即已經找到能夠接收touch事件的目標組件)時if成立
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
//判斷disallowIntercept(禁止攔截)標志位
//因為在其他地方可能調用了requestDisallowInterceptTouchEvent(boolean disallowIntercept)
//從而禁止執行是否需要攔截的判斷(有點拗口~其實看requestDisallowInterceptTouchEvent()方法名就可明白)
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//當沒有禁止攔截判斷時(即disallowIntercept為false)調用onInterceptTouchEvent(ev)方法
if (!disallowIntercept) {
//既然disallowIntercept為false那麼就調用onInterceptTouchEvent()方法將結果賦值給intercepted
//常說事件傳遞中的流程是:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent
//其實在這就是一個體現,在dispatchTouchEvent()中調用了onInterceptTouchEvent()
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
//當禁止攔截判斷時(即disallowIntercept為true)設置intercepted = false
intercepted = false;
}
} else {
//當事件不是ACTION_DOWN並且mFirstTouchTarget為null(即沒有Touch的目標組件)時
//設置 intercepted = true表示ViewGroup執行Touch事件攔截的操作。
//There are no touch targets and this action is not an initial down
//so this view group continues to intercept touches.
intercepted = true;
}
/**
* 第三步:檢查cancel(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;
//不是ACTION_CANCEL並且ViewGroup的攔截標志位intercepted為false(不攔截)
if (!canceled && !intercepted) {
//處理ACTION_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 (childrenCount != 0) {
// 依據Touch坐標尋找子View來接收Touch事件
// Find a child that can receive the event.
// Scan children from front to back.
final View[] children = mChildren;
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final boolean customOrder = isChildrenDrawingOrderEnabled();
// 遍歷子View判斷哪個子View接受Touch事件
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = children[childIndex];
if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// 找到接收Touch事件的子View!!!!!!!即為newTouchTarget.
// 既然已經找到了,所以執行break跳出for循環
// 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不滿足,當然也不會執行break語句.
* 於是代碼會執行到這裡來.
*
* 調用方法dispatchTransformedTouchEvent()將Touch事件傳遞給子View做
* 遞歸處理(也就是遍歷該子View的View樹)
* 該方法很重要,看一下源碼中關於該方法的描述:
* Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
* 將Touch事件傳遞給特定的子View.
* 該方法十分重要!!!!在該方法中為一個遞歸調用,會遞歸調用dispatchTouchEvent()方法!!!!!!!!!!!!!!
* 在dispatchTouchEvent()中:
* 如果子View為ViewGroup並且Touch沒有被攔截那麼遞歸調用dispatchTouchEvent()
* 如果子View為View那麼就會調用其onTouchEvent(),這個就不再贅述了.
*
*
* 該方法返回true則表示子View消費掉該事件,同時進入該if判斷.
* 滿足if語句後重要的操作有:
* 1 給newTouchTarget賦值
* 2 給alreadyDispatchedToNewTouchTarget賦值為true.
* 看這個比較長的英語名字也可知其含義:已經將Touch派發給新的TouchTarget
* 3 執行break.
* 因為該for循環遍歷子View判斷哪個子View接受Touch事件,既然已經找到了
* 那麼就跳出該for循環.
* 4 注意:
* 如果dispatchTransformedTouchEvent()返回false即子View
* 的onTouchEvent返回false(即Touch事件未被消費)那麼就不滿足該if條件,也就無法執行addTouchTarget()
* 從而導致mFirstTouchTarget為null.那麼該子View就無法繼續處理ACTION_MOVE事件
* 和ACTION_UP事件!!!!!!!!!!!!!!!!!!!!!!
* 5 注意:
* 如果dispatchTransformedTouchEvent()返回true即子View
* 的onTouchEvent返回true(即Touch事件被消費)那麼就滿足該if條件.
* 從而mFirstTouchTarget不為null!!!!!!!!!!!!!!!!!!!
* 6 小結:
* 對於此處ACTION_DOWN的處理具體體現在dispatchTransformedTouchEvent()
* 該方法返回boolean,如下:
* true---->事件被消費----->mFirstTouchTarget!=null
* false--->事件未被消費---->mFirstTouchTarget==null
* 因為在dispatchTransformedTouchEvent()會調用遞歸調用dispatchTouchEvent()和onTouchEvent()
* 所以dispatchTransformedTouchEvent()的返回值實際上是由onTouchEvent()決定的.
* 簡單地說onTouchEvent()是否消費了Touch事件(true or false)的返回值決定了dispatchTransformedTouchEvent()
* 的返回值!!!!!!!!!!!!!從而決定了mFirstTouchTarget是否為null!!!!!!!!!!!!!!!!從而進一步決定了ViewGroup是否
* 處理Touch事件.這一點在下面的代碼中很有體現.
*
*
*/
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();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
/**
* 該if條件表示:
* 經過前面的for循環沒有找到子View接收Touch事件並且之前的mFirstTouchTarget不為空
*/
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指向了最初的TouchTarget
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
/**
* 分發Touch事件至target(Dispatch to touch targets)
*
* 經過上面對於ACTION_DOWN的處理後mFirstTouchTarget有兩種情況:
* 1 mFirstTouchTarget為null
* 2 mFirstTouchTarget不為null
*
* 當然如果不是ACTION_DOWN就不會經過上面較繁瑣的流程
* 而是從此處開始執行,比如ACTION_MOVE和ACTION_UP
*/
if (mFirstTouchTarget == null) {
/**
* 情況1:mFirstTouchTarget為null
*
* 經過上面的分析mFirstTouchTarget為null就是說Touch事件未被消費.
* 即沒有找到能夠消費touch事件的子組件或Touch事件被攔截了,
* 則調用ViewGroup的dispatchTransformedTouchEvent()方法處理Touch事件則和普通View一樣.
* 即子View沒有消費Touch事件,那麼子View的上層ViewGroup才會調用其onTouchEvent()處理Touch事件.
* 在源碼中的注釋為:No touch targets so treat this as an ordinary view.
* 也就是說此時ViewGroup像一個普通的View那樣調用dispatchTouchEvent(),且在dispatchTouchEvent()
* 中會去調用onTouchEvent()方法.
* 具體的說就是在調用dispatchTransformedTouchEvent()時第三個參數為null.
* 第三個參數View child為null會做什麼樣的處理呢?
* 請參見下面dispatchTransformedTouchEvent()的源碼分析
*
* 這就是為什麼子view對於Touch事件處理返回true那麼其上層的ViewGroup就無法處理Touch事件了!!!!!!!!!!
* 這就是為什麼子view對於Touch事件處理返回false那麼其上層的ViewGroup才可以處理Touch事件!!!!!!!!!!
*
*/
handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
} else {
/**
* 情況2:mFirstTouchTarget不為null即找到了可以消費Touch事件的子View且後續Touch事件可以傳遞到該子View
* 在源碼中的注釋為:
* 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;
//對於非ACTION_DOWN事件繼續傳遞給目標子組件進行處理,依然是遞歸調用dispatchTransformedTouchEvent()
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;
}
}
/**
* 處理ACTION_UP和ACTION_CANCEL
* 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;
}
//=====================以上為dispatchTouchEvent()源碼分析======================
//===============以下為dispatchTransformedTouchEvent()源碼分析=================
/**
* 在dispatchTouchEvent()中調用dispatchTransformedTouchEvent()將事件分發給子View處理
*
* Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
*
* 在此請著重注意第三個參數:View child
* 在dispatchTouchEvent()中多次調用了dispatchTransformedTouchEvent(),但是有時候第三個參數為null,有時又不是.
* 那麼這個參數是否為null有什麼區別呢?
* 在如下dispatchTransformedTouchEvent()源碼中可見多次對於child是否為null的判斷,並且均做出如下類似的操作:
* if (child == null) {
* handled = super.dispatchTouchEvent(event);
* } else {
* handled = child.dispatchTouchEvent(event);
* }
* 這個代碼是什麼意思呢??
* 當child == null時會將Touch事件傳遞給該ViewGroup自身的dispatchTouchEvent()處理.
* 即super.dispatchTouchEvent(event)正如源碼中的注釋描述的一樣:
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
* 當child != null時會調用該子view(當然該view可能是一個View也可能是一個ViewGroup)的dispatchTouchEvent(event)處理.
* 即child.dispatchTouchEvent(event);
*
*
*/
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.
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;
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
if (newPointerIdBits == 0) {
return false;
}
// 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.
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;
}
}
上一篇/kf/201412/365596.html
Android IPC Binder
(一)前言Binder原本是IPC工具,但是在Android中它的主要作用是支持RPC(Remote Procedure Call),使得當前進程調用另一個進程的函數就像
Fresco-FaceBook推出的Android圖片加載庫
在Android設備上面,快速高效的顯示圖片是極為重要的。過去的幾年裡,我們在如何高效的存儲圖像這方面遇到了很多問題。圖片太大,但是手機的內存卻很小。每一個像素的R、G、
Android設計模式之一個例子讓你徹底明白裝飾者模式(Decorator Pattern)
導讀這篇文章中我不會使用概念性文字來說明裝飾者模式,因為通常概念性的問題都很抽象,很難懂,使得讀者很難明白到底為什麼要使用這種設計模式,我們設計模式的誕生,肯定是前輩們在
Android中View的布局及繪圖機制
為了研究Android中View的布局及繪圖機制,我創建了一個非常簡單的App,該App只有一個Activity,該Activity對應的layout如下所示: 該