View.requestFocus源碼分析
我們需要某個(gè)控件View進(jìn)行聚焦埋酬,一般會(huì)主動(dòng)調(diào)用該控件的requestFocus
方法柱告。(本文基于API 27源碼進(jìn)行分析)
<View.java>
public final boolean requestFocus() {
// 默認(rèn)使用FOCUS_DOWN進(jìn)行聚焦
return requestFocus(View.FOCUS_DOWN);
}
一步步往下跟,requestFocus
接著會(huì)走到requestFocusNoSearch
方法中,看方法名就能理解皮钠,因?yàn)槲覀兪侵苯?code>requestFocus,意圖就是指定某個(gè)View獲得焦點(diǎn)裂允,所以不需要走尋焦機(jī)制:
<View.java>
private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
// need to be focusable
// 如果該view設(shè)置的focusable = false近弟,直接返回
if ((mViewFlags & FOCUSABLE) != FOCUSABLE
|| (mViewFlags & VISIBILITY_MASK) != VISIBLE) {
return false;
}
// need to be focusable in touch mode if in touch mode
// 觸摸模式下
if (isInTouchMode() &&
(FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
return false;// 如果該view設(shè)置的focusableInTouchMode = false,直接返回
}
// need to not have any parents blocking us
if (hasAncestorThatBlocksDescendantFocus()) {
return false;// 如果parent中設(shè)置了FOCUS_BLOCK_DESCENDANTS,直接返回
}
handleFocusGainInternal(direction, previouslyFocusedRect);
return true;// 聚焦成功
}
- 首先第一步會(huì)判斷當(dāng)前的View的focusable狀態(tài),如果是false,說明該View并不能獲取焦點(diǎn)涂圆,也就沒有必要再往下走了。
- 接著會(huì)判斷是否觸摸模式币叹,在觸摸模式下润歉,如果focusableInTouchMode是false的話,也說明該View通過觸摸并不能獲取焦點(diǎn)颈抚,也沒必要往下走了踩衩。
- 繼續(xù)看下面一個(gè)判斷
hasAncestorThatBlocksDescendantFocus()
方法:
<View.java>
private boolean hasAncestorThatBlocksDescendantFocus() {
final boolean focusableInTouchMode = isFocusableInTouchMode();
ViewParent ancestor = mParent;
while (ancestor instanceof ViewGroup) {
final ViewGroup vgAncestor = (ViewGroup) ancestor;
if (vgAncestor.getDescendantFocusability() == ViewGroup.FOCUS_BLOCK_DESCENDANTS
|| (!focusableInTouchMode && vgAncestor.shouldBlockFocusForTouchscreen())) {
return true;
} else {
ancestor = vgAncestor.getParent();
}
}
return false;
}
這個(gè)方法也就是遍歷parent父View查找是否有設(shè)置FOCUS_BLOCK_DESCENDANTS,如果設(shè)置了贩汉,說明父View把焦點(diǎn)傳遞給攔截了驱富,并不希望自己獲得焦點(diǎn),因此該方法會(huì)返回true匹舞『峙福回到requestFocusNoSearch
中,也就直接return不往下走了赐稽。
經(jīng)過一系列的條件判斷叫榕,如果可聚焦,并且父View未攔截焦點(diǎn)姊舵,最終走到核心方法handleFocusGainInternal
中:
<View.java>
void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
if (DBG) {
System.out.println(this + " requestFocus()");
}
if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
mPrivateFlags |= PFLAG_FOCUSED;// 更新標(biāo)記位(isFocused判斷依據(jù))
// 當(dāng)前狀態(tài)下的焦點(diǎn)
View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;
if (mParent != null) {
mParent.requestChildFocus(this, this);// 清除當(dāng)前焦點(diǎn)晰绎,將mFocus變量更新值為當(dāng)前期望聚焦的view
updateFocusedInCluster(oldFocus, direction);// android高版本新增的方法,此方法和鍵盤相關(guān)括丁,在此不作重點(diǎn)關(guān)注
}
if (mAttachInfo != null) {
// 通知ViewTreeObserver焦點(diǎn)變化
mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
}
onFocusChanged(true, direction, previouslyFocusedRect);// 通知焦點(diǎn)變化回調(diào)
refreshDrawableState();// 當(dāng)前view聚焦荞下,刷新drawable狀態(tài)
}
}
這個(gè)方法中,首先會(huì)更新當(dāng)前View的標(biāo)記位mPrivateFlags
記錄自己的isFocused
狀態(tài),接著通過rootView查找到當(dāng)前的焦點(diǎn)賦值給oldFocus锄弱,然后調(diào)用parent的requestChildFocus
方法告知parent自己當(dāng)前聚焦啦。
<ViewGroup.java>
@Override
public void requestChildFocus(View child, View focused) {
if (DBG) {
System.out.println(this + " requestChildFocus()");
}
// 再次判斷是否設(shè)置了FOCUS_BLOCK_DESCENDANTS
if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
return;
}
// Unfocus us, if necessary
super.unFocus(focused);
// We had a previous notion of who had focus. Clear it.
if (mFocused != child) {
if (mFocused != null) {
mFocused.unFocus(focused);
}
mFocused = child;
}
if (mParent != null) {
mParent.requestChildFocus(this, focused);
}
}
在這個(gè)方法里祸憋,focused這個(gè)參數(shù)其實(shí)沒用到(unFocus作為形參傳入会宪,其實(shí)里面也根本沒用到該參數(shù)),在上面第一次調(diào)用時(shí)傳入的都是this蚯窥,這個(gè)focused實(shí)際上就是直接的焦點(diǎn)掸鹅,child在第一次調(diào)用時(shí)也是直接焦點(diǎn),child == focused拦赠,但是通過mParent.requestChildFocus(this, focused);
后巍沙,child這個(gè)參數(shù)就變成了直接焦點(diǎn)的父View,一層一層往上進(jìn)行調(diào)用以此類推荷鼠,這里要重點(diǎn)區(qū)分下兩個(gè)參數(shù)含義句携。下面是官方對(duì)該參數(shù)的注釋:
<ViewParent.java>
/**
* Called when a child of this parent wants focus
*
* @param child The child of this ViewParent that wants focus. This view
* will contain the focused view. It is not necessarily the view that
* actually has focus.
* @param focused The view that is a descendant of child that actually has
* focus
*/
public void requestChildFocus(View child, View focused);
在每個(gè)ViewGroup中都有一個(gè)mFocus變量,該變量的作用就是保存著當(dāng)前ViewGroup下的焦點(diǎn)允乐,并非直接焦點(diǎn)矮嫉。(官方對(duì)這個(gè)變量含義的注釋:The view contained within this ViewGroup that has or contains focus.
)
接著回到這個(gè)requestChildFocus(View child, View focused)
方法接著往下看,具體邏輯是:
- 再次判斷是否設(shè)置了FOCUS_BLOCK_DESCENDANTS牍疏,如果攔截則不繼續(xù)往下走蠢笋。
- 一般情況下,當(dāng)前焦點(diǎn)mFocused都和我們期望聚焦的view并非同一個(gè)鳞陨,則進(jìn)入分支調(diào)用
mFocused.unFocus(focused)
<View.java>
void unFocus(View focused) {
if (DBG) {
System.out.println(this + " unFocus()");
}
clearFocusInternal(focused, false, false);
}
// 最終調(diào)用這個(gè)方法
void clearFocusInternal(View focused, boolean propagate, boolean refocus) {
// 其實(shí)沒用到focused這個(gè)參數(shù)
if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
mPrivateFlags &= ~PFLAG_FOCUSED;
if (propagate && mParent != null) {
mParent.clearChildFocus(this);// 通知parent清除自己(當(dāng)前的焦點(diǎn))的mFocus值昨寞,因?yàn)榻裹c(diǎn)已經(jīng)不在該View樹節(jié)點(diǎn)下
}
onFocusChanged(false, 0, null);// 回調(diào)焦點(diǎn)狀態(tài)變更的通知
refreshDrawableState();// 刷新失去焦點(diǎn)后的drawable狀態(tài)
if (propagate && (!refocus || !rootViewRequestFocus())) {
notifyGlobalFocusCleared(this);
}
}
}
clearFocusInternal
這個(gè)方法是mFocus進(jìn)行調(diào)用的,也就是對(duì)當(dāng)前的焦點(diǎn)所在的View進(jìn)行清除焦點(diǎn)狀態(tài)處理厦滤,主要做了下面幾件事:
- 通知parent調(diào)用
clearChildFocus
將mFocus變量置null援岩,因?yàn)榻裹c(diǎn)已經(jīng)不在該View樹節(jié)點(diǎn)下。 - 回調(diào)自身的焦點(diǎn)狀態(tài)變更的通知掏导,我們通常所設(shè)置的
setOnFocusChangeListener
的監(jiān)聽就是在這里面進(jìn)行觸發(fā)回調(diào)的窄俏。 - 由于第1步中清除了自己的焦點(diǎn)狀態(tài),失焦之后自然需要刷新視圖狀態(tài)碘菜,這里會(huì)調(diào)用
refreshDrawableState
進(jìn)行drawableState的刷新凹蜈,也就是我們通常在xml中設(shè)置的selector狀態(tài)屬性。
注意一點(diǎn):這里面的focused參數(shù)其實(shí)根本沒用到忍啸,但是這個(gè)focused才是真正最直接的焦點(diǎn)仰坦。
清除了當(dāng)前焦點(diǎn)之后,回到parent的requestChildFocus
中计雌,將我們期望聚焦的child賦值給mFocused悄晃,前面說過這個(gè)mFocus變量就是保存著當(dāng)前的焦點(diǎn),走到這步,我們調(diào)用View.requestFocus
已經(jīng)成功將焦點(diǎn)從oldFocus轉(zhuǎn)移到newFocus上了妈橄。
<ViewGroup.java>
ViewGroup.requestChildFocus
...
// We had a previous notion of who had focus. Clear it.
if (mFocused != child) {
if (mFocused != null) {
mFocused.unFocus(focused);
}
mFocused = child;
}
if (mParent != null) {
mParent.requestChildFocus(this, focused);
}
...
接下去還會(huì)再次通過parent一層一層的告訴父View庶近,當(dāng)前焦點(diǎn)在我這。也就是說某一個(gè)子View如果聚焦了眷蚓,它會(huì)將自己賦值給parent的mFocus變量鼻种,這樣下次查找焦點(diǎn),就可以通過頂層的parent一級(jí)一級(jí)通過mFocus變量進(jìn)行findFocus查找到最下層的直接焦點(diǎn)沙热。這里展開一下findFocus
方法就很明白了:
<ViewGroup.java>
@Override
public View findFocus() {
if (DBG) {
System.out.println("Find focus in " + this + ": flags="
+ isFocused() + ", child=" + mFocused);
}
// 如果當(dāng)前isFocused了叉钥,說明我自己已經(jīng)是焦點(diǎn)了,直接返回自己
if (isFocused()) {
return this;
}
// mFocus不為null篙贸,說明焦點(diǎn)在這個(gè)mFocus的View樹下
if (mFocused != null) {
return mFocused.findFocus();
}
return null;
}
<View.java>
public View findFocus() {
// 當(dāng)遍歷到直接子View之后就是根據(jù)標(biāo)志位進(jìn)行判斷
return (mPrivateFlags & PFLAG_FOCUSED) != 0 ? this : null;
}
舉個(gè)例子:A包含B投队,B包含C,A和B都是ViewGroup爵川,C是直接View敷鸦,A的mFocus是B,B的mFocus是C寝贡,注意一點(diǎn)轧膘,這里面只是說mFocus != null
,并不是說A和B的isFocused
也是true的兔甘,要區(qū)別開hasFocus
和isFocused
谎碍。這里一層層的往上走,最終會(huì)走到ViewRootImpl的requestChildFocus進(jìn)行UI重繪洞焙。
<ViewGroup.java>
public boolean hasFocus() {
return (mPrivateFlags & PFLAG_FOCUSED) != 0 || mFocused != null;
}
<ViewRootImpl.java>
@Override
public void requestChildFocus(View child, View focused) {
if (DEBUG_INPUT_RESIZE) {
Log.v(mTag, "Request child focus: focus now " + focused);
}
checkThread();
scheduleTraversals();// UI重繪
}
回到上面蟆淀,handleFocusGainInternal
中的mPrivateFlags |= PFLAG_FOCUSED;
這里修改了標(biāo)記位,實(shí)際上isFocused就是通過這個(gè)標(biāo)記位進(jìn)行判斷的澡匪。
<View.java>
@ViewDebug.ExportedProperty(category = "focus")
public boolean isFocused() {
return (mPrivateFlags & PFLAG_FOCUSED) != 0;
}
至此熔任,View.requestFocus
的調(diào)用流程結(jié)束,焦點(diǎn)已經(jīng)從之前的oldFocus轉(zhuǎn)移到新的newFocus上了唁情。接下來疑苔,我們繼續(xù)分析下ViewGroup.requestFocus
方法:
<ViewGroup.java>
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
if (DBG) {
System.out.println(this + " ViewGroup.requestFocus direction="
+ direction);
}
int descendantFocusability = getDescendantFocusability();
// 主要還是看ViewGroup設(shè)置的焦點(diǎn)攔截模式
switch (descendantFocusability) {
case FOCUS_BLOCK_DESCENDANTS:// 攔截掉了焦點(diǎn),直接調(diào)用super的邏輯在自己中requestFocus
return super.requestFocus(direction, previouslyFocusedRect);
case FOCUS_BEFORE_DESCENDANTS: {// 首先調(diào)用super的邏輯在自己中requestFocus甸鸟,如果自己請求焦點(diǎn)失敗再遍歷子View進(jìn)行requestFocus
final boolean took = super.requestFocus(direction, previouslyFocusedRect);
return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect);
}
case FOCUS_AFTER_DESCENDANTS: {// 與FOCUS_BEFORE_DESCENDANTS相反惦费,先遍歷子View進(jìn)行requestFocus,如果子View都請求焦點(diǎn)失敗后再調(diào)用super的邏輯在自己中requestFocus
final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);
return took ? took : super.requestFocus(direction, previouslyFocusedRect);
}
default:
throw new IllegalStateException("descendant focusability must be "
+ "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS "
+ "but is " + descendantFocusability);
}
}
這里面的邏輯還是很清晰的抢韭,主要判斷依據(jù)就是焦點(diǎn)攔截模式descendantFocusability
:
- FOCUS_BLOCK_DESCENDANTS:自身攔截掉焦點(diǎn)薪贫,直接對(duì)自己進(jìn)行requestFocus調(diào)用去請求焦點(diǎn)
- FOCUS_BEFORE_DESCENDANTS:自身優(yōu)先子View獲得焦點(diǎn),先對(duì)自己進(jìn)行requestFocus調(diào)用去請求焦點(diǎn)刻恭,如果失敗再遍歷子View讓子View進(jìn)行聚焦
- FOCUS_AFTER_DESCENDANTS:先遍歷子View讓子View進(jìn)行聚焦瞧省,如果子View都沒有聚焦,則再對(duì)自己進(jìn)行requestFocus調(diào)用去請求焦點(diǎn)
下面我們看下onRequestFocusInDescendants
里做了些什么:
<ViewGroup.java>
protected boolean onRequestFocusInDescendants(int direction,
Rect previouslyFocusedRect) {
int index;
int increment;
int end;
int count = mChildrenCount;
if ((direction & FOCUS_FORWARD) != 0) {// 從前往后遍歷
index = 0;
increment = 1;
end = count;
} else {// 從后往前遍歷
index = count - 1;
increment = -1;
end = -1;
}
final View[] children = mChildren;// mChildren數(shù)組中保存了所有的childView
for (int i = index; i != end; i += increment) {
View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {// 遍歷子View,并且View可見
if (child.requestFocus(direction, previouslyFocusedRect)) {// 該子View請求焦點(diǎn)
return true;// 請求焦點(diǎn)成功鞍匾,直接返回
}
}
}
return false;
}
onRequestFocusInDescendants
主要功能就是遍歷該ViewGroup下所有子View交洗,然后對(duì)可見的子View調(diào)用requestFocus,如果請求焦點(diǎn)成功橡淑,則直接返回true构拳,至此,ViewGroup.requestFocus
也處理完畢了梳码。