分析Android源碼 View 焦點(diǎn)獲取

平常做手機(jī)開發(fā)的時(shí)候不太需要關(guān)心焦點(diǎn)獲取,但是如果做Android Tv或盒子開發(fā)的時(shí)候就要做好View之間焦點(diǎn)切換了。

View焦點(diǎn)流程圖

viewfocus.jpeg

源碼分析

view通過(guò)以下幾個(gè)方法來(lái)獲取焦點(diǎn)

  • View.java -> requestFocus(int direction, Rect previouslyFocusedRect)
public final boolean requestFocus() {
        return requestFocus(View.FOCUS_DOWN);
}
public final boolean requestFocus(int direction) {
        return requestFocus(direction, null);
 }
 public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
        return requestFocusNoSearch(direction, previouslyFocusedRect);
    }

從代碼中可以看到最終都會(huì)調(diào)到requestFocusNoSearch方法

  • view.java -> requestFocusNoSearch(int direction, Rect previouslyFocusedRect)
private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
        // 先判斷當(dāng)前視圖是否具有獲取焦點(diǎn)的能力,如果沒有獲取焦點(diǎn)的能力或沒有顯示則直接返回false
        if ((mViewFlags & FOCUSABLE) != FOCUSABLE
                || (mViewFlags & VISIBILITY_MASK) != VISIBLE) {
            return false;
        }

        // 判斷是否在Touch模式下,如果是在touch模式下,就必須要有Touch模式下獲取焦點(diǎn)的能力(FOCUSABLE_IN_TOUCH_MODE)
        if (isInTouchMode() &&
            (FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
               return false;
        }

        // 判斷父容器是否攔截當(dāng)前視圖獲取焦點(diǎn)(注意:是一層一層遍歷所有的父容器不是只判斷當(dāng)前視圖的直接父容器),如果某一個(gè)父容器阻止則直接返回false,可以通過(guò)設(shè)置setDecendantFocusability(int focusability)方法來(lái)設(shè)置ViewGroup是否阻止子視圖獲取焦點(diǎn),默認(rèn)是不阻止的
        if (hasAncestorThatBlocksDescendantFocus()) {
            return false;
        }
        //真正獲取焦點(diǎn)的函數(shù),執(zhí)行完后返回true
        handleFocusGainInternal(direction, previouslyFocusedRect);
        return true;
    }
  • View.java -> handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect)
void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
        if (DBG) {
            System.out.println(this + " requestFocus()");
        }
        //先判斷是否已獲取焦點(diǎn),防止多次調(diào)用
        if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
            //設(shè)置當(dāng)前視圖狀態(tài)
            mPrivateFlags |= PFLAG_FOCUSED;

            View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;

            if (mParent != null) {
                //調(diào)用父容器的requestChildFocus方法,第一個(gè)參數(shù)為當(dāng)前視圖,第二個(gè)視圖為真正要獲取的焦點(diǎn)的視圖
                mParent.requestChildFocus(this, this);
                updateFocusedInCluster(oldFocus, direction);
            }

            if (mAttachInfo != null) {
                mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
            }
            //執(zhí)行回調(diào)方法,并且觸發(fā)invalidate進(jìn)行刷新
            onFocusChanged(true, direction, previouslyFocusedRect);
            //更新繪制狀態(tài)
            refreshDrawableState();
        }
    }
  • ViewGroup.java ->requestChildFocus(View child, View focused)
    public void requestChildFocus(View child, View focused) {
        if (DBG) {
            System.out.println(this + " requestChildFocus()");
        }
        //先判斷DescendantFocusability是否為FOCUS_BLOCK_DESCENDANTS, 也就是是否阻止子視圖獲取焦點(diǎn),如果阻止則直接跳出
        if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
            return;
        }

        // 釋放當(dāng)前視圖的焦點(diǎn)
        super.unFocus();

        // mFocused是之前有焦點(diǎn)的子視圖,如果之前獲取焦點(diǎn)的視圖和當(dāng)前要獲取焦點(diǎn)視圖不是同一個(gè)的話就更新
        if (mFocused != child) {
            if (mFocused != null) {
            //釋放焦點(diǎn)
                mFocused.unFocus(focused);
            }
            //重新賦值
            mFocused = child;
        }
        //逐層上調(diào)requestChildFocus方法,直到View樹的root節(jié)點(diǎn)
        if (mParent != null) {
            mParent.requestChildFocus(this, focused);
        }
    }

ViewGroup獲取焦點(diǎn)處理

ViewGroup獲取焦點(diǎn)比View獲取焦點(diǎn)要稍微復(fù)雜點(diǎn)多了分發(fā)機(jī)制

  • ViewGroup.java -> requestFocus(int direction, Rect previouslyFocusedRect)
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
        if (DBG) {
            System.out.println(this + " ViewGroup.requestFocus direction="
                    + direction);
        }
        //獲取當(dāng)前焦點(diǎn)分發(fā)機(jī)制
        int descendantFocusability = getDescendantFocusability();

        switch (descendantFocusability) {
            case FOCUS_BLOCK_DESCENDANTS:       //阻止子View獲取焦點(diǎn)
                return super.requestFocus(direction, previouslyFocusedRect);
            case FOCUS_BEFORE_DESCENDANTS: {    //先交給自身獲取焦點(diǎn),如果自身不處理則交給子視圖處理(默認(rèn))
                final boolean took = super.requestFocus(direction, previouslyFocusedRect);
                return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect);
            }
            case FOCUS_AFTER_DESCENDANTS: { //先交給子視圖處理,如果子不處理再交給自身處理
                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);
        }
    }

onRequestFocusInDescendants方法是調(diào)用子視圖獲取焦點(diǎn)的方法,默認(rèn)是按照子視圖順序處理,direction如果向下或向右則從第一個(gè)開始,如果向上或向左則從最后一個(gè)開始遍歷,直到某個(gè)子視圖獲取焦點(diǎn)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容