Andorid事件分發
- 引言
-
- 源碼分析
引言
View分發是自定義View上的必經之路,也是比較難的一部分。今天我們從源碼的角度來分析一下View的分發機制
源碼分析

(ps:圖檔來源于hwldzh)
從圖檔來看 很難看到時間分發的核心在哪裡,想了解一個東西流程以及過程最好的辦法就是看他整個事件的過程流程以及思路,下面我們開始分析。
1.View,ViewGroup事件分發
1.Touch事件分發中隻有兩個主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三個相關事件。View包含dispatchTouchEvent、onTouchEvent兩個相關事件。其中ViewGroup又繼承于View。
2.ViewGroup和View組成了一個樹狀結構,根節點為Activity内部包含的一個ViwGroup。
3.觸摸事件由Action_Down、Action_Move、Aciton_UP組成,其中一次完整的觸摸事件中,Down和Up都隻有一個,Move有若幹個,可以為0個。
4.當Acitivty接收到Touch事件時,将周遊子View進行Down事件的分發。ViewGroup的周遊可以看成是遞歸的。分發的目的是為了找到真正要處理本次完整觸摸事件的View,這個View會在onTouchuEvent結果傳回true。
5.當某個子View傳回true時,會中止Down事件的分發,同時在ViewGroup中記錄該子View。接下去的Move和Up事件将由該子View直接進行處理。由于子View是儲存在ViewGroup中的,多層ViewGroup的節點結構時,上級ViewGroup儲存的會是真實處理事件的View所在的ViewGroup對象:如ViewGroup0-ViewGroup1-TextView的結構中,TextView傳回了true,它将被儲存在ViewGroup1中,而ViewGroup1也會傳回true,被儲存在ViewGroup0中。當Move和UP事件來時,會先從ViewGroup0傳遞至ViewGroup1,再由ViewGroup1傳遞至TextView。
6.當ViewGroup中所有子View都不捕獲Down事件時,将觸發ViewGroup自身的onTouch事件。觸發的方式是調用super.dispatchTouchEvent函數,即父類View的dispatchTouchEvent方法。在所有子View都不處理的情況下,觸發Acitivity的onTouchEvent方法。
7.onInterceptTouchEvent有兩個作用:1.攔截Down事件的分發。2.中止Up和Move事件向目标View傳遞,使得目标View所在的ViewGroup捕獲Up和Move事件。
2.1 事件分發的流程
從你手點選螢幕的那一刻起事件會傳遞到目前View所在的Activity中由Activity
的dispathOnTouchEvent進行分發
先來看看dispathOnTouchEvent的源碼
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
我們看到目前的代碼
getWindow().superDispathTouChEvent(ev)
首先調用了getWindow(). superDispatchTouchEvent方法,傳回true表示事件被消耗掉了;傳回false表示事件交給Activity的onTouchEvent方法處理。
這段代碼的調用了 getWindow 下面的分發,Window是一個類。在A濃度日的中
PhoneWindow 是Window的唯一實作類。
下面是Window類中的方法
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
該類調用了mDecor.superDispatchTouchEvent(event); 方法 mDecor是什麼?
這裡的mDecor是DecorView類型,DecorView是activity視窗的根視圖
我們來看看mDecor.superDispatchTouchEvent(event);又實作了什麼内容?
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
調用了super.dispatchTouchEvent(event); 方法 DecorView的父類就是ViewGroup
是以這一塊是調用了ViewGroup的 dispathTouchEvent(event)方法,所有已經傳遞到頂級的ViewGroup中了
2.2 ViewGroup的分發
下面我看一看ViewGroup中分發代碼重要部分
final boolean intercepted; //記錄是否被攔截
//目前事件為ACTION_DOWN時事件時,或者 mFirstTouchTarget 不為空(mFirstTouchTarget 下文會提到)
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
//由于事件為ACTION_DOWN時,重置了mGroupFlags,是以一定會調onInterceptTouchEvent方法。
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//調用onInterceptTouchEvent方法,判斷是否需要攔截該事件
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
mFirstTouchTarget != null,是什麼?用來記錄目前事件是否被子View消費了,
或者之前的某次事件已經經由此ViewGroup派發給children後被處理掉了(PS:經過一次處理之後以後的每次分發都交由上個處理的View 進行 事件的處理)
由于事件為ACTION_DOWN事件時,重置mGroupFlages。字面意思來看使用者
按下這一刻這個參數被清空了。其實在這段代碼之前還有一段 清空代碼!來看看
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) { // 一堆touch事件(從按下到松 手)中的第一個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.
cancelAndClearTouchTargets(ev);
resetTouchState(); // 作為新一輪的開始,reset所有相關的狀态
}
2.2.1ViewGroup進行攔截
intercepted = onInterceptTouchEvent(ev);
這段代碼是ViewGroup進行攔截時候進入的方法
我們看看這其中的代碼片段
if (!canceled && !intercepted) {
//目前為 true 是以不會進入到這裡
...省略部分代碼下文展出完整代碼
}
if (mFirstTouchTarget == null) {
// 沒有children處理則派發給自己處理
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
...省略
}
mFirstTouchTarget 為空是以 這個事件沒有被子View消費過。是以自己進行處理
其if分支中調用了
dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
注第三個參數為null
重要部分還是在最後面
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
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);
}
}
在這裡判斷了 child 是否為空 是以進入了if分支調用了
super.dispatchTouchEvent(transformedEvent); 自己進行消費
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) { // 一般都成立
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) { // 先在ENABLED狀态下嘗試調用onTouch方法
return true; // 如果被onTouch處理了,則直接傳回true
}
// 從這裡我們可以看出,當你既設定了OnTouchListener又設定了OnClickListener,那麼目前者傳回true的時候,
// onTouchEvent沒機會被調用,當然你的OnClickListener也就不會被觸發;另外還有個差別就是onTouch裡可以
// 收到每次touch事件,而onClickListener隻是在up事件到來時觸發。
if (onTouchEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false; // 上面的都沒處理,則傳回false
}
li.mOnTouchListener != null 這行代碼來判斷的了目前的View是否設定了
OnTouchListener 并且 OnTouch 中反回了true
true說明了 這裡不會再執行View的 onTouchEvent 方法 反之去執行
是以從源碼來分析
OnTouch 優先級 > onTouchEvent 優先級
那我們的onClick 事件的 當然 他的級别是最低的
OnTouch 優先級 > onTouchEvent 優先級 > onClick優先級
2.2.2 ViewGroup不進行攔截 false
intercepted 為false
是以目前進入到
public boolean dispatchTouchEvent(MotionEvent ev) {
if (!canceled && !intercepted) {// 沒取消也不攔截,即是個有效的touch事件
if (actionMasked == MotionEvent.ACTION_DOWN // 第一個手指down
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) // 接下來的手指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);
final int childrenCount = mChildrenCount;
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.
final View[] children = mChildren;
final boolean customOrder = isChildrenDrawingOrderEnabled();
// 從最後一個向第一個找
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder ?
getChildDrawingOrder(childrenCount, i) : i;
final View child = children[childIndex];
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue; // 不滿足這2個條件直接跳過,看下一個child
}
// child view能receive touch事件而且touch坐标也在view邊界内
newTouchTarget = getTouchTarget(child);// 查找child對應的TouchTarget
if (newTouchTarget != null) { // 比如在同一個child上按下了多跟手指
// 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; // newTouchTarget已經有了,跳出for循環
}
resetCancelNextUpFlag(child);
// 将此事件交給child處理
// 有這種情況,一個手指按在了child1上,另一個手指按在了child2上,以此類推
// 這樣TouchTarget的鍊就形成了
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = childIndex;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
// 如果處理掉了的話,将此child添加到touch鍊的頭部
// 注意這個方法内部會更新 mFirstTouchTarget
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true; // down或pointer_down事件已經被處理了
break; // 可以退出for循環了。。。
}
}
}
// 本次沒找到newTouchTarget但之前的mFirstTouchTarget已經有了
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;
}
// while結束後,newTouchTarget指向了最初的TouchTarget
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
這段代碼主要講解了周遊自己的子View 将事件分發下去
對于子View 調用了
(dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) 方法
目前的第三個參數 為自己的子View 是以傳遞了 child 參數 在上文中ViewGroup也調用同樣的方法 不給過第三個參數為 null
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);
}
當child不為空是 調用了 child.dispatchTouchEvent(transformedEvent)
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) { // 一般都成立
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) { // 先在ENABLED狀态下嘗試調用onTouch方法
return true; // 如果被onTouch處理了,則直接傳回true
}
// 從這裡我們可以看出,當你既設定了OnTouchListener又設定了OnClickListener,那麼目前者傳回true的時候,
// onTouchEvent沒機會被調用,當然你的OnClickListener也就不會被觸發;另外還有個差別就是onTouch裡可以
// 收到每次touch事件,而onClickListener隻是在up事件到來時觸發。
if (onTouchEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false; // 上面的都沒處理,則傳回false
}
當dispatchTransformedTouchEvent方法傳回true時,表示事件由該子View消耗了,是以addTouchTarget方法就會被執行。
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
2.3 View對事件的分發
在dispatchTransformedTouchEvent方法中 child不為空就表示目前是一個 View
調用 child.dispatchTouchEvent(transformedEvent); 方法
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
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);
}
}
View 的dispathTouchEvent代碼在下面
下面貼上三個重要方法的源碼+加注釋
ViewGroup 的 dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) { // view沒有被遮罩,一般都成立
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) { // 一堆touch事件(從按下到松手)中的第一個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.
cancelAndClearTouchTargets(ev);
resetTouchState(); // 作為新一輪的開始,reset所有相關的狀态
}
// Check for interception.
final boolean intercepted; // 檢查是否要攔截
if (actionMasked == MotionEvent.ACTION_DOWN // down事件
|| mFirstTouchTarget != null) { // 或者之前的某次事件已經經由此ViewGroup派發給children後被處理掉了
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) { // 隻有允許攔截才執行onInterceptTouchEvent方法
intercepted = onInterceptTouchEvent(ev); // 預設傳回false,不攔截
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false; // 不允許攔截的話,直接設為false
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
// 在這種情況下,actionMasked != ACTION_DOWN && mFirstTouchTarget == null
// 第一次的down事件沒有被此ViewGroup的children處理掉(要麼是它們自己不處理,要麼是ViewGroup從一
// 開始的down事件就開始攔截),則接下來的所有事件
// 也沒它們的份,即不處理down事件的話,那表示你對後面接下來的事件也不感興趣
intercepted = true; // 這種情況下設定ViewGroup攔截接下來的事件
}
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL; // 此touch事件是否取消了
// Update list of touch targets for pointer down, if needed.
// 是否拆分事件,3.0(包括)之後引入的,預設拆分
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null; // 接下來ViewGroup判斷要将此touch事件交給誰處理
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) { // 沒取消也不攔截,即是個有效的touch事件
if (actionMasked == MotionEvent.ACTION_DOWN // 第一個手指down
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) // 接下來的手指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);
final int childrenCount = mChildrenCount;
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.
final View[] children = mChildren;
final boolean customOrder = isChildrenDrawingOrderEnabled();
// 從最後一個向第一個找
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder ?
getChildDrawingOrder(childrenCount, i) : i;
final View child = children[childIndex];
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue; // 不滿足這2個條件直接跳過,看下一個child
}
// child view能receive touch事件而且touch坐标也在view邊界内
newTouchTarget = getTouchTarget(child);// 查找child對應的TouchTarget
if (newTouchTarget != null) { // 比如在同一個child上按下了多跟手指
// 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; // newTouchTarget已經有了,跳出for循環
}
resetCancelNextUpFlag(child);
// 将此事件交給child處理
// 有這種情況,一個手指按在了child1上,另一個手指按在了child2上,以此類推
// 這樣TouchTarget的鍊就形成了
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = childIndex;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
// 如果處理掉了的話,将此child添加到touch鍊的頭部
// 注意這個方法内部會更新 mFirstTouchTarget
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true; // down或pointer_down事件已經被處理了
break; // 可以退出for循環了。。。
}
}
}
// 本次沒找到newTouchTarget但之前的mFirstTouchTarget已經有了
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;
}
// while結束後,newTouchTarget指向了最初的TouchTarget
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// 非down事件直接從這裡開始處理,不會走上面的一大堆尋找TouchTarget的邏輯
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// 沒有children處理則派發給自己處理
// 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;
while (target != null) { // 周遊TouchTarget形成的連結清單
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true; // 已經處理過的不再讓其處理事件
} else {
// 取消child标記
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
// 如果ViewGroup從半路攔截了touch事件則給touch鍊上的child發送cancel事件
// 如果cancelChild為true的話
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true; // TouchTarget鍊中任意一個處理了則設定handled為true
}
if (cancelChild) { // 如果是cancelChild的話,則回收此target節點
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.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
// 取消或up事件時resetTouchState
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; // 傳回處理的結果
}
dispatchTransformedTouchEvent
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
if (newPointerIdBits == 0) {
return false;
}
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
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);
}
// Done.
transformedEvent.recycle();
return handled;
}
View的dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) { // 一般都成立
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) { // 先在ENABLED狀态下嘗試調用onTouch方法
return true; // 如果被onTouch處理了,則直接傳回true
}
// 從這裡我們可以看出,當你既設定了OnTouchListener又設定了OnClickListener,那麼目前者傳回true的時候,
// onTouchEvent沒機會被調用,當然你的OnClickListener也就不會被觸發;另外還有個差別就是onTouch裡可以
// 收到每次touch事件,而onClickListener隻是在up事件到來時觸發。
if (onTouchEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false; // 上面的都沒處理,則傳回false
總結
我們大體來總結一下View的事件分發
首先會進入dispatchTouchEvent方法 進行分發 ->進行判斷目前事件攔截->onInterceptTouchEvent(根據目前事件有沒有被子View 進行過消費)->攔截
true(判斷目前VIew是否進行了OnTouchListener設定 并且通過onTouch 的值判斷OnTouch事件是觸發) ->不攔截 繼續分析循環分發 調用 dispatchTransformedTouchEvent 方法
Andorid知識星球,歡迎大家關注!
本文部分參考
https://blog.csdn.net/brastom/article/details/52875837