Android事件分發(3)--ViewGroup原始碼分析
一、ViewGroup的onInterceptTouchEvent原始碼分析
onInterceptTouchEvent比較簡單先看他的原始碼
public boolean onInterceptTouchEvent(MotionEvent ev) {
//1、判斷是否是滑鼠裝置操作
//2、ACTION_DOWN事件
//3、是否是首要按鈕按下,如滑鼠左鍵
//4、是否是滑動條
//看意思以上是相容安卓非手機裝置用的判斷,滿足的話,就返回true,攔截事件分發
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
//預設返回false不攔截事件分發
return false;
}
onInterceptTouchEvent 作用就是要不要攔截向子View分發事件,如果想攔截的話,需要重寫onInterceptTouchEvent。
二、ViewGroup的dispatchTouchEvent原始碼分析
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//如果輸入一致性檢查事件不為null,先執行輸入前後一致性檢查
//原始碼英文註釋說是為了debug的目的,估計是設定斷點後,再次執行的時候,為了不丟失事件,檢查是否是同一個輸入,可忽略
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
//如果事件物件是容易獲取焦點的view,開始普通事件分發,可能是某個後代會處理點選。
//貌似是用來判斷 motion event是否設定了容易獲取焦點Flags
//這個flags是FLAG是_WINDOW_IS_OBSCURED,原始碼意思大約是設定事件物件,是否部分或者全部被遮擋到了
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
//handled 是最終要返回的結果值
boolean handled = false;
//onFilterTouchEventForSecurity 對事件進行安全檢查,如果true可執行,false就放棄
//onFilterTouchEventForSecurity 裡面進行了是否要遮擋進行過濾,以及event的物件是否被遮擋的兩個判斷
if (onFilterTouchEventForSecurity(ev)) {
//未被遮擋進入了此if內部
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
//處理最初的ACTION_DOWN事件
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.
//當開始了一個新的手勢,拋棄了之前所有的狀態
//由於app切換,ANR或者其他狀態改變,框架可能已經放棄了對前一個手勢的Up 和cancel的處理
//cancelAndClearTouchTargets 清空了之前所有的找到的觸控物件,裡面呼叫了dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);對子View的事件進行了取消,傳遞的第二個引數true 是是否取消的標誌
//裡面呼叫了clearTouchTargets 對比較重要的mFirstTouchTarget物件賦值null
//mFirstTouchTarget的英文註釋 First touch target in the linked list of touch targets.是第一個找到的觸控物件
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//檢查是否攔截
// Check for interception.
final boolean intercepted;
//第一種情況ACTION_DOWN的時候判斷攔截,
//第二種情況,已經找到了要處理事件的字View或者子ViewGroup
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//之後又對disallowIntercept進行了判斷,disallowIntercept的意思是,是否禁用攔截,disallowIntercept可以使用requestDisallowInterceptTouchEvent方法自己進行設定,後面會具體分析
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//沒有禁用攔截,就會根據攔截函式onInterceptTouchEvent的返回值,進行攔截判斷,是否攔截
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed恢復action,防止action被改變了
} else {//禁止攔截,所以設定攔截標識為不攔截
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
//沒有找到處理MotionEvent的目標物件,而且也不是最初的ACTION_DOWN,也就是現在是up 或者move事件了
//mFirstTouchTarget 為null說明自己沒有需要處理事件的物件,也就是自己這個ViewGroup下沒有View要處理事件,所以攔截
//
intercepted = true;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// 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;
if (!canceled && !intercepted) {//沒被取消或攔截
// If the event is targeting accessiiblity focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
//ACTION_POINTER_DOWN 是說在有一個觸控點的情況下,又有手指觸控式螢幕幕。多點觸控的判斷
//主要關心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);
//有效的子View的數量
final int childrenCount = mChildrenCount;
//newTouchTarget 為null且子view數量不為0
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
//找到一個能接收事件的子View,檢視層次上從上層到下層的順序查詢
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//是子View倒序遍歷子View,因為在新增子view到group的過程中,先新增的顯示在底層,後新增的顯示在上層,所以查詢的時候,先從可見的上層查詢起
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
//
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
//判斷子View能不能收到觸控事件
//判斷自VIew是不是在觸控範圍內 如果不滿足,就不檢查了
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//子View滿足可觸控和在觸控範圍內
//找到某一指定的子view的TouchTarget物件,傳遞引數有子View,返回子VIew的TouchTarget物件
newTouchTarget = getTouchTarget(child);
//沒有找到子View的TouchTarget物件的處理
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;
}
resetCancelNextUpFlag(child);
//把經過一定改變的MotionEvent傳遞給子View的相對應的區域,是在這個方法裡,進行的子View的分發事件的呼叫。稍後單獨分析。
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
//返回true表示子View想處理消費事件,
mLastTouchDownTime = ev.getDownTime();
//找到消費事件的view的index
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();
//把要消費事件的子View的TouchTarget物件放到list開頭
//addTouchTarget 裡面對mFirstTouchTarget進行了賦值,並返回,mFirstTouchTarget和newTouchTarget 此時指向相同
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//已經分發標識設定為true 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);
}
if (preorderedList != null) preorderedList.clear();
}
//沒有找到消費事件的子View
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.pointerIdBits |= idBitsToAssign;
}
}
}
//以上是對 不攔截,未取消事件的處理和分發 ,如果找到了要消費事件的view, 那麼mFirstTouchTarget 就不應該為空了
//mFirstTouchTarget 為null 一是可能事件都被攔截了,二是沒有找到要消費事件的子View
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
//沒有觸控物件,把它當成原始的view來處理
//第三個引數傳遞是null,在裡面判斷了null會呼叫view的dispatchTouchEvent方法,也就是把ViewGroup當成view處理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//有子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;
//alreadyDispatchedToNewTouchTarget為true是在找到newTouchTarget的時候設定的
//且alreadyDispatchedToNewTouchTarget設定為true是在ACTION_DOWN的if判斷裡面的,它消費了ACTION_DOWN事件
//所以返回handled = true表示已經有子view消費事件了
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
// 對於非ACTION_DOWN事件,則繼續傳遞給目標子元件進行處理(注意這裡的非ACTION_DOWN事件已經不需要再判斷是否攔截)
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
// 如果target子元件進行處理,符合某些條件的話,會傳遞ACTION_CANCEL給target子元件
// 條件是:如果ACTION_DOWN時沒有被攔截,而後面的touch事件被攔截,則需要傳送ACTION_CANCEL給target子元件
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
//ACTION_CANCEL或者ACTION_UP,重置touch 的狀態
//mFirstTouchTarget重置為null
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;
}
相關推薦
Android事件分發(3)--ViewGroup原始碼分析
一、ViewGroup的onInterceptTouchEvent原始碼分析 onInterceptTouchEvent比較簡單先看他的原始碼 public boolean onInterceptTouchEvent(MotionEvent ev
android事件分發(二)
sim tdi p s oat front rac ram addclass framework 非常早之前寫過一篇android事件分發的博客,主要寫的是它是怎樣分發的,具體非常多原理的東西都沒有涉及到。今天就從源代碼看android怎樣控制它的分發機
android事件分發(三)重要的函式requestDisallowInterceptTouchEvent
事件分發在android中非常重要,寫了3篇文章總結其中的故事 具體實現 前面我們說過,兒子吃到肉了,父親還可能搶那麼兒子有沒有辦法不讓父親搶呢,有? 可以通過呼叫mParent.requestDisallowInterceptTouchEvent(true),
android原始碼分析之View的事件分發(上)
1、View的繼承關係圖 View的繼承關係圖如下: 其中最重要的子類為ViewGroup,View是所有UI元件的基類,而ViewGroup是容納這些元件的容器,同時它也是繼承於View類。而UI元件的繼承關係如上圖,比較常用的元件類用紅色字型標出
【朝花夕拾】Android自定義View篇之(六)Android事件分發機制(中)從原始碼分析事件分發邏輯及經常遇到的一些“詭異”現象
前言 轉載請註明,轉自【https://www.cnblogs.com/andy-songwei/p/11039252.html】謝謝! 在上一篇文章【【朝花夕拾】Android自定義View篇之(
Android的事件分發(dispatchTouchEvent),攔截(onInterceptTouchEvent)與處理(onTouchEvent)
在Android中,View的結構是樹狀的,所以,當觸發觸控事件的時候,其事件傳遞也是從上之下一層層的傳遞。下面我們結合例子來一點點進行分析。 首先,我們需要了解事件處理中的幾個方法: 1、在ViewGroup中,事件分為dispatchTouchEvent(事件的分發)
cocos2dx學習原始碼之介面iOS事件分發(2)
今天看看事件是如何分發的。程式碼版本:cocos2dx-3.9 上次說到GLView接手touch事件:handleTouchesBegin。 原始碼: void GLView::handleTou
Android touch 事件分發 (一)Activity dispatchTouchEvent
/** * You can override this to intercept all touch screen events before they are dispatched to the window <br> * * @return f
GeoMesa編譯與二次開發專欄(3) — GeoMesa原始碼編譯
前言 1、參考:GeoMesa官方英文文件:https://www.geomesa.org/documentation/developer/introduction.html 2、本篇介紹瞭如何編譯GeoMesa原始碼,即官方文件中所謂的從原始碼構建GeoMesa,以及對GeoM
android影象處理(3)浮雕效果
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!  
android影象處理(3)底片效果
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!  
android四大元件(3)(BroadcastReceiver)
一、aandroid的四大元件BroadcastReceiver廣播接收器(用於接收程式所發出的Broadcast Intent),其本生就是一個全域性的監聽器,用於監聽系統全域性的廣播訊息。由於BroadcastReceiver是全域性的監聽器,所以它可以非常方便地實現系統中不同元件之間地通訊。
Android事件匯流排(一)EventBus3.0用法全解析
前言 EventBus是一款針對Android優化的釋出/訂閱事件匯流排。簡化了應用程式內各元件間、元件與後臺執行緒間的通訊。優點是開銷小,程式碼更優雅,以及將傳送者和接收者解耦。如果Activity和Activity進行互動還好說,如果Fragmen
寫給Android App開發人員看的Android底層知識(3)
(七)App啟動流程第2篇 書接上文,App啟動一共有七個階段,上篇文章篇幅所限,我們只看了第一階段,接下來講剩餘的六個階段,仍然是拿鬥魚App舉例子。 簡單回顧一下第一階段的流程,就是Launcher向AMS傳送一個跨程序通訊,通過AMN/AMP,告訴A
android-進階(3)-自定義view(2)-Android中View繪製流程以及相關方法的分析
最近正在學自定義view,這篇文章主要講view的繪製流程和一些相關的方法,淺顯易懂,寫的非常好,忍不住就轉載了。 前言: 本文是我讀《Android核心剖析》第13章----View工作原理總結而成的,在此膜拜下作者 。
WSO2 ——(3)ESB 原始碼編譯
WSO2 ESB編譯真是費勁,編譯得花費四五個小時。先是編譯4.8.0版本,缺少各種jar包轉戰到4.7.0版本,最後發現原來是公司網路原因,崩潰。4.7.0版本網上有人已經編譯成功,又花了幾天時間終於編譯完成了。 1 幾個概念 1.1 WSO2 ESB wso2的一個產
Android framework回顧(3)binder利用及IBinder BpRefbase IInterface INTERFACE 之間關係
status_t AudioSystem::setStreamVolumeIndex(audio_stream_type_t stream, int index, audio_devices_t device){ const sp<IAudioPolicySe
android-進階(3)-自定義view(1)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:custom="http://sch
HLS學習(四)HLSDownloader原始碼分析(3)總體流程
總體流程 下載一個媒體檔案的流程 1、初始化 2、根據URL下載m3u8檔案(即PlayList檔案) 3、判斷m3u8檔案的型別,是Master PlayList還是Media PlayList 4、如果是Master PlayList,那麼分析這個PlayList
android launcher開發(3)初始化介面
初始化執行環境 LauncherAppState.setApplicationContext(getApplicationContext()); LauncherAppState app = LauncherAppState.getIns