Android事件分發(fā)機(jī)制全面解析
事件分發(fā)機(jī)制
事件分發(fā)機(jī)制的兩個(gè)階段:
- 分發(fā):事件從父視圖往子視圖分發(fā),被攔截后不再傳遞,進(jìn)入回溯階段
- 回溯:事件從子視圖往父視圖回溯,被消費(fèi)后不再回溯
關(guān)鍵方法:
- ViewGroup.dispatchTouchEvent 往子視圖分發(fā)事件
- ViewGroup.onInterceptTouchEvent 返回 true 表示攔截分發(fā)事件,不再傳遞,進(jìn)入當(dāng)前視圖 onTouchEvent
- View.dispatchTouchEvent 默認(rèn)事件分發(fā),調(diào)用 onTouchEvent
- View.onTouchEvent 通常重載此方法處理事件,返回 true 表示消費(fèi)事件,不再傳遞,返回 false 往上回溯
- ViewParent.requestDisallowInterceptTouchEvent(true) 可以確保事件分發(fā)到子視圖前不被攔截
假設(shè)視圖層次為 A.B.C.D,事件分發(fā)回溯默認(rèn)過程為:
A.dispatchTouchEvent B.dispatchTouchEvent C.dispatchTouchEvent D.dispatchTouchEvent D.onTouchEvent C.onTouchEvent B.onTouchEvent A.onTouchEvent
假設(shè) B 攔截了事件:
A.dispatchTouchEvent B.dispatchTouchEvent -> B.onInterceptTouchEvent B.onTouchEvent A.onTouchEvent
假設(shè) C.onTouchEvent 消費(fèi)了事件:
A.dispatchTouchEvent B.dispatchTouchEvent C.dispatchTouchEvent D.dispatchTouchEvent D.onTouchEvent C.onTouchEvent
事件分發(fā)機(jī)制偽代碼:
class Activity {
fun dispatchTouchEvent(ev) {
if (parent.dispatchTouchEvent(ev)) {
return true
}
return onTouchEvent(ev)
}
fun onTouchEvent(ev):Boolean {...}
}
class ViewGroup : View {
fun dispatchTouchEvent(ev) {
var handled = false
if (!onInterceptTouchEvent(ev)) {
handled = child.dispatchTouchEvent(ev)
}
return handled || super.dispatchTouchEvent(ev)
}
fun onInterceptTouchEvent(ev):Boolean {...}
fun onTouchEvent(ev):Boolean {...}
}
class View {
fun dispatchTouchEvent(ev) {
var result = false
if (handleScrollBarDragging(ev)) {
result = true
}
if (!result && mOnTouchListener.onTouch(ev)) {
result = true
}
if (!result && onTouchEvent(ev)) {
result = true
}
return result
}
fun onTouchEvent(ev):Boolean {...}
}
ViewGroup.dispatchTouchEvent 源碼分析
1.開始:ACTION_DOWN 事件開始一個(gè)新的事件序列,清除之前觸摸狀態(tài)
2.攔截:
2.1. 非 ACTION_DOWN 事件如果當(dāng)前沒有子視圖消費(fèi)事件,表示事件序列已被攔截
2.2. 事件未被攔截且子視圖未申請(qǐng)禁止攔截時(shí),再通過 onInterceptTouchEvent 嘗試攔截事件
3.分發(fā):如果事件未被攔截也未被取消,就遍歷子視圖分發(fā)事件,并尋找當(dāng)前事件的觸摸目標(biāo)
3.1. 在觸摸目標(biāo)鏈表中找到了可以消費(fèi)當(dāng)前事件的視圖觸摸目標(biāo) -> 將其標(biāo)記為當(dāng)前觸摸目標(biāo),延遲到步驟4分發(fā)事件給它
3.2. 一個(gè)不在觸摸目標(biāo)鏈表中的視圖消費(fèi)了事件 -> 將其標(biāo)記為當(dāng)前觸摸目標(biāo),并設(shè)置為觸摸目標(biāo)鏈表表頭
3.3. 未找到消費(fèi)當(dāng)前事件的視圖,但觸摸目標(biāo)鏈表不為空 -> 將觸摸目標(biāo)鏈表末端標(biāo)記為當(dāng)前觸摸目標(biāo)
4.分發(fā):觸摸目標(biāo)鏈表不為空,則遍歷觸摸目標(biāo)鏈嘗試傳遞事件或取消觸摸目標(biāo)(事件被攔截)
5.回溯:觸摸目標(biāo)鏈表為空(當(dāng)前沒有子視圖消耗事件序列),則將事件轉(zhuǎn)發(fā)給基類 dispatchTouchEvent 處理
注:觸摸目標(biāo)(ViewGourp.TouchTarget) 描述一個(gè)被觸摸的子視圖和它捕獲的指針ids
public boolean dispatchTouchEvent(MotionEvent ev) {
// 省略代碼 ...
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 1. `ACTION_DOWN` 事件開始一個(gè)新的事件序列,清除之前觸摸狀態(tài) ...
}
// 省略代碼 ...
final boolean intercepted;
// 2. 攔截
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// 2.2. 事件未被攔截且子視圖未申請(qǐng)禁止攔截時(shí),再通過 onInterceptTouchEvent 嘗試攔截事件
intercepted = onInterceptTouchEvent(ev);
// 省略代碼 ...
} else {
intercepted = false;
}
} else {
// 2.1. 非 `ACTION_DOWN` 事件如果當(dāng)前沒有子視圖消費(fèi)事件,表示事件序列已被攔截
intercepted = true;
}
// 省略代碼 ...
if (!canceled && !intercepted) {
// 省略代碼 ...
// 3. 分發(fā):如果事件未被攔截也未被取消,就遍歷子視圖分發(fā)事件,并尋找當(dāng)前事件的觸摸目標(biāo)
for (int i = childrenCount - 1; i >= 0; i--) {
// 省略代碼 ...
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// 3.1. 在觸摸目標(biāo)鏈表中找到了可以消費(fèi)當(dāng)前事件的視圖觸摸目標(biāo) -> 將其標(biāo)記為當(dāng)前觸摸目標(biāo),延遲到步驟4分發(fā)事件給它
// 省略代碼 ...
break;
}
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 省略代碼 ...
// 3.2. 一個(gè)不在觸摸目標(biāo)鏈表中的視圖消費(fèi)了事件 -> 將其標(biāo)記為當(dāng)前觸摸目標(biāo),并設(shè)置為觸摸目標(biāo)鏈表表頭
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// 省略代碼 ...
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
// 3.3. 未找到消費(fèi)當(dāng)前事件的視圖,但觸摸目標(biāo)鏈表不為空 -> 將觸摸目標(biāo)鏈表末端標(biāo)記為當(dāng)前觸摸目標(biāo)
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
// 省略代碼 ...
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// 5. 回溯:觸摸目標(biāo)鏈表為空(當(dāng)前沒有子視圖消耗事件序列),則將事件轉(zhuǎn)發(fā)給基類 dispatchTouchEvent 處理
handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
} else {
// 省略代碼 ...
// 4. 分發(fā):觸摸目標(biāo)鏈表不為空,則遍歷觸摸目標(biāo)鏈嘗試傳遞事件或取消觸摸目標(biāo)(事件被攔截)
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
// 省略代碼 ...
if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
handled = true;
}
// 省略代碼 ...
target = next;
}
}
// 省略代碼 ...
}
// 省略代碼 ...
return handled;
}
View.dispatchTouchEvent 和 View.onTouchEvent 源碼分析
- 滾動(dòng)條消費(fèi)鼠標(biāo)事件
- OnTouchListener 消費(fèi)觸摸事件
- onTouchEvent 消費(fèi)觸摸事件
TouchDelegate 消費(fèi)觸摸事件
public boolean dispatchTouchEvent(MotionEvent event) {
// 省略代碼 ...
boolean result = false;
// 省略代碼 ...
if (onFilterTouchEventForSecurity(event)) {
// 滾動(dòng)條消費(fèi)鼠標(biāo)事件
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
// OnTouchListener 消費(fèi)觸摸事件
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// View默認(rèn)的事件處理邏輯,事件可能在其中被設(shè)置的 TouchDelegate 消費(fèi)
if (!result && onTouchEvent(event)) {
result = true;
}
}
// 省略代碼 ...
return result;
}
public boolean onTouchEvent(MotionEvent event) {
// 省略代碼 ...
if (mTouchDelegate != null) {
// TouchDelegate 消費(fèi)觸摸事件
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
// 省略代碼 ...
return false;
}
以上就是Android事件分發(fā)機(jī)制全面解析的詳細(xì)內(nèi)容,更多關(guān)于Android事件分發(fā)機(jī)制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android實(shí)現(xiàn)ListView的A-Z字母排序和過濾搜索功能 實(shí)現(xiàn)漢字轉(zhuǎn)成拼音
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)ListView的A-Z字母排序和過濾搜索功能,實(shí)現(xiàn)漢字轉(zhuǎn)成拼音功能2017-06-06
android實(shí)現(xiàn)系統(tǒng)信息推送
這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)系統(tǒng)信息推送,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04
android里面屏蔽home鍵/禁止Home鍵或者隨你DIY
可以先禁止Home鍵,再在onKeyDown里處理按鍵值,點(diǎn)然后在擊Home鍵的時(shí)候就把程序關(guān)閉,或者隨你DIY等等,感覺你可以隨心所欲吧,再接再厲,希望本文可以幫助到你2013-01-01
Android實(shí)戰(zhàn)教程第七篇之如何在內(nèi)存中存儲(chǔ)用戶名和密碼
這篇文章主要為大家詳細(xì)介紹了Android如何實(shí)現(xiàn)在內(nèi)存中存儲(chǔ)用戶名和密碼的相關(guān)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
android中g(shù)zip數(shù)據(jù)壓縮與網(wǎng)絡(luò)框架解壓縮
這篇文章主要為大家介紹了android中g(shù)zip數(shù)據(jù)壓縮與網(wǎng)絡(luò)框架解壓縮實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
Android ChipGroup收起折疊效果實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了Android ChipGroup收起折疊效果實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
NestedScrollView+Recyclerview下滑卡頓解決方法
本文為大家解決安卓開發(fā)時(shí)候NestedScrollView+Recyclerview下滑卡頓的問題,希望能夠幫助到你。2017-11-11
Cocos2d-x入門教程(詳細(xì)的實(shí)例和講解)
這篇文章主要介紹了Cocos2d-x入門教程,包括詳細(xì)的實(shí)例、講解以及實(shí)現(xiàn)過程,需要的朋友可以參考下2014-04-04

