網站首頁 編程語言 正文
前言:
事件分發從手指觸摸屏幕開始,即產生了觸摸信息,被底層系統捕獲后會傳遞給Android的輸入系統服務IMS
,通過Binder把消息發送到activity,activity會通過phoneWindow、DecorView最終發送給ViewGroup。這里就直接分析ViewGroup的事件分發
整體流程
配合圖在看一段偽代碼:
public boolean dispatchTouchEvent(MotionEvent ev) :Boolean{ val result = false //處理結果,默認是沒消費過的 if (!onInterceptTouchEvent(ev)){ //是否攔截 result = child.dispatchTouchEvent(ev) // 分發給子view處理 } if (!result){ //事件沒有消費 if (onTouchListener != null) { //先詢問是否設置了onTouchListener result = onTouchListener.onTouch(ev) } if (!result) { //還是沒有消費就交給onTouchEvent處理 result = onTouchEvent(ev) } } return result }
這張圖和這段偽代碼實際上已經概括了ViewGroup和View對事件處理的整個流程,注意只有ViewGroup有攔截機制即onInterceptTouchEvent
源碼分析
在分析源碼之前先了解個基本概念?同一事件序列
:同一個事件序列是指從手指接觸屏幕的那一刻起,到手指離開屏幕的那一刻結束,在這個過程中所產生的一系列事件,這個事件序列以down事件開始,中間含有數量不定的move事件,最終以up事件結束
public boolean dispatchTouchEvent(MotionEvent ev) { boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; /** * step1 * ACTION_DOWN是一個系列事件的起點,終點是ACTION_UP * 如果是ACTION_DOWN會重置一些flag并且會把mFirstTouchTarget置空 */ if (actionMasked == MotionEvent.ACTION_DOWN) { cancelAndClearTouchTargets(ev); resetTouchState(); } final boolean intercepted;//變量判斷消息是否被攔截 /** * step2 * 從以下代碼可以看出如果事件不是ACTION_DOWN并且mFirstTouchTarget為空的話那么ViewGroup是不能再攔截同一系列的事件了 * mFirstTouchTarget 代表的就是一個單鏈表,它會把處理當前這一系列事件的view保存下來 * 假如當前事件是ACTION_MOVE,并攔截了該事件那么會在step9中把mFirstTouchTarget置空 * * 結論1: * 如果View決定攔截一個事件那么該View的 onInterceptTouchEvent 方法不會再被調用了, * 同一序列事件后續的所有事件都只能由該View處理(當然前提是事件能分發到該view,有可能在上層被攔截了) */ if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { /** * disallowIntercept表示是否禁用攔截功能,子view通過 requestDisallowInterceptTouchEvent 方法 * 可以要求父view不準攔截事件,不過該方法在MotionEvent.ACTION_DOWN事件中不起作用,因為在step1中會把所有標志位重置 * */ 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 { /** * 如果進不到上面的if判斷則表示當前系列事件viewGroup已經攔截過某個事件了 * intercepted 直接置為true */ intercepted = true; } final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE; final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0 && !isMouseEvent; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; /** * step3 * 看這里如果ViewGroup攔截了該事件則不會進入step3里面了,而是直接走到step9中 */ if (!canceled && !intercepted) { /** * step4 * 這里我們只考慮單指的點擊、移動和抬起 * ACTION_POINTER_DOWN和多點觸控有關,ACTION_HOVER_MOVE和鼠標有關 * 所以如果當前事件是MOVE也不會走step4也是直接走到step9中找到對應的子view繼而分發事件 * 結論2:如果DOWN事件被某個view消耗那么后續的事件都會直接交給這個view(前提是父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 final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS; final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex); final float y = isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex); // Find a child that can receive the event. // Scan children from front to back. final ArrayList<View> preorderedList = buildTouchDispatchChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; /** * step5 * 遍歷所有的子view */ for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); //省略部分代碼。。。 /** * step6 * 當找到一個合適的子view時,在 dispatchTransformedTouchEvent 中會調用子view的dispatchTouchEvent * 如果該子view消耗了事件,會把子view保存到mFirstTouchTarget對應的鏈表中,并結束for循環 * * 結論3: * 如果一個view沒有消耗DOWN事件那么后續的事件都不會再分發給該view * 該結論和結論2呼應上了,因為在這個for循環中只有子view的 dispatchTransformedTouchEvent返回true才會被加入到鏈表中 * 下一次的事件并不會再到step4中來了 */ 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(); /** * step7 * 把子view保存到鏈表中,mFirstTouchTarget指向表頭 * alreadyDispatchedToNewTouchTarget置為true * 結束for循環 */ 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); } } } } /** * step8 * 如果攔截了事件會把 mFirstTouchTarget 置空這個時候就直接調用viewGroup的super.dispatchTouchEvent * 即view中的dispatchTouchEvent */ 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. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; /** * step9 * 如果攔截了就把mFirstTouchTarget置空,沒有攔截就找到對應的childView把事件分發下去 */ while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; //注意這里cancelChild如果為true,并且target.child不為空的話,dispatchTransformedTouchEvent會把事件轉成CANCEL分發給target.child 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; } } } return handled; }
看下cancel事件的由來,這里需要結合上文代碼step9看
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { /** * 把事件轉換成ACTION_CANCEL */ event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { /** * 如果child不為空就分發給它 */ handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } return handled; }
原文鏈接:https://juejin.cn/post/7140191790690205726
相關推薦
- 2022-09-03 解決Python3錯誤:SyntaxError:?unexpected?EOF?while?pars
- 2022-05-23 Python+OpenCV實現在圖像上繪制矩形_python
- 2022-05-10 uniapp中的@tap和@click的區別
- 2023-07-28 el-table 默認勾選數據
- 2023-01-08 C#?Winform文本面板帶滾動條的實現過程_C#教程
- 2022-06-01 kubernetes中的namespace、node、pod介紹_云和虛擬化
- 2023-07-02 oracle實現根據字段分組排序,取其第一條數據_oracle
- 2022-04-09 SpringBoot 動態過濾自動配置類
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支