文章目錄
- 基本知識
- 事件傳遞的三個主體
- 事件分發機制相關三個經典函式
- 事件分發機制四個經典事件
- 事件分發機制場景
- 不攔截、不消費
- ViewGroup攔截、無消費
- ViewGoup消費,不攔截
- 原始碼分析
- Activity的dispatchTouchEvent()原始碼
- ViewGroup的dispatchTouchEvent()原始碼
- 總體邏輯分析
- 具體分析一
- 具體分析二
- View的dispatchTouchEvent()原始碼
- 總結
基本知識
事件傳遞的三個主體
Activity、ViewGroup、View
他們三個的嵌套關系一般是這樣:

但是還要明白的是:
- ViewGroup當然可以嵌套ViewGroup,即ViewGroup也可以是另一個ViewGroup的子View,
- ViewGroup其實是繼承于View,是View的子類,
事件分發機制相關三個經典函式
- dispatchTouchEvent():分發函式
- onInterceptTouchEvent():攔截函式
- onTouchEvent():消費函式
它們的功能和它們名字一樣,其中攔截函式是ViewGroup獨有的,其它兩個函式在上面說的三個主體都存在,
事件分發機制四個經典事件
- ACTION_DOWN
- ACTION_MOVE
- ACTION_UP
- ACTION_CANCLE
意如其名
事件分發機制場景
參考文章:https://blog.csdn.net/caifengyao/article/details/65437695?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param
不攔截、不消費
當三個主體的任何函式的 回傳值 都不做任何處理時,即不攔截、不消費:
可見:
- 對于down事件:我會從外層一層層地分發下去(Activity->ViewGroup->view),看看
- down不消費,move,up我就不傳遞去了
ViewGroup攔截、無消費
當在ViewGroup使onInterceptTouchEvent()回傳true,即ViewGroup對事件進行攔截時:
可見:
- 事件被攔截之后就不會往下分發
ViewGoup消費,不攔截
當在ViewGroup使onTouchEvent()回傳true,即ViewGroup對事件進行消費時:
可見:
- 當down被消費了就不會往上冒
- move up不會往下發,而是直接分發給消費者,
原始碼分析
具體代碼怎么實作?主要是看分發函式dispatchTouchEvent(),接下來我們看看 三個主體的dispatchTouchEvent() 原始碼分析
Activity的dispatchTouchEvent()原始碼
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();//這是一個空方法
}
//主要看這一句
//getWindow().superDispatchTouchEvent(ev)
//這句函式呼叫的時DecorView的superDispatchTouchEvent()
//而DecorView繼承于ViewGroup
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
代碼中我寫了注釋,我們可以得出下面的結論:
- getWindow().superDispatchTouchEvent(ev),實際是呼叫了一個ViewGroup的dispatchTouchEvent()
- getWindow().superDispatchTouchEvent(ev)回傳true,說明有子View消費該事件(為什么呢?我們要分析完ViewGroup的dispatchTouchEvent()才知道,但現在可以暫時給出這個結論);這個子View可能是某個ViewGroup或者View,
- 如果有子view消費該事件則回傳true,否則呼叫自身的onTouchEvent(ev),即把事件分發給自己,
ViewGroup的dispatchTouchEvent()原始碼
ViewGroup的dispatchTouchEvent()原始碼很長,我參考了https://blog.csdn.net/wolinxuebin/article/details/53057075
之后得出一些小結,現在貼出一部分,一段一段分析,
總體邏輯分析
我把部分代碼和具體邏輯去掉,看它的空架子
//這是一個單鏈表,我暫時理解為用于存放回應了DOWN的事件
private TouchTarget mFirstTouchTarget;
public boolean dispatchTouchEvent(MotionEvent ev) {
...
//判斷是否是模糊視窗,如果是視窗,則表示不希望處理改事件,(如dialog后的視窗)
if (onFilterTouchEventForSecurity(ev)) {
// 清空之前的狀態
if (actionMasked == MotionEvent.ACTION_DOWN) {}
//檢查是否需要攔截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {} else {}
//檢查是否要取消,即標記了PFLAG_CANCEL_NEXT_UP_EVENT 或者 當前是一個Cancel事件
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
//不用取消,無需攔截,則進行事件分發
if (!canceled && !intercepted) {
//分發DOWN事件
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//分發DOWN給child
if (newTouchTarget == null && childrenCount != 0) {
}
//如果沒有child相應該事件,則將此事件交給最近加入的target?
//這里不是很懂,如果沒有child回應,那么mFirstTouchTarget也是null呀
if (newTouchTarget == null && mFirstTouchTarget != null) {
}
}
}
//mFirstTouchTarget為空表明沒有child回應這個事件,則分發給自己
if (mFirstTouchTarget == null) {}
//按照mFirstTouchTarget分發
else {}
}
...
//如果自身或者child消費了事件則回傳true,否則回傳false
return handled;
}
關于決議看代碼中的注釋,接下來看看其中幾段邏輯具體怎么實作的,
具體分析一
看一下分發給DOWN給子View的具體邏輯:
//分發DOWN給child
if (newTouchTarget == null && childrenCount != 0) {
// 對子Views進行排序,有兩種方式:1、按照Z軸,2、按照draw
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//遍歷子View
for (int i = childrenCount - 1; i >= 0; i--) {
//這里兩行代碼,簡單的理解根據不同的排列選項(1、view添加到 2、view的draw順序 3、viewZ 軸順序)
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//canViewReceivePointerEvents 判斷child是否為visiable 或者 是否有影片
//isTransformedTouchPointInView 判斷x, y是否在view的區域內(如果是執行了補間影片 則x,y會通過獲取的matrix變換值
// 換算當相應的區域,這也是為什么補間影片的觸發區域不隨著影片而改變)
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
//如果chile已經在mFirstTouchTarget單鏈表里面,結束回圈
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;
}
//判斷child的dispatchTouchEvent()是否會回傳true,如果是true,將child加入單鏈表,然后結束回圈
//dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)會呼叫child的dispatchTouchEvent()
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// 找到childIndex所代表的child的最原始的index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
//將相應該事件的child包裝成一個Target,添加到mFirstTouchTarget鏈表中
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
這里我只貼出部分的代碼,決議都在注釋,可以看到,mFirstTouchTarget鏈表只存在一個值,就是回應了事件的那個child,
具體分析二
//這一段的要么分發down給自己要么按照單鏈表分發move、up
//偽代碼
//
if (mFirstTouchTarget == null) {
//沒有child回應事件,則分發給自己,handled將作為回傳值回傳,
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;
//在前面Down事件處理中,已經將這個事件交給newTouchTarget處理過了,就不重復處理了
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//這里其實是分發move和up事件,因為down事件在前面已經處理完了,不會進入這里
//再次判定是否需要cancel,因為有可能在move程序事件被攔截
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//如果cancel,回收鏈表節點空間,最后使mFirstTouchTarget置null
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
View的dispatchTouchEvent()原始碼
同樣的,我省略了部分代碼,決議看注釋
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
if (onFilterTouchEventForSecurity(event)) {
//如果設定了onTouchListener,會先呼叫onTouch()
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//呼叫onTouchEvent()
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
可以看到:
- View的dispatchTouchEvent()的回傳值取決于onTouch()或onTouchEvent(),也就是有沒有消費該事件,
- 如果onTouch()回傳true的話就不會呼叫onTouchEvent(),這就是為什么有些博客寫onTouch()優先于onTouchEvent(),
總結
分析原始碼就可以知道前面說的三種場景是怎么回事了,
有點像皇帝派任務,皇帝說現在有個好活兒,但是不知道誰要接這個活兒,于是派了一個小太監去探測一下;
小太監先去找宰相,宰相又讓他去找知府,知府讓他去找衙門小兵,
這里面皇帝就像Acticity、各級官員就像ViewGroup、小兵就像View、而小太監就像DOWN事件,活兒就是跟著DOWN后面的MOVE和UP事件,
- 不攔截、不消費:小太監一層層找到小兵后,沒有一個小兵想接這個活兒(一層層分發DOWN事件),于是小兵沿路回傳報告給知府、知府也不想做就報告給宰相,宰相不想做就回去報告給皇帝,皇帝說沒人做那我看看自己能不能做吧(DOWN事件回到Activity派給自己,MOVE、UP也不再分發而是直接派給自己)
- ViewGroup攔截、不消費:宰相讓小太監找到知府的時候, 這個知府有點霸道直接把小太監攔下了,小太監就就不再繼續通知下級人員了(攔截事件),但是呢這個官員只是單純攔下了小太監但他并不想接這個活兒,于是小兵還是沿路回去報告給說下面沒人接活,最侄訓是傳回給皇帝說沒人做那我看看自己能不能做(MOVE、UP不再分發直接派給自己)
- ViewGroup消費、不攔截:同樣,無人攔截的話,小太監一層層找到小兵,發現小兵沒人想做,就回去報告知府,這時候知府說小兵不做我來做(DOWM在這里被消費了),然后知府寫信報告宰相說這活兒我接了(回傳true),宰相又報告皇帝說下面有人接受任務了,然會皇帝下次就直接派發任務給宰相,宰相找到那個愿意接受任務知府,把任務派給他,(派發MOVE、UP)
分發函式分發DOWN時其實有點類似于遞回的方式,只不過不是自己呼叫自己,而是一層層地呼叫child的同名函式,分發MOVE、UP則不再一一詢問,而是根據DOWN是否被消費進行分發,
原始碼很長現在頭都有點亂,那么從原始碼可以學習到什么呢,大家幫忙補充吧
- 如有有這種嵌套式的應答需求,可以學習ViewGroup的分發函式,用類似于遞回的方式提問和接收應答,
- 對于較大量的資訊,命令傳送,可以先派一個小兵嗅探一下,記錄可行的路線,后續資訊按路線分發,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/232480.html
標籤:其他
上一篇:一文看懂h5+app拍照上傳圖片
