記錄一下Touch事件的分析- -
什么是事件:當(dāng)用戶觸摸屏幕時(shí)匀钧,將產(chǎn)生的觸摸行為(Touch事件)
事件的類型:
- MotionEvent.ACTION_DOWN 手指剛接觸屏幕
- MotionEvent.ACTION_UP 手指從屏幕上松開
- MotionEvent.ACTION_MOVE 手指在屏幕上滑動(dòng)
- MotionEvent.ACTION_CANCEL 非人為因素取消
正常情況下夺谁,一次手指觸摸屏幕的行為會(huì)觸發(fā)一系列點(diǎn)擊事件
- 點(diǎn)擊屏幕后立即松開含懊,事件序列為DOWN—>UP
-
點(diǎn)擊屏幕滑動(dòng)一會(huì)再松開上鞠,事件序列為DOWN——>MOVE——>……——>MOVE—>UP
image.png
事件分發(fā)對(duì)象
Activity ——> ViewGroup——>View
主要方法
- dispatchTouchEvent(MotionEvent ev) 用來進(jìn)行事件分發(fā)
- onInterceptTouchEvent(MotionEvent ev) 判斷是否攔截事件(只存在于ViewGroup中)
- onTouchEvent(MotionEvent ev) 處理點(diǎn)擊事件
Activity
當(dāng)一個(gè)事件發(fā)生時(shí)首先會(huì)調(diào)用Activity的dispatchTouchEvent()事件
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction(); //這是一個(gè)空方法子類可以實(shí)現(xiàn)來獲取到當(dāng)前用戶觸摸屏幕的監(jiān)聽
}
//getWindow 返回的是PhoneWindow 實(shí)際上就是調(diào)用PhoneWindow的superDispatchTouchEvent的方法
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//如果沒有View消費(fèi)事件,就會(huì)執(zhí)行Activity的onTouchEvent事件
return onTouchEvent(ev);
}
然后在PhoneWindow中的superDispatchTouchEvent()方法
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
//mDecor 是繼承自FrameLayout的一個(gè)子類
return mDecor.superDispatchTouchEvent(event);
}
然后再看一下DecorView的superDispatchTouchEvent()方法
public boolean superDispatchTouchEvent(MotionEvent event) {
//它調(diào)用了父類的dispatchTouchEvent()方法
return super.dispatchTouchEvent(event);
}
由于DecorView是FrameLayout的子類所以就到了ViewGroup的dispatchTouchEvent()方法
流程就是
image.png
Activity收到事件后會(huì)一層一層的往下派發(fā)到ViewGroup到了ViewGroup之后就涉及到了ViewGroup的dispatchTouchEvent方法。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
//mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0&& (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED!=0
//就是檢查上述兩個(gè)標(biāo)志上述兩個(gè)標(biāo)志均為true時(shí)onFilterTouchEventForSecurity會(huì)返回false
//mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0 是View有設(shè)置被遮擋時(shí)不處理觸摸事件的flag
//event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED!=0 檢查受到該事件的窗口是被其它窗口遮擋
//FILTER_TOUCHES_WHEN_OBSCURED可以通過在xml文件中的android:filterTouchesWhenObscured來設(shè)置可婶,或者在Java中通過setFilterTouchesWhenObscured()來添加或移除
//DecorView默認(rèn)是沒有這個(gè)標(biāo)志位的,而其他View基本上默認(rèn)都是有的
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
//當(dāng)時(shí)down事件時(shí)援雇,是一個(gè)新的事件的開始矛渴,會(huì)進(jìn)行一系列的reset操作,對(duì)上一次的事件一些狀態(tài)進(jìn)行重置&mFirstTouchTarget設(shè)置成null
//清除TouchTarget的緩存
cancelAndClearTouchTargets(ev);
//對(duì)mGroupFlags的標(biāo)志進(jìn)行重置為~FLAG_DISALLOW_INTERCEPT
resetTouchState();
}
// Check for interception.
//記錄當(dāng)前事件能否被攔截
final boolean intercepted;
//如果當(dāng)前為down事件 或者mFirstTouchTarget為null
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//FLAG_DISALLOW_INTERCEPT這個(gè)標(biāo)志很重要 當(dāng)子View調(diào)用requestDisallowInterceptTouchEvent(boolean)方法時(shí)就是設(shè)置的mGroupFlags的屬性
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//這個(gè)disallowIntercept就是判斷是否允許攔截除了down之外的事件 為什么是down事件 是因?yàn)樵谏弦粋€(gè)判斷中對(duì)mGroupFlags的標(biāo)志進(jìn)行重置所以在down事件中disallowIntercept永遠(yuǎn)為false
//所以就會(huì)執(zhí)行onInterceptTouchEvent()方法
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
//onInterceptTouchEvent這個(gè)方法默認(rèn)為false就是viewgroup默認(rèn)不會(huì)攔截事件
ev.setAction(action); // restore action in case it was changed
} else {
//如果子View調(diào)用getParent().requestDisallowInterceptTouchEvent(true)就會(huì)執(zhí)行下邊語句惫搏,不在攔截事件
intercepted = false;
}
} else {
//這里默認(rèn)就是mFirstTouchTarget為null,可以理解為沒有子View能夠分發(fā)此事件 所以 intercepted標(biāo)志就變?yōu)閠rue,
intercepted = true;
}
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// 標(biāo)識(shí)本次事件需不需要取消
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// 檢查父View是否支持多點(diǎn)觸控具温,即將多個(gè)TouchEvent分發(fā)給子View,
// 通過setMotionEventSplittingEnabled()可以修改這個(gè)值筐赔。
// FLAG_SPLIT_MOTION_EVENTS在3.0是默認(rèn)為true的铣猩,即支持多點(diǎn)觸控的分發(fā)
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//默認(rèn)不攔截
if (!canceled && !intercepted) {
// 檢查TouchEvent是否可以觸發(fā)View獲取焦點(diǎn),如果可以茴丰,查找本View中有沒有獲得焦點(diǎn)的子View达皿,
// 有就獲取它,沒有就為null
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
//如果當(dāng)前為down事件
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE //鼠標(biāo)移動(dòng)) {
// 獲取當(dāng)前觸摸手指在多點(diǎn)觸控中的排序
// 這個(gè)值可能因?yàn)橛惺种赴l(fā)生Down或Up而發(fā)生改變
final int actionIndex = ev.getActionIndex(); // always 0 for down
// 標(biāo)識(shí)當(dāng)前是那一個(gè)點(diǎn)的觸摸事件
//ev.getPointerId()此時(shí)獲取到手指的Id贿肩,這個(gè)值在Down到Up這個(gè)過程中是不會(huì)改變的
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// 清理之前觸摸事件中的目標(biāo)
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
//5.0加入的 將所有子View放到集合中峦椰,按照添加順序排序,但是受到Z軸影響
//只有子View數(shù)量大于1汰规,并且其中至少有一個(gè)子View的Z軸不為0汤功,其實(shí)就是elevation屬性大于0,它才不為null
// 7.0中溜哮,View的elevation默認(rèn)為0
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//對(duì)Viewgroup的所有子View進(jìn)行倒序遍歷(為什么是倒序 是因?yàn)?默認(rèn)最后添加的View在最上層滔金,應(yīng)該最優(yōu)先得到事件)
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// 如果當(dāng)前已經(jīng)有View獲得焦點(diǎn)了,找到它茬射。后面的觸摸事件會(huì)優(yōu)先傳給它鹦蠕。
// 應(yīng)該主要影響后面觸摸點(diǎn)的Down事件
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
//// 找到后i設(shè)為最后一個(gè)View,強(qiáng)制結(jié)束for循環(huán)不再繼續(xù)查找
i = childrenCount - 1;
}
//此方法判斷子View是否可見&沒有在執(zhí)行動(dòng)畫
if (!canViewReceivePointerEvents(child)
//判斷觸摸區(qū)域是否在View中
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//找到了可以分發(fā)的View
//根據(jù)當(dāng)前child查找是否已經(jīng)記錄在mFirstTouchTarget這個(gè)單鏈表中
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
// 再次重置View
resetCancelNextUpFlag(child);
//將child傳遞到dispatchTransformedTouchEvent方法中如果傳入的child為null會(huì)調(diào)用super.dispatchTouchEvent
// 否則會(huì)對(duì)想X,Y計(jì)算根據(jù)當(dāng)前的View進(jìn)行偏移然后調(diào)用child.dispatchTouchEvent方法將事件傳遞到child中
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 記錄這次TouchEvent按下的時(shí)間
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();
//將mFirstTouchTarget指向消費(fèi)該事件的View
newTouchTarget = addTouchTarget(child, idBitsToAssign);
// 標(biāo)記已經(jīng)把事件分發(fā)給了newTouchTarget在抛,退出子View遍歷
alreadyDispatchedToNewTouchTarget = true;
break;
}
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
//這個(gè)只有在多點(diǎn)觸控才會(huì)執(zhí)行
// newTouchTarget在不是Down事件钟病,或者沒有找到處理事件的View時(shí)是null
// mFirstTouchTarget在Down事件時(shí),如果找到了處理的View就不為null
if (newTouchTarget == null && mFirstTouchTarget != null) {
// 直接讓上次處理的View繼續(xù)處理
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
//到這里 ACTION_DOWN的事件處理完畢
}
}
//mFirstTouchTarget == null 表示沒有能相應(yīng)該事件的child刚梭,那么就調(diào)用父類(也就是View)的dispatchTouchEvent肠阱,如果在down事件中intercepted為true,則newTouchTarget也為null也會(huì)執(zhí)行此方法
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
//表示在Down事件處理中,已經(jīng)將這個(gè)事件交給newTouchTarget處理過了朴读,就不重復(fù)處理了
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
//對(duì)Move Up事件的處理
} else {
//cancelChild 為true 如果事件被父View攔截了屹徘,或者child原本被打上了暫時(shí)不可接收TouchEvent的標(biāo)記PFLAG_CANCEL_NEXT_UP_EVENT
// 就給他給它發(fā)送取消事件
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//將Move Up等事件的分發(fā)給子View
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//cancelChild為true 就會(huì)清空事件隊(duì)列,這樣后續(xù)事件就會(huì)被ViewGroup本身攔截掉
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.
//可以忽略衅金,跟主流程無關(guān)
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;
}
因?yàn)榇a注釋太多了噪伊,仔細(xì)分析可以看注釋即可簿煌,簡單分析就是
public boolean dispatchTouchEvent(MotionEvent ev){
boolean concume = fasle ;
//判斷是否要攔截,
if(onInterceptTouchEvent(ev)){
//如果攔截就調(diào)用自身的onTouchEvent()事件鉴吹。 ViewGroup的onTouchEvent是用的父類View的姨伟,自身并沒有實(shí)現(xiàn)這個(gè)方法
consume = onTouchEvent(ev)
}else{
//如果不攔截就調(diào)用child.dispatchTouchEvent再對(duì)事件分發(fā)給子View
Consume = child.dispatchTouchEvent(ev);
}
return consume;
}
還需要注意一下下邊的代碼
//FLAG_DISALLOW_INTERCEPT這個(gè)標(biāo)志很重要 當(dāng)子View調(diào)用requestDisallowInterceptTouchEvent(boolean)方法時(shí)就是設(shè)置的mGroupFlags的屬性
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//這個(gè)disallowIntercept就是判斷是否允許攔截除了down之外的事件 為什么是down事件 是因?yàn)樵谏弦粋€(gè)判斷中對(duì)mGroupFlags的標(biāo)志進(jìn)行重置所以在down事件中disallowIntercept永遠(yuǎn)為false
//所以就會(huì)執(zhí)行onInterceptTouchEvent()方法
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
//onInterceptTouchEvent這個(gè)方法默認(rèn)為false就是viewgroup默認(rèn)不會(huì)攔截事件
ev.setAction(action); // restore action in case it was changed
} else {
//如果子View調(diào)用getParent().requestDisallowInterceptTouchEvent(true)就會(huì)執(zhí)行下邊語句,不在攔截事件
intercepted = false;
}
再看一下dispatchTransformedTouchEvent這個(gè)方法
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// 先記錄原本的Action
//如果cancel為true就會(huì)發(fā)送cancel事件豆励,實(shí)際傳遞進(jìn)來的事件就會(huì)被覆蓋掉
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
// 可能過來的事件沒有ACTION_CANCEL夺荒,如果希望取消的話,那么為事件添加取消標(biāo)志良蒸。
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
// 如果沒有子View了技扼,調(diào)用View中的dispatchTouchEvent
// 進(jìn)而調(diào)用View的onTouch或者onTouchEvent方法,觸發(fā)ACTION_CANCEL邏輯
handled = super.dispatchTouchEvent(event);
} else {
// 如果有子View嫩痰,將這個(gè)取消事件傳遞給子View
handled = child.dispatchTouchEvent(event);
}
// 在設(shè)置回原本的Action
// 此時(shí)TouchEvent的行為相當(dāng)于沒變
// 但是卻把該ViewGroup的
event.setAction(oldAction);
return handled;
}
// 獲取觸摸事件的觸摸點(diǎn)id
final int oldPointerIdBits = event.getPointerIdBits();
// 看和期望的觸摸點(diǎn)是不是一個(gè)
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
if (newPointerIdBits == 0) {
//不是
return false;
}
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
//這里邊如果child == null 就會(huì)調(diào)用自身的super.dispatchTouchEvent方法
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
//否則就會(huì)計(jì)算偏移量然后調(diào)用 就會(huì)調(diào)用child.dispatchTouchEvent方法進(jìn)行分發(fā)
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
// 恢復(fù)TouchEvent坐標(biāo)到原來位置剿吻,避免影響后面的流
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 handle;
}
再記錄一下cancelAndClearTouchTargets這個(gè)方法
private void cancelAndClearTouchTargets(MotionEvent event) {
// 如果觸摸事件目標(biāo)隊(duì)列不為空才執(zhí)行后面的邏輯
if (mFirstTouchTarget != null) {
boolean syntheticEvent = false;
if (event == null) {
final long now = SystemClock.uptimeMillis();
//自己創(chuàng)建一個(gè)ACTION_CANCEL事件
event = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
// 設(shè)置事件源類型為觸摸屏幕
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
// 標(biāo)記一下,這是一個(gè)合成事件
syntheticEvent = true;
}
// TouchTarget是一個(gè)鏈表結(jié)構(gòu)始赎,保存了事件傳遞的子一系列目標(biāo)View
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
// 檢查View是否設(shè)置了暫時(shí)不在接收事件的標(biāo)志位和橙,如果有清除該標(biāo)志位
// 這樣該View就能夠接收下一次事件了仔燕。
//這個(gè)標(biāo)志位是PFLAG_CANCEL_NEXT_UP_EVENT 一個(gè)View如果有PFLAG_CANCEL_NEXT_UP_EVENT標(biāo)志造垛,表示它跟ViewGroup解除了綁定,通常會(huì)在調(diào)用ViewGroup#detachViewFromParent(View),很少用
resetCancelNextUpFlag(target.child);
// 將這個(gè)取消事件傳給子View晰搀,給上一次接收事件流的子View發(fā)送模擬的ACTION_CANCEL事件五辽,可以重置這些子View的觸摸狀態(tài)。比如取消它們的點(diǎn)擊或者長按事件
dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
}
// 清空觸摸事件目標(biāo)隊(duì)列
clearTouchTargets();
if (syntheticEvent) {
// 如果是合成事件外恕,需要回收它
event.recycle();
}
}
}
再學(xué)習(xí)一個(gè)類
TouchTarget是一個(gè)內(nèi)部類 他是一個(gè)單向的鏈表杆逗,mFirstTouchTarget表示的就是頭,他記錄的就是能夠處理child的TouchTarget鳞疲。它內(nèi)部有一個(gè)鏈表組成的TouchTarget對(duì)象池能夠起到復(fù)用的機(jī)制
private static final class TouchTarget {
//這個(gè)值表示最大個(gè)數(shù)罪郊,這個(gè)值也就決定了事件最多傳遞32層,所以當(dāng)寫一個(gè)layout的層級(jí)超過32的時(shí)候尚洽,子View就會(huì)收不到事件悔橄。
private static final int MAX_RECYCLED = 32;
private static final Object sRecycleLock = new Object[0];
//這一個(gè)static的值。所有的ViewGroup對(duì)象共用一個(gè)這點(diǎn)很重要
private static TouchTarget sRecycleBin;
private static int sRecycledCount;
public static final int ALL_POINTER_IDS = -1; // all ones
// The touched child view.
public View child;
// The combined bit mask of pointer ids for all pointers captured by the target.
public int pointerIdBits;
// The next target in the target list.
public TouchTarget next;
private TouchTarget() {
}
//獲取一個(gè)可以復(fù)用的target
public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
if (child == null) {
throw new IllegalArgumentException("child must be non-null");
}
final TouchTarget target;
synchronized (sRecycleLock) {
//如果第一次為null就會(huì)創(chuàng)建一個(gè)新的
if (sRecycleBin == null) {
target = new TouchTarget();
} else {
//這基本就是鏈表操作
//會(huì)在sRecycleBin取一個(gè)出來腺毫,將對(duì)象傳遞出去
target = sRecycleBin;
sRecycleBin = target.next;
//sRecycleBin 的count-1 ,因?yàn)橐瞥鋈チ艘粋€(gè)
sRecycledCount--;
target.next = null;
}
}
target.child = child;
target.pointerIdBits = pointerIdBits;
return target;
}
//對(duì)this進(jìn)行回收操作癣疟,將它放進(jìn)sRecycleBin的鏈表里
public void recycle() {
if (child == null) {
throw new IllegalStateException("already recycled once");
}
//基本鏈表操作
synchronized (sRecycleLock) {
if (sRecycledCount < MAX_RECYCLED) {
next = sRecycleBin;
sRecycleBin = this;
sRecycledCount += 1;
} else {
next = null;
}
child = null;
}
}
總結(jié):
- ViewGroup的事件會(huì)先判斷是否被攔截,如果true就調(diào)用super.dispatchTouchEvent也就是自己的View父類的dispatchTouchEven否則就會(huì)下發(fā)child.dispatchTouchEvent
- Down事件是一系列事件的開端潮酒,所以很重要睛挚,花費(fèi)的事件也最長
- 子View可以通過調(diào)用getParent().requestDisallowInterceptTouchEvent(true)通知ViewGroup不攔截事件
- ViewGroup的onTouchEvent()沒有實(shí)現(xiàn),是用的View的
- ViewGroup通過TouchTarget來緩存down事件接收的View,然后其他事件通過TouchTarget來分發(fā)事件急黎,防止再次循環(huán)遍歷扎狱,提高效率
- TouchTarget是一個(gè)單鏈表實(shí)現(xiàn)的對(duì)象池侧到,這個(gè)思路值得學(xué)習(xí)。Android源碼內(nèi)部有很多類似的設(shè)計(jì)
下邊看一下View的淤击,View的事件非常簡單床牧,因?yàn)樗粫?huì)再繼續(xù)往下分發(fā)了。所以只要判斷處不處理就可以了
public boolean dispatchTouchEvent(MotionEvent event) {
if (event.isTargetAccessibilityFocus()) {
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
//5.0內(nèi)嵌滑動(dòng)的處理
stopNestedScroll();
}
//跟ViewGroup一樣的判斷
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
ListenerInfo li = mListenerInfo;
//優(yōu)先執(zhí)行l(wèi)i.mOnTouchListener.onTouch方法遭贸。
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//如果執(zhí)行l(wèi)i.mOnTouchListener.onTouch方法返回為true就不會(huì)再次執(zhí)行onTouchEvent了這個(gè)需要注意
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
可以看到
- View的dispatchTouchEvent方法很簡單戈咳,并沒有再次分發(fā)事件的邏輯了
-
onTouch優(yōu)先于onTouchEvent執(zhí)行,并且返回true之后onTouchEvent就不再執(zhí)行
所以事件分發(fā)的總體流程就是
image.png
最后再分析一下View的onTouchEvent事件就完事了
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//如果ENABLED為false 則直接return
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
//如果ENABLED為false 則直接return 不在繼續(xù)后續(xù)操作
return clickable;
}
if (mTouchDelegate != null) {
//如果有TouchDelegate則直接返回true
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
setPressed(true, x, y);
}
//mHasPerformedLongPress 會(huì)有1中情況會(huì)true 只有當(dāng)觸摸了超過500ms并且onLongClick回調(diào)返回了true mHasPerformedLongPress就會(huì)true 可查看源碼比較清晰壕吹,
//這種情況下就不會(huì)出發(fā)點(diǎn)擊事件
//所以就會(huì)又2種情況 低于500ms會(huì)移除長按事件著蛙,并觸發(fā)點(diǎn)擊事件,超過500ms之后并且設(shè)置了長按監(jiān)聽會(huì)優(yōu)先處理長按事件然后根據(jù)返回值來處理是否執(zhí)行下邊代碼
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
//移除長按事件
removeLongPressCallback();
if (!focusTaken) {
//點(diǎn)擊事件
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
// 省去若干代碼
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
//mHasPerformedLongPress 改成false
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
//發(fā)送一個(gè)100ms的點(diǎn)擊任務(wù)
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
//mPendingCheckForTap 里邊會(huì)發(fā)送一個(gè)500ms的長按事件的延遲任務(wù) 是一個(gè)CheckForLongPress的類
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
////省去若干代碼
break;
}
return true;
}
return false;
}
總結(jié)
- 一個(gè)時(shí)間序列從手指觸摸屏幕到手指離開屏幕耳贬,在這個(gè)過程中產(chǎn)生一系列事件踏堡,以DOWN事件開始,中間含有不定數(shù)的MOVE事件咒劲,以UP事件結(jié)束
- 正常情況下顷蟆,一個(gè)事件序列只能被一個(gè)View攔截并且消耗
- 某個(gè)View一旦決定攔截,那么這個(gè)事件序列都將由它的onTouchEvent處理腐魂,并且它的OnInterceptTouchEvent不會(huì)再調(diào)用
- 某個(gè)View一旦開始處理事件帐偎,如果它不消耗ACTION_DOWN事件(onTouchEvent返回false),那么同一事件序列中的其他事件都不會(huì)再交給它處理蛔屹,并且重新交由它的父級(jí)元素處理(父元素onTouchEvent被調(diào)用)
- 事件的傳遞過程是由外向內(nèi)的削樊,即事件總是先傳遞給父元素,然后再由父元素分發(fā)給子View,通過requestDisallowInterceptTouchEvent方法可以在子View中干預(yù)父元素的事件分發(fā)過程兔毒,ACTION_DOWN除外
- ViewGroup默認(rèn)不攔截任何事件漫贞,即onInterceptTouchEvent默認(rèn)返回false。View沒有onInterceptTouchEvent方法育叁,一旦有點(diǎn)擊事件傳遞給它迅脐,那么它的onTouchEvent方法就會(huì)被調(diào)用
- View的onTouchEvent默認(rèn)會(huì)消耗事件(返回true),除非它是不可點(diǎn)擊的(clickable和longClickable同時(shí)為false)View的longClickable默認(rèn)都是false豪嗽,clickable要分情況谴蔑,比如Button的clickable默認(rèn)為true,TextView的默認(rèn)為false
- View的enable屬性不影響onTouchEvent的默認(rèn)返回值昵骤。哪怕一個(gè)View是disable狀態(tài)的树碱,只要它的clickable或者longClickable有一個(gè)為true,那么它的onTouchEvent就會(huì)返回true
- onClick回響應(yīng)的前提是當(dāng)前View是可點(diǎn)擊的,并且收到了ACTION_DOWN和ACTION_UP的事件变秦,并且受長按事件影響成榜,當(dāng)長按事件返回true時(shí),onClick不會(huì)響應(yīng)
- onLongClick在ACTION_DOWN里判斷是否進(jìn)行響應(yīng)蹦玫,要想執(zhí)行長按事件該View必須是longClickable的并且設(shè)置了OnLongClickListener