深入解析Andoird應(yīng)用開發(fā)中View的事件傳遞

下面以點擊某個view之后的事件傳遞為例子。
首先分析view里的dispatchTouchEvent()方法,它是點擊view執(zhí)行的第一個方法。
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
注意:里面包含兩個回調(diào)函數(shù) onTouch(),onTouchEvent();如果控件綁定了OnTouchListener,且該控件是enabled,那么就執(zhí)行onTouch()方法,如果該方法返回true,則說明該觸摸事件已經(jīng)被OnTouchListener監(jiān)聽器消費掉了,不會再往下分發(fā)了;但是如果返回false,則說明未被消費,繼續(xù)往下分發(fā)到該控件的onTouchEvent()去處理。
然后分析onTouchEvent()方法,進行進一步的觸摸事件處理。
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
.....
performClick(); //響應(yīng)點擊事件
break;
case MotionEvent.ACTION_DOWN:
..... break;
case MotionEvent.ACTION_CANCEL:
..... break;
case MotionEvent.ACTION_MOVE:
..... break;
}
return true;
}
return false;
如果該控件是clickable 、long_clickable的,那么就可以響應(yīng)對應(yīng)事件,響應(yīng)完后返回true繼續(xù)響應(yīng)。比如點擊事件,先響應(yīng)ACTION_DOWN,然后break并返回true,然后手抬起,又從dispatchTouchEvent()分發(fā)下來,再響應(yīng)ACTION_UP,里面會去performClick()響應(yīng)點擊事件。
響應(yīng)點擊事件
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
里面執(zhí)行mOnClickListener.onClick(this);即回調(diào)綁定監(jiān)聽器的onClick()函數(shù)。
關(guān)鍵點:
onTouch和onTouchEvent的區(qū)別,又該如何使用?
答:
當(dāng)view控件接受到觸摸事件,如果控件綁定了onTouchListener監(jiān)聽器,而且該控件是enable,那么就去執(zhí)行onTouch()方法,如果返回true,則已經(jīng)把觸摸事件消費掉,不再向下傳遞;如果返回false,那么繼續(xù)調(diào)用onTouchEvent()事件。
Android的Touch事件傳遞到Activity頂層的DecorView(一個FrameLayout)之后,會通過ViewGroup一層層往視圖樹的上面?zhèn)鬟f,最終將事件傳遞給實際接收的View。下面給出一些重要的方法。
dispatchTouchEvent
事件傳遞到一個ViewGroup上面時,會調(diào)用dispatchTouchEvent。代碼有刪減
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Attention 1 :在按下時候清除一些狀態(tài)
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
//注意這個方法
resetTouchState();
}
// Attention 2:檢查是否需要攔截
final boolean intercepted;
//如果剛剛按下 或者 已經(jīng)有子View來處理
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 {
// 不是一個動作序列的開始 同時也沒有子View來處理,直接攔截
intercepted = true;
}
//事件沒有取消 同時沒有被當(dāng)前ViewGroup攔截,去找是否有子View接盤
if (!canceled && !intercepted) {
//如果這是一系列動作的開始 或者有一個新的Pointer按下 我們需要去找能夠處理這個Pointer的子View
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
//上面說的觸碰點32的限制就是這里導(dǎo)致
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
//對當(dāng)前ViewGroup的所有子View進行排序,在上層的放在開始
final ArrayList<View> 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);
// canViewReceivePointerEvents visible的View都可以接受事件
// isTransformedTouchPointInView 計算是否落在點擊區(qū)域上
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//能夠處理這個Pointer的View是否已經(jīng)處理之前的Pointer,那么把
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;
} }
//Attention 3 : 直接發(fā)給子View
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;
}
}
}
}
}
// 前面已經(jīng)找到了接收事件的子View,如果為NULL,表示沒有子View來接手,當(dāng)前ViewGroup需要來處理
if (mFirstTouchTarget == null) {
// ViewGroup處理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
if(alreadyDispatchedToNewTouchTarget) {
//ignore some code
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
}
}
return handled;
}
上面代碼中的Attention在后面部分將會涉及,重點注意。
這里需要指出一點的是,一系列動作中的不同Pointer可以分配給不同的View去響應(yīng)。ViewGroup會維護一個PointerId和處理View的列表TouchTarget,一個TouchTarget代表一個可以處理Pointer的子View,當(dāng)然一個View可以處理多個Pointer,比如兩根手指都在某一個子View區(qū)域。TouchTarget內(nèi)部使用一個int來存儲它能處理的PointerId,一個int32位,這也就是上層為啥最多只能允許同時最多32點觸碰。
看一下Attention 3 處的代碼,我們經(jīng)常說view的dispatchTouchEvent如果返回false,那么它就不能系列動作后面的動作,這是為啥呢?因為Attention 3處如果返回false,那么它不會被記錄到TouchTarget中,ViewGroup認(rèn)為你沒有能力處理這個事件。
這里可以看到,ViewGroup真正處理事件是在dispatchTransformedTouchEvent里面,跟進去看看:
dispatchTransformedTouchEvent
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
//沒有子類處理,那么交給viewgroup處理
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);
}
return handled;
}
可以看到這里不管怎么樣,都會調(diào)用View的dispatchTouchEvent,這是真正處理這一次點擊事件的地方。
dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
if (onFilterTouchEventForSecurity(event)) {
//先走View的onTouch事件,如果onTouch返回True
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設(shè)置的onTouch事件處在一個較高的優(yōu)先級,如果onTouch執(zhí)行返回true,那么就不會去走view的onTouchEvent,而我們一些點擊事件都是在onTouchEvent中處理的,這也是為什么onTouch中返回true,view的點擊相關(guān)事件不會被處理。
小小總結(jié)一下這個流程
ViewGroup在接受到上級傳下來的事件時,如果是一系列Touch事件的開始(ACTION_DOWN),ViewGroup會先看看自己需不需要攔截這個事件(onInterceptTouchEvent,ViewGroup的默認(rèn)實現(xiàn)直接返回false表示不攔截),接著ViewGroup遍歷自己所有的View。找到當(dāng)前點擊的那個View,馬上調(diào)用目標(biāo)View的dispatchTouchEvent。如果目標(biāo)View的dispatchTouchEvent返回false,那么認(rèn)為目標(biāo)View只是在那個位置而已,它并不想接受這個事件,只想安安靜靜的做一個View(我靜靜地看著你們裝*)。此時,ViewGroup還會去走一下自己dispatchTouchEvent,Done!
相關(guān)文章
解決springboot文件配置端口不起作用(默認(rèn)8080)
這篇文章主要介紹了解決springboot文件配置端口不起作用(默認(rèn)8080),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
Spring?Boot深入學(xué)習(xí)數(shù)據(jù)訪問之Spring?Data?JPA與Hibernate的應(yīng)用
Spring?Data?JPA是Spring?Data的子項目,在使用Spring?Data?JPA之前,先了解一下Hibernate,因為Spring?Data?JPA是由Hibernate默認(rèn)實現(xiàn)的2022-10-10
SpringBoot將所有依賴(包括本地jar包)打包到項目
這篇文章主要介紹了SpringBoot將所有依賴(包括本地jar包)打包到項目,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-06-06
JDK源碼之線程并發(fā)協(xié)調(diào)神器CountDownLatch和CyclicBarrier詳解
我一直認(rèn)為程序是對于現(xiàn)實世界的邏輯描述,而在現(xiàn)實世界中很多事情都需要各方協(xié)調(diào)合作才能完成,就好比完成一個平臺的交付不可能只靠一個人,而需要研發(fā)、測試、產(chǎn)品以及項目經(jīng)理等不同角色人員進行通力合作才能完成最終的交付2022-02-02

