View.requestFocus聚焦源碼分析

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ū)別開hasFocusisFocused谎碍。這里一層層的往上走,最終會(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也處理完畢了梳码。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末隐圾,一起剝皮案震驚了整個(gè)濱河市伍掀,隨后出現(xiàn)的幾起案子掰茶,更是在濱河造成了極大的恐慌,老刑警劉巖蜜笤,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件濒蒋,死亡現(xiàn)場離奇詭異,居然都是意外死亡把兔,警方通過查閱死者的電腦和手機(jī)沪伙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來县好,“玉大人围橡,你說我怎么就攤上這事÷乒保” “怎么了翁授?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長晾咪。 經(jīng)常有香客問我收擦,道長,這世上最難降的妖魔是什么谍倦? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任塞赂,我火速辦了婚禮,結(jié)果婚禮上昼蛀,老公的妹妹穿的比我還像新娘宴猾。我一直安慰自己,他們只是感情好叼旋,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布鳍置。 她就那樣靜靜地躺著,像睡著了一般送淆。 火紅的嫁衣襯著肌膚如雪税产。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音辟拷,去河邊找鬼撞羽。 笑死,一個(gè)胖子當(dāng)著我的面吹牛衫冻,可吹牛的內(nèi)容都是我干的诀紊。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼隅俘,長吁一口氣:“原來是場噩夢啊……” “哼邻奠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起为居,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤碌宴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蒙畴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贰镣,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年膳凝,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了碑隆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蹬音,死狀恐怖上煤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情著淆,我是刑警寧澤劫狠,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站牧抽,受9級(jí)特大地震影響嘉熊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜扬舒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一阐肤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧讲坎,春花似錦孕惜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至瓮栗,卻和暖如春削罩,著一層夾襖步出監(jiān)牢的瞬間瞄勾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國打工弥激, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留进陡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓微服,卻偏偏與公主長得像趾疚,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子以蕴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容