View. RequestFocus source code analysis

We need a control View to focus on, and we usually actively call the control’s requestFocus method. (This article is analyzed based on API 27 source code)

<View.java>
public final boolean requestFocus(a) {
    // FOCUS_DOWN is used by default
    return requestFocus(View.FOCUS_DOWN);
}
Copy the code

RequestFocus () {focus (” View “, “View”, “View”, “View”);

<View.java>
private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
    // need to be focusable
    // If the view sets focusable = false, return directly
    if((mViewFlags & FOCUSABLE) ! = FOCUSABLE || (mViewFlags & VISIBILITY_MASK) ! = VISIBLE) {return false;
    }

    // need to be focusable in touch mode if in touch mode
    // In touch mode
    if(isInTouchMode() && (FOCUSABLE_IN_TOUCH_MODE ! = (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {return false;// If the view sets focusableInTouchMode = false, return
    }

    // need to not have any parents blocking us
    if (hasAncestorThatBlocksDescendantFocus()) {
        return false;Return to FOCUS_BLOCK_DESCENDANTS if FOCUS_BLOCK_DESCENDANTS is set in parent
    }

    handleFocusGainInternal(direction, previouslyFocusedRect);
    return true;// Focus on success
}
Copy the code
  • The first step is to determine the current View’s focusable state. If false, the View does not get focus, so there is no need to proceed further.
  • In touch mode, if focusableInTouchMode is false, the View can’t get focus by touch and there is no need to move down.
  • Let’s move on to the next judgmenthasAncestorThatBlocksDescendantFocus()Methods:
<View.java>
private boolean hasAncestorThatBlocksDescendantFocus(a) {
    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;
}
Copy the code

FOCUS_BLOCK_DESCENDANTS = FOCUS_BLOCK_DESCENDANTS = FOCUS_BLOCK_DESCENDANTS = FOCUS_BLOCK_DESCENDANTS = FOCUS_BLOCK_DESCENDANTS = FOCUS_BLOCK_DESCENDANTS = FOCUS_BLOCK_DESCENDANTS = FOCUS_BLOCK_DESCENDANTS = FOCUS_BLOCK_DESCENDANTS = FOCUS_BLOCK_DESCENDANTS = FOCUS_BLOCK_DESCENDANTS = FOCUS_BLOCK_DESCENDANTS = FOCUS_BLOCK_DESCENDANTS = FOCUS_BLOCK_DESCENDANTS = FOCUS_BLOCK_DESCENDANTS = FOCUS_BLOCK_DESCENDANTS = FOCUS_BLOCK_DESCENDANTS When you return to requestFocusNoSearch, you simply return instead of going down.

After a series of criteria, if focusable and the parent View does not intercept the focus, we end up in the core method 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;// update the isFocused files
        // Focus in the current stateView oldFocus = (mAttachInfo ! =null)? getRootView().findFocus() :null;

        if(mParent ! =null) {
            mParent.requestChildFocus(this.this);// Clear the current focus, and update the mFocus variable to the current desired focus view
            updateFocusedInCluster(oldFocus, direction);// This method is related to the keyboard and will not be focused on here
        }

        if(mAttachInfo ! =null) {
            // Notify the ViewTreeObserver of focus changes
            mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
        }

        onFocusChanged(true, direction, previouslyFocusedRect);// Notify focus change callback
        refreshDrawableState();// The current view is in focus and the drawable state is refreshed}}Copy the code

In this method, the isFocused state of the current View will be updated by mPrivateFlags. Then, the current focus will be found by rootView and assigned to oldFocus. Then call the parent’s requestChildFocus method to tell the parent that it is in focus.

<ViewGroup.java>
@Override
public void requestChildFocus(View child, View focused) {
    if (DBG) {
        System.out.println(this + " requestChildFocus()");
    }
    // Check again whether FOCUS_BLOCK_DESCENDANTS is set
    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); }}Copy the code

‘focused’ is not used in this method (unFocus is not used in this method). ‘focused’ is the direct focus of the first call, and ‘child’ is the direct focus of the first call. Child = = focused, but through the mParent requestChildFocus (this, focused); After that, the child argument becomes the parent View of the direct focus, called layer by layer and so on. The next two arguments should be distinguished. Here is the official comment on this parameter:

<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);
Copy the code

