View Focus的處理過程及ViewGroup的mFocused字段分析

  經過上篇的介紹,咱們知道在對KeyEvent的處理中有很是重要的一環,那就是KeyEvent在focus view的path上自上而下的分發,html

換句話說只有focus的view纔有資格參與KeyEvent的處理,因此說focused view在KeyEvent的處理中很重要,咱們須要弄清楚明白android

focus view是如何設置以及改變的。算法

  經過Android官方文檔http://developer.android.com/reference/android/view/View.html中關於Focus Handling的介紹,app

咱們知道framework會根據用戶的輸入處理常規的focus移動,包括當刪除、隱藏或添加新的view時改變focus。一個view有資格得到ide

focus的前提是isFocusable()方法返回true,你能夠經過setFocusable(boolean)方法來設置它。另外當在touch mode下的時候,還佈局

須要isFocusableInTouchMode()也返回true,你也能夠經過setFocusableInTouchMode(boolean)來設置它。focus的移動是基於這ui

樣的算法,它嘗試在某個給定的方向上找最臨近的view,設置它爲新的focus。在極個別狀況,若是默認的算法不符合你的需求,你也能夠this

在xml佈局文件中經過顯式指定nextFocusDown/Left/Right/Up這些屬性來代表focus移動的順序。在運行時刻,你也能夠經過調用spa

View.requestFocus()方法來動態地讓某個view得到focus。做爲開始,咱們先看看這幾個具有得到焦點前提的方法,以下:code

    /**
     * Returns whether this View is able to take focus.
     *
     * @return True if this view can take focus, or false otherwise.
     * @attr ref android.R.styleable#View_focusable
     */
    @ViewDebug.ExportedProperty(category = "focus")
    public final boolean isFocusable() {
        return FOCUSABLE == (mViewFlags & FOCUSABLE_MASK); // 各類位操做,不熟悉、習慣的同窗能夠翻本C語言的書看看,
    }                                                      // 這裏順便推薦下《C Primer Plus》,一本足矣,並且裏面
                                                           // 有一章是專門介紹bit操做的應用的,很是贊!!!
    /**
     * When a view is focusable, it may not want to take focus when in touch mode.
     * For example, a button would like focus when the user is navigating via a D-pad
     * so that the user can click on it, but once the user starts touching the screen,
     * the button shouldn't take focus
     * @return Whether the view is focusable in touch mode.
     * @attr ref android.R.styleable#View_focusableInTouchMode
     */
    @ViewDebug.ExportedProperty
    public final boolean isFocusableInTouchMode() {
        return FOCUSABLE_IN_TOUCH_MODE == (mViewFlags & FOCUSABLE_IN_TOUCH_MODE);
    }

    /**
     * Set whether this view can receive the focus.
     *
     * Setting this to false will also ensure that this view is not focusable
     * in touch mode.
     *
     * @param focusable If true, this view can receive the focus.
     *
     * @see #setFocusableInTouchMode(boolean)
     * @attr ref android.R.styleable#View_focusable
     */
    public void setFocusable(boolean focusable) {
        if (!focusable) { // 注意:是false的時候,會順便保證在touch mode下也不能得到focus
            setFlags(0, FOCUSABLE_IN_TOUCH_MODE);
        }
        setFlags(focusable ? FOCUSABLE : NOT_FOCUSABLE, FOCUSABLE_MASK); // 設置FOCUSABLEB位
    }

    /**
     * Set whether this view can receive focus while in touch mode.
     *
     * Setting this to true will also ensure that this view is focusable.
     *
     * @param focusableInTouchMode If true, this view can receive the focus while
     *   in touch mode.
     *
     * @see #setFocusable(boolean)
     * @attr ref android.R.styleable#View_focusableInTouchMode
     */
    public void setFocusableInTouchMode(boolean focusableInTouchMode) {
        // Focusable in touch mode should always be set before the focusable flag
        // otherwise, setting the focusable flag will trigger a focusableViewAvailable()
        // which, in touch mode, will not successfully request focus on this view
        // because the focusable in touch mode flag is not set
        setFlags(focusableInTouchMode ? FOCUSABLE_IN_TOUCH_MODE : 0, FOCUSABLE_IN_TOUCH_MODE);
        if (focusableInTouchMode) { // 若是是true順便打開FOCUSABLE位
            setFlags(FOCUSABLE, FOCUSABLE_MASK);
        }
    }

    接下來咱們就看看本文的重點View.requestFocus()等相關方法,代碼以下:

