還是之前的項(xiàng)目中的一些東西掸绞,繼續(xù)抽出來(lái)給大家淹遵。
文章結(jié)構(gòu):(1)展現(xiàn)焦點(diǎn)問(wèn)題(以及一些體驗(yàn)交互的狀態(tài))继找;(2)分析焦點(diǎn)問(wèn)題遂跟,詳解兩個(gè)屬性;(3)結(jié)合部分相關(guān)源碼討論事件傳遞機(jī)制;
一幻锁、展現(xiàn)焦點(diǎn)問(wèn)題:
(1)如果對(duì)我下面給的demo不加一些屬性處理凯亮,效果如下:
這里寫圖片描述
也就是看不到上面的輪播圖,這樣的話越败,就是recyclerview搶占了activity的焦點(diǎn)咯触幼。
那么demo中硼瓣,我們?cè)趺唇鉀Q的呢究飞??堂鲤?
<!-- 我就加了一句屬性android:focusableInTouchMode="true" 亿傅,就把焦點(diǎn)交給輪播圖-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_collapseMode="pin"
android:focusableInTouchMode="true">
<com.bigkoo.convenientbanner.ConvenientBanner
android:id="@+id/banner"
android:layout_width="match_parent"
android:layout_height="200dp"
app:canLoop="true" />
</LinearLayout>
為什么這樣做呢?瘟栖?葵擎?
我何用做是為了給輪播圖控件施加焦點(diǎn)嘛,我就在輪播圖的父控件設(shè)定了android:focusableInTouchMode=true半哟,也就攔截了輪播圖默認(rèn)行為讓父控件得到高亮得到焦點(diǎn)酬滤,當(dāng)然也搶奪了recyclerview想要的焦點(diǎn)。
(2)另外寓涨,大家在編寫自己的登錄頁(yè)面時(shí)也經(jīng)常遇到焦點(diǎn)問(wèn)題吧盯串??是什么導(dǎo)致的呢戒良?是EditTextL迥蟆!它自動(dòng)搶奪焦點(diǎn)的E雌椤几缭!
EditText這種即使在TouchMode下,依然需要獲取焦點(diǎn)的控件
怎么解決呢?
解決:在EditText的父級(jí)控件中找一個(gè)沃呢,設(shè)置成
android:focusable="true"
android:focusableInTouchMode="true"
這樣年栓,就把EditText默認(rèn)的行為截?cái)嗔耍”∷〔蛔屗詣?dòng)奪取焦點(diǎn)韵洋。
(3)講述另外一些體驗(yàn)交互狀態(tài)
Select
這里寫圖片描述
Focusable in Touch Mode
也就是我們上面的edittext,點(diǎn)擊去獲取焦點(diǎn)黄锤。
Focus
就是焦點(diǎn)模式咯搪缨。
二、分析焦點(diǎn)問(wèn)題鸵熟,詳解兩個(gè)屬性:
focusableInTouchMode跟focusable有什么區(qū)別副编?
1.要理解這個(gè)屬性,首先你得知道,Android不是只面向手機(jī)的,它還有可能被安裝在電視等非觸摸輸入設(shè)備上.即使是在手機(jī)上,目前很多手機(jī)也都支持鍵盤輸入了。
2.focusable這種屬性,更多的是為了解決非觸摸輸入的,因?yàn)槟阌眠b控器或鍵盤點(diǎn)擊控件,就必然要涉及到焦點(diǎn)的問(wèn)題,只有可以獲得焦點(diǎn)的控件才能響應(yīng)鍵盤或者遙控器或者軌跡球的確定事件.
3.focusableInTouchMode.這個(gè)屬性的意思一如字面所述,就是在進(jìn)入觸摸輸入模式后,該控件是否還有獲得焦點(diǎn)的能力.
什么意思呢流强?痹届?再通俗點(diǎn)呻待,
對(duì)于一個(gè)擁有觸摸屏功能的設(shè)備而言, 一旦用戶用手點(diǎn)擊屏幕, 設(shè)備會(huì)立刻進(jìn)入touch mode。這時(shí)候被點(diǎn)擊的控件只有設(shè)置android:focusableInTouchMode為true的時(shí)候才會(huì)獲得focus队腐,比如EditText控件蚕捉。其他可以觸摸的控件比如Button。
然后其android:focusableInTouchMode默認(rèn)為false, 當(dāng)被點(diǎn)擊的時(shí)候不會(huì)獲取焦點(diǎn)柴淘,它們只是簡(jiǎn)單地執(zhí)行onClick事件而已迫淹。
所以以上兩個(gè)屬性就針對(duì)這幾種的touch情況啦。交互體驗(yàn)是十分地不同的N稀A舶尽!
所以我們大致看下這份源碼的接口聲明第股,我們就可以清晰認(rèn)知focus跟touch是極大的不同应民。
public static class ListenerInfo {
/**
* Listener used to dispatch focus change events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnFocusChangeListener mOnFocusChangeListener;
/**
* Listeners for layout change events.
*/
private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;
/**
* Listeners for attach events.
*/
private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;
/**
* Listener used to dispatch click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
public OnClickListener mOnClickListener;
/**
* Listener used to dispatch long click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnLongClickListener mOnLongClickListener;
/**
* Listener used to build the context menu.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnCreateContextMenuListener mOnCreateContextMenuListener;
private OnKeyListener mOnKeyListener;
private OnTouchListener mOnTouchListener;
private OnHoverListener mOnHoverListener;
private OnGenericMotionListener mOnGenericMotionListener;
private OnDragListener mOnDragListener;
private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;
OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
}
三、討論事件傳遞機(jī)制
焦點(diǎn)問(wèn)題也是涉及到事件機(jī)制的夕吻,所以我們就順便進(jìn)一步地去討論這個(gè)android事件機(jī)制咯诲锹。
關(guān)于事件的傳遞,我們主要是關(guān)注幾個(gè)問(wèn)題:(1)事件怎么傳遞涉馅?它的傳遞流程是怎樣归园?(2)事件是怎么消費(fèi)的?控漠?(3)自定義view也事件沖突時(shí)蔓倍,我們?cè)趺刺幚恚浚?)源碼是怎么定義這個(gè)事件機(jī)制的盐捷?偶翅?
(1)事件怎么傳遞?它的傳遞流程是怎樣碉渡?
首先由Activity分發(fā)聚谁,分發(fā)給根View,也就是DecorView(DecorView為整個(gè)Window界面的最頂層View)滞诺。
然后由根View分發(fā)到子的ViewGroup形导,再由各個(gè)ViewGroup分發(fā)給子View
這里寫圖片描述
好了看下源碼:
//攔截事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
return super.onInterceptTouchEvent(ev);
}
//處理事件
@Override
public boolean onTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
return super.onTouchEvent(ev);
}
//分發(fā)事件
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
return super.dispatchTouchEvent(ev);
}
我們來(lái)仔細(xì)討論ViewGroup事件的傳遞機(jī)制:
這里寫圖片描述
雖然那個(gè)圖真的很棒,但是講得不夠清晰呢习霹,下面我將它具體講述朵耕。
(1)當(dāng)我們點(diǎn)擊viewC時(shí),就會(huì)觸發(fā)事件淋叶,然后事件傳遞給viewgroupA阎曹,viewgroupA它首先會(huì)執(zhí)行dispatchTouchEvent來(lái)調(diào)用onInterceptTouchEvent判斷本group是否可以處理,return ture則交由onTouchEvent處理事件,return false則使用dispatchTouchEvent往下傳遞事件处嫌。
(2)往下傳遞過(guò)來(lái)的事件由viewgroupB的onInterceptTouchEvent攔截栅贴,問(wèn)自己能否處理該事件,能則處理熏迹,不能則往下繼續(xù)傳遞檐薯。同理viewC的這一步流程。
(3)當(dāng)傳遞到最終的viewC的時(shí)候注暗,如果不能夠處理該觸發(fā)事件坛缕,是會(huì)重新回傳給父控件的!S汛妗5簧拧陶衅!
(2)事件是怎么消費(fèi)的屡立??
就是dispatchTouchEvent判斷自己能處理后就調(diào)用自己的onTouchEvent進(jìn)行處理搀军。
(3)自定義view時(shí)膨俐,我們?cè)趺刺幚恚?/h2>
點(diǎn)這里看例子。感謝那位博主的精妙例子罩句。
(4)源碼是怎么定義這個(gè)事件機(jī)制的焚刺??
事件即MotionEvent:
(1)MotionEvent.ACTION_DOWN 按下View门烂,是所有事件的開(kāi)始
(2)MotionEvent.ACTION_MOVE 滑動(dòng)事件
(3)MotionEvent.ACTION_UP 與down對(duì)應(yīng)乳愉,表示抬起
//負(fù)責(zé)分發(fā)事件的這位,很重要的代碼
//看之前先記住這句話:mFirstTouchTarget為null就是說(shuō)Touch事件未被消費(fèi). 至于是什么內(nèi)容請(qǐng)仔細(xì)閱讀下文
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
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.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;//標(biāo)記這個(gè)為事件暫存值
/*
一屯远、因?yàn)锳CTION_DOWN是一系列事件的開(kāi)端,當(dāng)是ACTION_DOWN時(shí)進(jìn)行一些初始化操作.
*/
// Handle an initial 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.
//下面的方法中有一個(gè)非常重要的操作:clearTouchTargets();將mFirstTouchTarget設(shè)置為null!!!!隨后在resetTouchState()中重置Touch狀態(tài)標(biāo)識(shí)
cancelAndClearTouchTargets(ev);//清除以往的Touch狀態(tài)(state)開(kāi)始新的手勢(shì)(gesture)
resetTouchState();
}
/*
二蔓姚、檢查是否要攔截
*/
// Check for interception.
final boolean intercepted;//使用變量intercepted來(lái)標(biāo)記ViewGroup是否攔截Touch事件的傳遞.
// 事件為ACTION_DOWN或者mFirstTouchTarget不為null(即已經(jīng)找到能夠接收touch事件的目標(biāo)組件)時(shí)if成立
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//判斷disallowIntercept(禁止攔截)標(biāo)志位
//因?yàn)樵谄渌胤娇赡苷{(diào)用了requestDisallowInterceptTouchEvent(boolean disallowIntercept)
//從而禁止執(zhí)行是否需要攔截的判斷(有點(diǎn)拗口~其實(shí)看requestDisallowInterceptTouchEvent()方法名就可明白)
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//當(dāng)沒(méi)有禁止攔截判斷時(shí)(即disallowIntercept為false)調(diào)用onInterceptTouchEvent(ev)方法
if (!disallowIntercept) {
//既然disallowIntercept為false那么就調(diào)用onInterceptTouchEvent()方法將結(jié)果賦值給intercepted
//常說(shuō)事件傳遞中的流程是:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent
//其實(shí)在這就是一個(gè)體現(xiàn),在dispatchTouchEvent()中調(diào)用了onInterceptTouchEvent()
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
/當(dāng)禁止攔截判斷時(shí)(即disallowIntercept為true)設(shè)置intercepted = false
intercepted = false;
}
} else {
//當(dāng)事件不是ACTION_DOWN并且mFirstTouchTarget為null(即沒(méi)有Touch的目標(biāo)組件)時(shí)
//設(shè)置 intercepted = true表示ViewGroup執(zhí)行Touch事件攔截的操作。
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
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);
}
/*
三慨丐、檢查cancel
*/
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
/**
* 第四步:事件分發(fā)
*/
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;//獲取新的touch事件
boolean alreadyDispatchedToNewTouchTarget = false;//判別是否分發(fā)給新的事件目標(biāo)
//不是ACTION_CANCEL并且ViewGroup的攔截標(biāo)志位intercepted為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_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);
final int childrenCount = mChildrenCount;
// 依據(jù)Touch坐標(biāo)尋找子View來(lái)接收Touch事件
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 ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
// 遍歷子View判斷哪個(gè)子View接受Touch事件
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(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;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// 找到接收Touch事件的子View!!!!!!!即為newTouchTarget.
// 既然已經(jīng)找到了,所以執(zhí)行break跳出for循環(huán)
// 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);
//調(diào)用方法dispatchTransformedTouchEvent()將Touch事件傳遞給子View做,遞歸處理(也就是遍歷該子View的View樹(shù)) ,將Touch事件傳遞給特定的子View的onTouchEvent返回true(即Touch事件被消費(fèi))那么就滿足該if條件.
//如果dispatchTransformedTouchEvent()返回true即子View
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();
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);
}
if (preorderedList != null) preorderedList.clear();
}
/**
* 該if條件表示:
* 經(jīng)過(guò)前面的for循環(huán)沒(méi)有找到子View接收Touch事件并且之前的mFirstTouchTarget不為空的時(shí)候 坡脐,就返回最初的事件
*/
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指向了最初的TouchTarget
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
/**
* 分發(fā)Touch事件至target(Dispatch to touch targets)
*
* 經(jīng)過(guò)上面對(duì)于ACTION_DOWN的處理后mFirstTouchTarget有兩種情況:
* 1 mFirstTouchTarget為null
* 2 mFirstTouchTarget不為null
*
* 當(dāng)然如果不是ACTION_DOWN就不會(huì)經(jīng)過(guò)上面較繁瑣的流程
* 而是從此處開(kāi)始執(zhí)行,比如ACTION_MOVE和ACTION_UP
*/
// Dispatch to touch targets.分發(fā)事件
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
/*
mFirstTouchTarget為null就是說(shuō)Touch事件未被消費(fèi).
即沒(méi)有找到能夠消費(fèi)touch事件的子組件或Touch事件被攔截了。
則調(diào)用ViewGroup的dispatchTransformedTouchEvent()方法處理Touch事件則和普通View一樣.
即子View沒(méi)有消費(fèi)Touch事件,那么子View的上層ViewGroup才會(huì)調(diào)用其onTouchEvent()處理Touch事件.
也就是說(shuō)此時(shí)ViewGroup像一個(gè)普通的View那樣調(diào)用dispatchTouchEvent(),
且在dispatchTouchEvent() 中會(huì)去調(diào)用onTouchEvent()方法
具體的說(shuō)就是在調(diào)用dispatchTransformedTouchEvent()時(shí)第三個(gè)參數(shù)為null.
第三個(gè)參數(shù)View child為null會(huì)做什么樣的處理呢?
請(qǐng)參見(jiàn)下面dispatchTransformedTouchEvent()的源碼分析
這就是為什么子view對(duì)于Touch事件處理返回true那么其上層的ViewGroup就無(wú)法處理Touch事件
這就是為什么子view對(duì)于Touch事件處理返回false那么其上層的ViewGroup才可以處理Touch事件
*/
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//mFirstTouchTarget不為null即找到了可以消費(fèi)Touch事件的子View且后續(xù)Touch事件可以傳遞到該子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;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//對(duì)于非ACTION_DOWN事件繼續(xù)傳遞給目標(biāo)子組件進(jìn)行處理,依然是遞歸調(diào)用dispatchTransformedTouchEvent()
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;
}
}
/**
* 處理ACTION_UP和ACTION_CANCEL
* Update list of touch targets for pointer up or cancel, if needed.
* 在此主要的操作是還原狀態(tài)
*/
// Update list of touch targets for pointer up or cancel, if needed.
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;
}
dispatchTransformedTouchEvent源碼:這個(gè)是把事件交給子view去處理的方法
/*
第一個(gè)參數(shù):傳遞事件咯
第二個(gè)參數(shù):是否還原狀態(tài)
第三個(gè)參數(shù):View child
在dispatchTouchEvent()中多次調(diào)用了dispatchTransformedTouchEvent(),但是有時(shí)候第三個(gè)參數(shù)為null,有時(shí)又不是房揭。
那么這個(gè)參數(shù)是否為null有什么區(qū)別呢备闲?
在如下dispatchTransformedTouchEvent()源碼中可見(jiàn)多次對(duì)于child是否為null的判斷,如下:
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
*/
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;//是否處理了標(biāo)記
// 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);
/*
當(dāng)child == null時(shí)會(huì)將Touch事件傳遞給該ViewGroup自身的dispatchTouchEvent()處理.
當(dāng)child != null時(shí)會(huì)調(diào)用該子view(當(dāng)然該view可能是一個(gè)View也可能是一個(gè)ViewGroup)的dispatchTouchEvent(event)處理.即child.dispatchTouchEvent(event);
*/
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
//計(jì)算傳遞的指針數(shù)
// 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.
//判斷事件一致性捅暴,傳遞的數(shù)量不一致就返回false
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.
//判斷事件一致性恬砂,一致的話,我們就不用執(zhí)行些不可逆的轉(zhuǎn)換蓬痒,只要小心回復(fù)我們所做的修改泻骤,我們就可以重用該事件。否則就要copy該事件
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
//當(dāng)child == null時(shí)會(huì)將Touch事件傳遞給該ViewGroup自身的dispatchTouchEvent()處理.
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);
}
//執(zhí)行一些必要的轉(zhuǎn)換和調(diào)度
// 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;
}
參考博客:
生命壹號(hào)
郭朝
源碼下載:Android-多列表的項(xiàng)目Rxjava+Rtrofit+Recyclerview+Glide+Adapter封裝
好了,Android--焦點(diǎn)問(wèn)題以及討論事件傳遞機(jī)制問(wèn)題講完了瞪讼。本博客是這個(gè)系列的第四篇钧椰,討論的是我在項(xiàng)目中遇到的一些細(xì)節(jié)坑,以及它們的相關(guān)機(jī)制符欠。另外嫡霞,這個(gè)系列還有一些我在外包項(xiàng)目過(guò)程中做的優(yōu)化,以及一些發(fā)布簽名等等技巧希柿,我會(huì)盡快出完給大家诊沪,分享經(jīng)驗(yàn)給大家。歡迎在下面指出錯(cuò)誤曾撤,共同學(xué)習(xí)6艘Α!你的點(diǎn)贊是對(duì)我最好的支持<废ぁ渐裸!
更多內(nèi)容,可以訪問(wèn)JackFrost的博客
//負(fù)責(zé)分發(fā)事件的這位,很重要的代碼
//看之前先記住這句話:mFirstTouchTarget為null就是說(shuō)Touch事件未被消費(fèi). 至于是什么內(nèi)容請(qǐng)仔細(xì)閱讀下文
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
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.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;//標(biāo)記這個(gè)為事件暫存值
/*
一屯远、因?yàn)锳CTION_DOWN是一系列事件的開(kāi)端,當(dāng)是ACTION_DOWN時(shí)進(jìn)行一些初始化操作.
*/
// Handle an initial 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.
//下面的方法中有一個(gè)非常重要的操作:clearTouchTargets();將mFirstTouchTarget設(shè)置為null!!!!隨后在resetTouchState()中重置Touch狀態(tài)標(biāo)識(shí)
cancelAndClearTouchTargets(ev);//清除以往的Touch狀態(tài)(state)開(kāi)始新的手勢(shì)(gesture)
resetTouchState();
}
/*
二蔓姚、檢查是否要攔截
*/
// Check for interception.
final boolean intercepted;//使用變量intercepted來(lái)標(biāo)記ViewGroup是否攔截Touch事件的傳遞.
// 事件為ACTION_DOWN或者mFirstTouchTarget不為null(即已經(jīng)找到能夠接收touch事件的目標(biāo)組件)時(shí)if成立
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//判斷disallowIntercept(禁止攔截)標(biāo)志位
//因?yàn)樵谄渌胤娇赡苷{(diào)用了requestDisallowInterceptTouchEvent(boolean disallowIntercept)
//從而禁止執(zhí)行是否需要攔截的判斷(有點(diǎn)拗口~其實(shí)看requestDisallowInterceptTouchEvent()方法名就可明白)
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//當(dāng)沒(méi)有禁止攔截判斷時(shí)(即disallowIntercept為false)調(diào)用onInterceptTouchEvent(ev)方法
if (!disallowIntercept) {
//既然disallowIntercept為false那么就調(diào)用onInterceptTouchEvent()方法將結(jié)果賦值給intercepted
//常說(shuō)事件傳遞中的流程是:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent
//其實(shí)在這就是一個(gè)體現(xiàn),在dispatchTouchEvent()中調(diào)用了onInterceptTouchEvent()
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
/當(dāng)禁止攔截判斷時(shí)(即disallowIntercept為true)設(shè)置intercepted = false
intercepted = false;
}
} else {
//當(dāng)事件不是ACTION_DOWN并且mFirstTouchTarget為null(即沒(méi)有Touch的目標(biāo)組件)時(shí)
//設(shè)置 intercepted = true表示ViewGroup執(zhí)行Touch事件攔截的操作。
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
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);
}
/*
三慨丐、檢查cancel
*/
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
/**
* 第四步:事件分發(fā)
*/
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;//獲取新的touch事件
boolean alreadyDispatchedToNewTouchTarget = false;//判別是否分發(fā)給新的事件目標(biāo)
//不是ACTION_CANCEL并且ViewGroup的攔截標(biāo)志位intercepted為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_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);
final int childrenCount = mChildrenCount;
// 依據(jù)Touch坐標(biāo)尋找子View來(lái)接收Touch事件
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 ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
// 遍歷子View判斷哪個(gè)子View接受Touch事件
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(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;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// 找到接收Touch事件的子View!!!!!!!即為newTouchTarget.
// 既然已經(jīng)找到了,所以執(zhí)行break跳出for循環(huán)
// 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);
//調(diào)用方法dispatchTransformedTouchEvent()將Touch事件傳遞給子View做,遞歸處理(也就是遍歷該子View的View樹(shù)) ,將Touch事件傳遞給特定的子View的onTouchEvent返回true(即Touch事件被消費(fèi))那么就滿足該if條件.
//如果dispatchTransformedTouchEvent()返回true即子View
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();
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);
}
if (preorderedList != null) preorderedList.clear();
}
/**
* 該if條件表示:
* 經(jīng)過(guò)前面的for循環(huán)沒(méi)有找到子View接收Touch事件并且之前的mFirstTouchTarget不為空的時(shí)候 坡脐,就返回最初的事件
*/
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指向了最初的TouchTarget
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
/**
* 分發(fā)Touch事件至target(Dispatch to touch targets)
*
* 經(jīng)過(guò)上面對(duì)于ACTION_DOWN的處理后mFirstTouchTarget有兩種情況:
* 1 mFirstTouchTarget為null
* 2 mFirstTouchTarget不為null
*
* 當(dāng)然如果不是ACTION_DOWN就不會(huì)經(jīng)過(guò)上面較繁瑣的流程
* 而是從此處開(kāi)始執(zhí)行,比如ACTION_MOVE和ACTION_UP
*/
// Dispatch to touch targets.分發(fā)事件
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
/*
mFirstTouchTarget為null就是說(shuō)Touch事件未被消費(fèi).
即沒(méi)有找到能夠消費(fèi)touch事件的子組件或Touch事件被攔截了。
則調(diào)用ViewGroup的dispatchTransformedTouchEvent()方法處理Touch事件則和普通View一樣.
即子View沒(méi)有消費(fèi)Touch事件,那么子View的上層ViewGroup才會(huì)調(diào)用其onTouchEvent()處理Touch事件.
也就是說(shuō)此時(shí)ViewGroup像一個(gè)普通的View那樣調(diào)用dispatchTouchEvent(),
且在dispatchTouchEvent() 中會(huì)去調(diào)用onTouchEvent()方法
具體的說(shuō)就是在調(diào)用dispatchTransformedTouchEvent()時(shí)第三個(gè)參數(shù)為null.
第三個(gè)參數(shù)View child為null會(huì)做什么樣的處理呢?
請(qǐng)參見(jiàn)下面dispatchTransformedTouchEvent()的源碼分析
這就是為什么子view對(duì)于Touch事件處理返回true那么其上層的ViewGroup就無(wú)法處理Touch事件
這就是為什么子view對(duì)于Touch事件處理返回false那么其上層的ViewGroup才可以處理Touch事件
*/
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//mFirstTouchTarget不為null即找到了可以消費(fèi)Touch事件的子View且后續(xù)Touch事件可以傳遞到該子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;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//對(duì)于非ACTION_DOWN事件繼續(xù)傳遞給目標(biāo)子組件進(jìn)行處理,依然是遞歸調(diào)用dispatchTransformedTouchEvent()
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;
}
}
/**
* 處理ACTION_UP和ACTION_CANCEL
* Update list of touch targets for pointer up or cancel, if needed.
* 在此主要的操作是還原狀態(tài)
*/
// Update list of touch targets for pointer up or cancel, if needed.
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;
}
/*
第一個(gè)參數(shù):傳遞事件咯
第二個(gè)參數(shù):是否還原狀態(tài)
第三個(gè)參數(shù):View child
在dispatchTouchEvent()中多次調(diào)用了dispatchTransformedTouchEvent(),但是有時(shí)候第三個(gè)參數(shù)為null,有時(shí)又不是房揭。
那么這個(gè)參數(shù)是否為null有什么區(qū)別呢备闲?
在如下dispatchTransformedTouchEvent()源碼中可見(jiàn)多次對(duì)于child是否為null的判斷,如下:
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
*/
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;//是否處理了標(biāo)記
// 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);
/*
當(dāng)child == null時(shí)會(huì)將Touch事件傳遞給該ViewGroup自身的dispatchTouchEvent()處理.
當(dāng)child != null時(shí)會(huì)調(diào)用該子view(當(dāng)然該view可能是一個(gè)View也可能是一個(gè)ViewGroup)的dispatchTouchEvent(event)處理.即child.dispatchTouchEvent(event);
*/
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
//計(jì)算傳遞的指針數(shù)
// 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.
//判斷事件一致性捅暴,傳遞的數(shù)量不一致就返回false
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.
//判斷事件一致性恬砂,一致的話,我們就不用執(zhí)行些不可逆的轉(zhuǎn)換蓬痒,只要小心回復(fù)我們所做的修改泻骤,我們就可以重用該事件。否則就要copy該事件
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
//當(dāng)child == null時(shí)會(huì)將Touch事件傳遞給該ViewGroup自身的dispatchTouchEvent()處理.
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);
}
//執(zhí)行一些必要的轉(zhuǎn)換和調(diào)度
// 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;
}