In each ViewGroup, there is a mFocus variable that holds the focus of the current ViewGroup, not the direct focus. (Official note on the meaning of this variable: The view contained within this ViewGroup that has or contains focus.) then return to The requestChildFocus(View child, View focused (‘ focused ‘) methods

  • Check again if FOCUS_BLOCK_DESCENDANTS is set, and stop going down if intercepted.
  • In most cases, the current mFocused view is not the same as the one we want to focus on, so we branch into the callmFocused.unFocus(focused)
<View.java>
void unFocus(View focused) {
    if (DBG) {
        System.out.println(this + " unFocus()");
    }
    clearFocusInternal(focused, false.false);
}

// Finally call this method
void clearFocusInternal(View focused, boolean propagate, boolean refocus) {
    // focused is not used
    if((mPrivateFlags & PFLAG_FOCUSED) ! =0) {
        mPrivateFlags &= ~PFLAG_FOCUSED;

        if(propagate && mParent ! =null) {
            mParent.clearChildFocus(this);// Tell the parent to clear its mFocus value because the focus is no longer under the View tree node
        }

        onFocusChanged(false.0.null);// Callback notification of a focus state change
        refreshDrawableState();// Refresh the drawable state after losing focus

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

The clearFocusInternal method is called by mFocus, which clears the current focus state of the View. It does the following:

  • Notify parent callclearChildFocusSet the mFocus variable to null because the focus is no longer under the View tree node.
  • Callbacks themselves to notifications of state changes in focus as we normally set them upsetOnFocusChangeListenerThis is where the listener triggers the callback.
  • Since you cleared your focus state in Step 1, you naturally need to refresh the view state after you lose focus, which is called hererefreshDrawableStateTo refresh the drawableState, which is the selector state property that we normally set in XML.

Note that the ‘FOCUSED’ parameter is not used at all, but the ‘focused’ is the most direct focus.

After clearing the current focus, go back to the parent’s requestChildFocus and assign the desired child to mFocused. RequestFocus successfully shifts focus from oldFocus to newFocus by calling View.requestFocus.

<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); }...Copy the code

The parent then tells the parent View, layer by layer, that the current focus is on me. That is, if a child View is focused, it assigns itself to parent’s mFocus variable, so that the next time you look for a focus, you can find the immediate focus at the lowest level by using the mFocus variable on the top level of parent. To make this clear, expand the findFocus method:

<ViewGroup.java>
@Override
public View findFocus(a) {
    if (DBG) {
        System.out.println("Find focus in " + this + ": flags="
                + isFocused() + ", child=" + mFocused);
    }
    // If isFocused is present, I am focused and return to myself
    if (isFocused()) {
        return this;
    }
    // mFocus is not null, indicating that the focus is under the mFocus View tree
    if(mFocused ! =null) {
        return mFocused.findFocus();
    }
    return null;
}

<View.java>
public View findFocus(a) {
    // When traversing through the direct child View, it is judged by the flag bit
    return(mPrivateFlags & PFLAG_FOCUSED) ! =0 ? this : null;
}
Copy the code

For example: A contains B, B contains C, A and B are both viewgroups, C is A direct View, A’s mFocus is B, and B’s mFocus is C. = null (‘ A ‘, ‘B’, ‘hasFocus’,’ ISfocus’); You’ll end up in requestChildFocus of ViewRootImpl for UI redrawing.

<ViewGroup.java>
public boolean hasFocus(a) {
    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 redrawn
}
Copy the code

Return to the above, the mPrivateFlags handleFocusGainInternal | = PFLAG_FOCUSED; There is a change to the tag bit by which isFocused is actually judged.

<View.java>
@ViewDebug.ExportedProperty(category = "focus")
public boolean isFocused(a) {
    return(mPrivateFlags & PFLAG_FOCUSED) ! =0;
}
Copy the code

At this point, the view.requestfocus invocation process is finished, and the focus has moved from the oldFocus to the new newFocus. Let’s look at the viewgroup.requestFocus method:

<ViewGroup.java>
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
    if (DBG) {
        System.out.println(this + " ViewGroup.requestFocus direction="
                + direction);
    }
    int descendantFocusability = getDescendantFocusability();
    // Focus interception mode set by ViewGroup
    switch (descendantFocusability) {
        case FOCUS_BLOCK_DESCENDANTS:// Intercepts the focus and calls the super logic directly in requestFocus itself
            return super.requestFocus(direction, previouslyFocusedRect);
        case FOCUS_BEFORE_DESCENDANTS: {// first call the super logic in your own requestFocus, if your requestFocus failed again through the sub-view for requestFocus
            final boolean took = super.requestFocus(direction, previouslyFocusedRect);
            return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect);
        }
        case FOCUS_AFTER_DESCENDANTS: {Instead of FOCUS_BEFORE_DESCENDANTS, first traverse the sub-views for requestFocus, and then call the super logic for requestFocus in itself if the sub-views all failed to 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); }}Copy the code

DescendantFocusability: descendantFocusability is descendantFocusability:

  • FOCUS_BLOCK_DESCENDANTS: intercepts the focus itself and makes a requestFocus call on itself to request the focus
  • FOCUS_BEFORE_DESCENDANTS: FOCUS_BEFORE_DESCENDANTS: when its own priority subview gets the focus, make a requestFocus call on itself first to get the focus, and then traverse the subview to get the focus if it fails
  • FOCUS_AFTER_DESCENDANTS: first traverse the sub-views to get them focused, and then make a requestFocus call to oneself if none of the sub-views are focused

Below we see onRequestFocusInDescendants done:

<ViewGroup.java>
protected boolean onRequestFocusInDescendants(int direction,
        Rect previouslyFocusedRect) {
    int index;
    int increment;
    int end;
    int count = mChildrenCount;
    if((direction & FOCUS_FORWARD) ! =0) {// go from front to back
        index = 0;
        increment = 1;
        end = count;
    } else {// traverse from back to front
        index = count - 1;
        increment = -1;
        end = -1;
    }
    final View[] children = mChildren;// The mChildren array holds all childViews
    for (inti = index; i ! = end; i += increment) { View child = children[i];if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {// Walk through the child View, and the View is visible
            if (child.requestFocus(direction, previouslyFocusedRect)) {// This child View requests focus
                return true;// Request focus successful, return directly}}}return false;
}
Copy the code

OnRequestFocusInDescendants main function is to traverse all child under this ViewGroup View, then the son of the visible View call requestFocus, focus if the request is successful, the direct returns true, at this point, ViewGroup. RequestFocus is also handled.