/**
     * Call this to try to give focus to a specific view or to one of its
     * descendants.
     *
     * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns
     * false), or if it is focusable and it is not focusable in touch mode
     * ({@link #isFocusableInTouchMode}) while the device is in touch mode.
     *
     * See also {@link #focusSearch(int)}, which is what you call to say that you
     * have focus, and you want your parent to look for the next one.
     *
     * This is equivalent to calling {@link #requestFocus(int, Rect)} with arguments
     * {@link #FOCUS_DOWN} and <code>null</code>.
     *
     * @return Whether this view or one of its descendants actually took focus.
     */
    public final boolean requestFocus() {
        return requestFocus(View.FOCUS_DOWN);
    }

    /**
     * Call this to try to give focus to a specific view or to one of its
     * descendants and give it a hint about what direction focus is heading.
     *
     * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns
     * false), or if it is focusable and it is not focusable in touch mode
     * ({@link #isFocusableInTouchMode}) while the device is in touch mode.
     *
     * See also {@link #focusSearch(int)}, which is what you call to say that you
     * have focus, and you want your parent to look for the next one.
     *
     * This is equivalent to calling {@link #requestFocus(int, Rect)} with
     * <code>null</code> set for the previously focused rectangle.
     *
     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
     * @return Whether this view or one of its descendants actually took focus.
     */
    public final boolean requestFocus(int direction) {
        return requestFocus(direction, null);
    }

    /**
     * Call this to try to give focus to a specific view or to one of its descendants
     * and give it hints about the direction and a specific rectangle that the focus
     * is coming from.  The rectangle can help give larger views a finer grained hint
     * about where focus is coming from, and therefore, where to show selection, or
     * forward focus change internally.
     *
     * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns
     * false), or if it is focusable and it is not focusable in touch mode
     * ({@link #isFocusableInTouchMode}) while the device is in touch mode.
     *
     * A View will not take focus if it is not visible.
     *
     * A View will not take focus if one of its parents has
     * {@link android.view.ViewGroup#getDescendantFocusability()} equal to
     * {@link ViewGroup#FOCUS_BLOCK_DESCENDANTS}.
     *
     * See also {@link #focusSearch(int)}, which is what you call to say that you
     * have focus, and you want your parent to look for the next one.
     *
     * You may wish to override this method if your custom {@link View} has an internal
     * {@link View} that it wishes to forward the request to.
     *
     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
     * @param previouslyFocusedRect The rectangle (in this View's coordinate system)
     *        to give a finer grained hint about where focus is coming from.  May be null
     *        if there is no hint.
     * @return Whether this view or one of its descendants actually took focus.
     */
    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
        return requestFocusNoSearch(direction, previouslyFocusedRect);
    }

    private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) { // 此方法就是最終被調用的版本
        // need to be focusable
        if ((mViewFlags & FOCUSABLE_MASK) != FOCUSABLE || // 不是FOCUSABLE,即沒資格獲取焦點
                (mViewFlags & VISIBILITY_MASK) != VISIBLE) { // 或者不是VISIBLE的,都直接返回false,表示請求獲取焦點失敗
            return false; // 因此除了上文提到的2個獲取focus的前提,其實這裏的VISIBLE也應該算是第3個前提吧!
        }

        // need to be focusable in touch mode if in touch mode
        if (isInTouchMode() && // 一樣在touch mode下,也要檢測FOCUSABLE_IN_TOUCH_MODE標誌
            (FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
               return false; // 不知足也直接返回false,表示失敗
        }

        // need to not have any parents blocking us
        if (hasAncestorThatBlocksDescendantFocus()) { // parents阻止咱們得到焦點的話,咱們也只能以失敗了結
            return false;
        }
        // 以上重重關卡都經過了,纔會走到這裏,真正設置focus
        handleFocusGainInternal(direction, previouslyFocusedRect);
        return true;
    }

    /**
     * Give this view focus. This will cause
     * {@link #onFocusChanged(boolean, int, android.graphics.Rect)} to be called.
     *
     * Note: this does not check whether this {@link View} should get focus, it just
     * gives it focus no matter what.  It should only be called internally by framework
     * code that knows what it is doing, namely {@link #requestFocus(int, Rect)}.
     *
     * @param direction values are {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
     *        {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT}. This is the direction which
     *        focus moved when requestFocus() is called. It may not always
     *        apply, in which case use the default View.FOCUS_DOWN.
     * @param previouslyFocusedRect The rectangle of the view that had focus
     *        prior in this View's coordinate system.
     */
    void handleFocusGainInternal(int direction, Rect previouslyFocusedRect) {
        if (DBG) {
            System.out.println(this + " requestFocus()");
        }

        if ((mPrivateFlags & PFLAG_FOCUSED) == 0) { // 只有當前View不是focused view時纔會發生一系列操做,不然do nothing
            mPrivateFlags |= PFLAG_FOCUSED; // 若是沒focus的話,先設置此view的focused標誌,isFocused,hasFocus等方法會檢測此標誌

            View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null; // 找到以前的focus

            if (mParent != null) {
                mParent.requestChildFocus(this, this); // 若是有mParent,則將此新focus請求向上傳遞
            }

            if (mAttachInfo != null) { // callback接口,將focus change事件notify出去
                mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
            }

            onFocusChanged(true, direction, previouslyFocusedRect);
            refreshDrawableState();
        }
    }

接着咱們看下關於PFLAG_FOCUSED標誌位相關的幾個方法,以下:

/**
     * Returns true if this view has focus
     *
     * @return True if this view has focus, false otherwise.
     */
    @ViewDebug.ExportedProperty(category = "focus")
    public boolean isFocused() {
        return (mPrivateFlags & PFLAG_FOCUSED) != 0;
    }

    /**
     * Find the view in the hierarchy rooted at this view that currently has
     * focus.
     *
     * @return The view that currently has focus, or null if no focused view can
     *         be found.
     */
    public View findFocus() {
        return (mPrivateFlags & PFLAG_FOCUSED) != 0 ? this : null;
    }

    /**
     * Returns true if this view has focus iteself, or is the ancestor of the
     * view that has focus.
     *
     * @return True if this view has or contains focus, false otherwise.
     */
    @ViewDebug.ExportedProperty(category = "focus")
    public boolean hasFocus() { // 對View來講,hasFocus和isFocus是相同的,ViewGroup類重載了此方法
        return (mPrivateFlags & PFLAG_FOCUSED) != 0;
    }

  最後,咱們看看ViewParent接口(以及其實現ViewGroup)的requestChildFocus()實現,代碼以下:

    /**
     * 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); // parent中的某個child請求得到focus,child要麼是focused,
                                                             // 要麼是focused的parent
    // 咱們能夠看到其實現類有ViewGroup、ScrollView等,這裏咱們看下ViewGroup類的,其餘的有興趣的同窗能夠自行研究

    @Override
    public void requestChildFocus(View child, View focused) {
        if (DBG) {
            System.out.println(this + " requestChildFocus()");
        }
        if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
            return; // 若是此ViewGroup被設置爲阻止任何children得到focus,則直接返回
        }

        // Unfocus us, if necessary
        super.unFocus(); // 先unFocus 此ViewGroup

        // We had a previous notion of who had focus. Clear it.
        if (mFocused != child) { // 若是mFocused不一樣於傳遞進來的child,則更新mFocused
            if (mFocused != null) {
                mFocused.unFocus(); // 讓舊的放棄focus
            }

            mFocused = child; // 更新mFocused
        }
        if (mParent != null) { // 接着沿着focus path往上傳遞(遞歸調用)
            mParent.requestChildFocus(this, focused); // 注意這裏的第2個參數,一直是傳遞進來的focused不變
        }
    }

咱們注意到只有ViewGroup纔有mFocused字段,表示focus path上的一個節點。咱們看看與之相關的代碼:

    // The view contained within this ViewGroup that has or contains focus.
    private View mFocused; // 此ViewGroup中的child view,它要麼是focused view自己要麼包含focused view

    @Override
    void handleFocusGainInternal(int direction, Rect previouslyFocusedRect) { // 此方法重載了View中的
        if (mFocused != null) { // 添加了對mFocused的處理
            mFocused.unFocus(); // 讓mFocused unFocus在這種狀況下
            mFocused = null;
        }
        super.handleFocusGainInternal(direction, previouslyFocusedRect);
    }

    /**
     * {@inheritDoc}
     */
    public void clearChildFocus(View child) {
        if (DBG) {
            System.out.println(this + " clearChildFocus()");
        }

        mFocused = null; // 清空
        if (mParent != null) { // 將事件告訴parent
            mParent.clearChildFocus(this);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void clearFocus() {
        if (DBG) {
            System.out.println(this + " clearFocus()");
        }
        if (mFocused == null) { // 若是沒有mFocused,則ViewGroup自身clearFocus
            super.clearFocus();
        } else { // 不然,讓mFocused clearFocus,而且重置爲null
            View focused = mFocused;
            mFocused = null;
            focused.clearFocus();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void unFocus() { // 大致同clearFocus,只是調的是unFocus方法
        if (DBG) {
            System.out.println(this + " unFocus()");
        }
        if (mFocused == null) {
            super.unFocus();
        } else {
            mFocused.unFocus();
            mFocused = null;
        }
    }

    /**
     * Returns the focused child of this view, if any. The child may have focus
     * or contain focus.
     *
     * @return the focused child or null.
     */
    public View getFocusedChild() { // 返回這個字段,供客戶端代碼使用
        return mFocused;
    }

    /**
     * Returns true if this view has or contains focus
     *
     * @return true if this view has or contains focus
     */
    @Override
    public boolean hasFocus() { // ViewGroup本身是focused或者其子、孫後代包含focused view
        return (mPrivateFlags & PFLAG_FOCUSED) != 0 || mFocused != null;
    }

    /*
     * (non-Javadoc)
     *
     * @see android.view.View#findFocus()
     */
    @Override
    public View findFocus() {
        if (DBG) {
            System.out.println("Find focus in " + this + ": flags="
                    + isFocused() + ", child=" + mFocused);
        }

        if (isFocused()) { // 本身是focused,直接返回this
            return this;
        }

        if (mFocused != null) { // 不然,mFocused不爲空,則沿着這條線往下繼續找
            return mFocused.findFocus();
        }
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean hasFocusable() {
        if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) {
            return false;
        }

        if (isFocusable()) {
            return true;
        }

        final int descendantFocusability = getDescendantFocusability();
        if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
            final int count = mChildrenCount;
            final View[] children = mChildren;

            for (int i = 0; i < count; i++) {
                final View child = children[i];
                if (child.hasFocusable()) {
                    return true;
                }
            }
        }

        return false;
    }

接着咱們看下View本身的unFocus、clearFocus實現,代碼以下:

    /**
     * Called internally by the view system when a new view is getting focus.
     * This is what clears the old focus.
     * <p>
     * <b>NOTE:</b> The parent view's focused child must be updated manually
     * after calling this method. Otherwise, the view hierarchy may be left in
     * an inconstent state.
     */
    void unFocus() {
        if (DBG) {
            System.out.println(this + " unFocus()");
        }

        clearFocusInternal(false, false);
    }

    /**
     * Called when this view wants to give up focus. If focus is cleared
     * {@link #onFocusChanged(boolean, int, android.graphics.Rect)} is called.
     * <p>
     * <strong>Note:</strong> When a View clears focus the framework is trying
     * to give focus to the first focusable View from the top. Hence, if this
     * View is the first from the top that can take focus, then all callbacks
     * related to clearing focus will be invoked after wich the framework will
     * give focus to this view.
     * </p>
     */
    public void clearFocus() {
        if (DBG) {
            System.out.println(this + " clearFocus()");
        }

        clearFocusInternal(true, true);
    }

    /**
     * Clears focus from the view, optionally propagating the change up through
     * the parent hierarchy and requesting that the root view place new focus.
     *
     * @param propagate whether to propagate the change up through the parent
     *            hierarchy
     * @param refocus when propagate is true, specifies whether to request the
     *            root view place new focus
     */
    void clearFocusInternal(boolean propagate, boolean refocus) {
        if ((mPrivateFlags & PFLAG_FOCUSED) != 0) { // 若是當前是focused,先清掉PFLAG_FOCUSED位
            mPrivateFlags &= ~PFLAG_FOCUSED;

            if (propagate && mParent != null) {
                mParent.clearChildFocus(this); // 若是向上傳播的話,調用parent.clearChildFocus方法
            }

            onFocusChanged(false, 0, null); // 調用callback方法

            refreshDrawableState(); // 刷新drawable狀態

            if (propagate && (!refocus || !rootViewRequestFocus())) {
                notifyGlobalFocusCleared(this);
            }
        }
    }

  經過上一篇的介紹,咱們知道KeyEvent的派發就是在view層次結構的focus path上自上而下發生的,具體參見View.dispatchKeyEvent

的方法doc。剛開始我一直不明白這裏的focus path是怎麼造成的,怎麼按着這個鏈傳遞的。這裏爲了幫助你們理解,我舉一個典型的例子,

經過例子能夠很清楚的看到傳遞過程。比方說咱們的view層次結構是這樣的,C是個Button,B是C的parent,LinearLayout,A是B的parent,

FrameLayout。這裏咱們先假設C、B、A都是有資格且其parent都不阻止它得到焦點,當咱們在代碼裏調用C.requestFocus()時發生的調用

序列以下:

1. --> B.requestChildFocus(C, C); 當此方法發生後產生的結果是:B.mFocused = C;接着產生2調用;

2. --> A.requestChildFocus(B, C); 一樣的,當此方法發生後,A.mFocused = B; 接着往上傳遞直到parent爲空時中止。

當C.requestFocus()調用結束時,若是沒有各類失敗的case發生,那麼C就是當前view層次結構中的focus了,也就是C.isFocused()方法

此時會返回true。看到了嗎?經過這個遞歸調用,focus path的鏈就造成了,從最頂層的A能經過其mFocused字段找到B,從找到的B能經過

其mFocused字段找到C,以此類推。爲了加深這個印象,咱們最後再看眼ViewGroup.dispatchKeyEvent()方法:

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        /// 2.2.1.1...
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onKeyEvent(event, 1);
        }

        if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
                == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) { // ViewGroup是focused,則優先交給它本身處理
            /// 2.2.1.2. 
            if (super.dispatchKeyEvent(event)) {
                return true;
            }
        } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
                == PFLAG_HAS_BOUNDS) { // 不然就沿着mFocused造成的focus path向下傳遞
            /// 2.2.1.3. 
            if (mFocused.dispatchKeyEvent(event)) {
                return true;
            }
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
        }
        /// 2.2.1.4. 
        return false;
    }

  如今再回過頭來看這裏的邏輯,是否是感受特別簡單呢?那是由於你已經徹底弄清楚了mFocused的由來以及各類變化過程。至此view層次

結構中關於focus的變化過程已經所有分析完畢了,enjoy。

相關文章
相關標籤/搜索