Android focus search logic

The introduction

In the last article on Android focus distribution, we briefly looked at the logic of focus distribution. This time, we will explore the logic of focus search.

Search starting point: focusSearch

In the last article on Android Focus distribution logic, we said that focus searches are done by calling focusSearch(…). Method, so what does this method do?

ViewRootImpl.java

    public View focusSearch(View focused, int direction) {
        checkThread();
        if(! (mViewinstanceof ViewGroup)) {
            return null;
        }
        return FocusFinder.getInstance().findNextFocus((ViewGroup) mView, focused, direction);
    }
Copy the code

Let’s start by looking at the ViewRootImpl class’s focusSearch(…) For the ViewRootImpl class, mView is the RootView we added to the Window, so normally mViews are of ViewGroup type, so focusSearch(…) The method ends up outsourcing the lookup logic to focusFinder.getInstance ().findNextFocus(…). .

View.java

    /** * Find the nearest view in the specified direction that can take focus. * This does not actually give focus to that view. */
    public View focusSearch(@FocusRealDirection int direction) {
        if(mParent ! =null) {
            return mParent.focusSearch(this, direction);
        } else {
            return null; }}Copy the code

Now let’s look at the View’s focusSearch(…) Method: View focusSearch(…) The method logic is simple, calling mparent.focusSearch (…) directly. Method, passed to the upper level.

ViewGroup.java

    /** * Find the nearest view in the specified direction that wants to take * focus. */
    public View focusSearch(View focused, int direction) {
        if (isRootNamespace()) {
            // root namespace means we should consider ourselves the top of the
            // tree for focus searching; otherwise we could be focus searching
            // into other tabs. see LocalActivityManager and TabHost for more info
            return FocusFinder.getInstance().findNextFocus(this, focused, direction);
        } else if(mParent ! =null) {
            return mParent.focusSearch(focused, direction);
        }
        return null;
    }
Copy the code

Obviously, the final processing is still the ViewGroup focusSearch(…). Methods:

If isRootNamespace() is true, the lookup logic is also outsourced to FocusFinder.getInstance().findNextFocus(…). . If isRootNamespace() is false, mparent.focusSearch (…) is still called. Pass up one level. So what does _isRootNamespace()_ mean? In fact, isRootNamespace() indicates whether the current ViewGroup is the root of the ViewTree, as its name suggests:

View.java

    public void setIsRootNamespace(boolean isRoot) {
        if (isRoot) {
            mPrivateFlags |= PFLAG_IS_ROOT_NAMESPACE;
        } else{ mPrivateFlags &= ~PFLAG_IS_ROOT_NAMESPACE; }}public boolean isRootNamespace(a) {
        return(mPrivateFlags&PFLAG_IS_ROOT_NAMESPACE) ! =0;
    }
Copy the code

PhoneWindow.java

    private void installDecor(a) {
        if (mDecor == null) {
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if(! mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures ! =0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); }}... }Copy the code

As you can see from the PhoneWindow source code, a PhoneWindow$DecorView is the View with isRootNamespace() set to true on the ViewTree, which is the root node of the ViewTree.

In conclusion, focusSearch (…). Methods all end up outsourcing the lookup logic to FocusFinder.

Overall screening process

It’s time to get down to business and look at how FocusFinder collects and filters “candidate views.”

findNextFocus

FocusFinder.java

private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
        View next = null;
        if(focused ! =null) {
            // Find if the focus distribution logic is customized
            next = findNextUserSpecifiedFocus(root, focused, direction);
        }
        if(next ! =null) {
            return next;
        }
        // Collect all candidate views (focusable)
        ArrayList<View> focusables = mTempList;
        try {
            focusables.clear();
            root.addFocusables(focusables, direction);
            if(! focusables.isEmpty()) {// Find the most suitable candidate Viewnext = findNextFocus(root, focused, focusedRect, direction, focusables); }}finally {
            focusables.clear();
        }
        return next;
    }
Copy the code

First of all, findUserSetNextFocus (…). It looks for custom focus distribution logic, using setNextFocusXxxxId(…) Method sets the focus distribution logic, and returns the View if it exists. That is, setNextFocusXxxxId(…) This approach comes from defining focus distribution logic.

Second, if there is no custom focus distribution logic, the system default focus distribution logic will be followed in two main steps:

  • First, collect all candidate views by recursively calling addFocusables(…). Method to collect all focusable views

  • The second is to iterate over all candidate Views by calling findNextFocus(…). Methods The most suitable candidate View was screened

Building a candidate list

addFocusables

View.java

    /** * Adds any focusable views that are descendants of this view (possibly * including this view if it is focusable itself) to views. */
    public void addFocusables(ArrayList<View> views, @FocusDirection int direction,
            @FocusableMode int focusableMode) {
        if (views == null) {
            return;
        }
        if(! isFocusable()) {return;
        }
        if((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE && isInTouchMode() && ! isFocusableInTouchMode()) {return;
        }
        views.add(this);
    }
Copy the code

AddFocusables (…) in the View class The method is relatively simple: determine whether the current View can be focused, and if so, add the current View to the Focusables.

ViewGroup.java

    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
        final int focusableCount = views.size();
        final int descendantFocusability = getDescendantFocusability();
        if(descendantFocusability ! = FOCUS_BLOCK_DESCENDANTS) {if (shouldBlockFocusForTouchscreen()) {
                focusableMode |= FOCUSABLES_TOUCH_MODE;
            }
            // Add a child View
            final int count = mChildrenCount;
            final View[] children = mChildren;
            for (int i = 0; i < count; i++) {
                final View child = children[i];
                if((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { child.addFocusables(views, direction, focusableMode); }}}// we add ourselves (if focusable) in all cases except for when we are
        // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is
        // to avoid the focus search finding layouts when a more precise search
        // among the focusable children would be more interesting.
        if((descendantFocusability ! = FOCUS_AFTER_DESCENDANTS// No focusable descendants|| (focusableCount == views.size())) && (isFocusableInTouchMode() || ! shouldBlockFocusForTouchscreen())) {// Add the current View
            super.addFocusables(views, direction, focusableMode); }}Copy the code

In the ViewGroup class, addFocusables(…) The method is more complicated. It does this differently with descendantFocusability strategy:

  • FOCUS_BLOCK_DESCENDANTS intercepts ChildView: call only the current View’s addFocusables(…) Method (that is, just add the current View).

  • FOCUS_BEFORE_DESCENDANTS (ParentView) : recursively call addFocusables(…) of all visible childViews. Method (that is, add visible ChildView) and then call the addFocusables(…) of the current View. Method (that is, add the current View)

  • FOCUS_AFTER_DESCENDANTS: recursively call the addFocusables(…) of all visible childViews. Method (that is, add visible ChildView), then __ If no ChildView __ is added, the current View’s addFocusables(…) is called. Method (that is, add the current View)

So __ If we need to customize focus distribution logic, we can also override addFocusables(…) Method to implement. __

Screening candidate Views

findNextFocus

FocusFinder.java

private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
            int direction, ArrayList<View> focusables) {
        // Initialize focusedRect
        if(focused ! =null) {
            if (focusedRect == null) {
                focusedRect = mFocusedRect;
            }
            // fill in interesting rect from focused
            focused.getFocusedRect(focusedRect);
            root.offsetDescendantRectToMyCoords(focused, focusedRect);
        } else {
            if (focusedRect == null) {
                focusedRect = mFocusedRect;
                // make up a rect at top left or bottom right of root
                switch (direction) {
                    case View.FOCUS_RIGHT:
                    case View.FOCUS_DOWN:
                        / / the top left corner
                        setFocusTopLeft(root, focusedRect);
                        break;
                    case View.FOCUS_FORWARD:
                        if (root.isLayoutRtl()) {
                            setFocusBottomRight(root, focusedRect);
                        } else {
                            setFocusTopLeft(root, focusedRect);
                        }
                        break;
                    case View.FOCUS_LEFT:
                    case View.FOCUS_UP:
                        / / the bottom right hand corner
                        setFocusBottomRight(root, focusedRect);
                        break;
                    case View.FOCUS_BACKWARD:
                        if (root.isLayoutRtl()) {
                            setFocusTopLeft(root, focusedRect);
                        } else {
                            setFocusBottomRight(root, focusedRect);
                        break; }}}}// Filter candidate views
        switch (direction) {
            case View.FOCUS_FORWARD:
            case View.FOCUS_BACKWARD:
                return findNextFocusInRelativeDirection(focusables, root, focused, focusedRect,
                        direction);
            case View.FOCUS_UP:
            case View.FOCUS_DOWN:
            case View.FOCUS_LEFT:
            case View.FOCUS_RIGHT:
                return findNextFocusInAbsoluteDirection(focusables, root, focused,
                        focusedRect, direction);
            default:
                throw new IllegalArgumentException("Unknown direction: "+ direction); }}Copy the code

findNextFocus(…) The method is also divided into two steps. First, calculate the focal area of the current focus. Why must there be this focal area? Because when judging which candidate View is more suitable, we need to find the View whose position is closest to this region (including __ azimuth and distance __) :

  • If focus is currently present, go directly to getFocusedRect(…) Get its focus area and convert it to the RootView coordinate system (typically PhoneWindow$DecorView).

  • If no focus exists, a default focus capture region is initialized according to the focus distribution event:

    • For _FOCUS_RIGHT_ or FOCUS_DOWN events, this is set to the top left corner of the RootView (including scrollX and scrollY).

    • For FOCUS_LEFT or FOCUS_UP events, it is set to the lower right corner of RootView (including scrollX and scrollY).

    • In addition, for the FOCUS_FORWARD or _FOCUS_BACKWARD_ event, this position is closely related to the layout direction (isLayoutRtl()), which is not detailed here.

Then, started to filter the most suitable candidate View, if it is FOCUS_FORWARD or FOCUS_BACKWARD event called findNextFocusInRelativeDirection (…). Methods Comparative screening; Other events are called findNextFocusInAbsoluteDirection (…). Conduct comparison screening. From the method name, we can also see that forward and backward are the most appropriate views in relative position, while up, down and left are the most appropriate views in absolute position.

Relative position screening

findNextFocusInRelativeDirection
    private View findNextFocusInRelativeDirection(ArrayList<View> focusables, ViewGroup root,
            View focused, Rect focusedRect, int direction) {
        try {
            // Note: This sort is stable.
            // Sort by DrawingRect
            mSequentialFocusComparator.setRoot(root);
            mSequentialFocusComparator.setIsLayoutRtl(root.isLayoutRtl());
            Collections.sort(focusables, mSequentialFocusComparator);
        } finally {
            mSequentialFocusComparator.recycle();
        }
        // Find the relative position (front and back) View
        final int count = focusables.size();
        switch (direction) {
            case View.FOCUS_FORWARD:
                return getNextFocusable(focused, focusables, count);
            case View.FOCUS_BACKWARD:
                return getPreviousFocusable(focused, focusables, count);
        }
        return focusables.get(count - 1);
    }
Copy the code

First of all, mSequentialFocusComparator according to each candidate View getDrawingRect (…). The order is related to the layout direction (isLayoutRtl()) :

  • Sort by drawingRect. top: Sort by top value in ascending order.

  • Order by drawingRect. left. This order depends on the layout direction: if the layout direction is left to right, the left value is in ascending order. If the layout is from right to left, the left value is sorted in descending order.

  • Sort by drawingRect. bottom: Sort by the bottom value in ascending order.

  • Order by drawingRect. right: if the layout is from left to right, order by right. If the layout direction is from right to left, the right value is sorted in descending order.

Next, call getNextFocusable(…) (or _getPreviousFocusable (…). The _) method finds the previous (or next) candidate View of the current focusables, and returns the first (or last) candidate View if the current focusables View is not in the focusables.

Absolute position screening

findNextFocusInAbsoluteDirection
    View findNextFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused,
            Rect focusedRect, int direction) {
        // initialize the best candidate to something impossible
        // (so the first plausible view will become the best choice)
        // Initialize mBestCandidateRect
        mBestCandidateRect.set(focusedRect);
        switch(direction) {
            case View.FOCUS_LEFT:
                mBestCandidateRect.offset(focusedRect.width() + 1.0);
                break;
            case View.FOCUS_RIGHT:
                mBestCandidateRect.offset(-(focusedRect.width() + 1), 0);
                break;
            case View.FOCUS_UP:
                mBestCandidateRect.offset(0, focusedRect.height() + 1);
                break;
            case View.FOCUS_DOWN:
                mBestCandidateRect.offset(0, -(focusedRect.height() + 1));
        }
        // Compare and filter the optimal solution
        View closest = null;
        int numFocusables = focusables.size();
        for (int i = 0; i < numFocusables; i++) {
            View focusable = focusables.get(i);
            // only interested in other non-root views
            if (focusable == focused || focusable == root) continue;
            // get focus bounds of other view in same coordinate system
            focusable.getFocusedRect(mOtherRect);
            root.offsetDescendantRectToMyCoords(focusable, mOtherRect);
            if(isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) { mBestCandidateRect.set(mOtherRect); closest = focusable; }}return closest;
    }
Copy the code

findNextFocusInAbsoluteDirection(…) The logic of the method is a little more complicated, because it involves sifting out views that are most suitable for ____ position __ and __ distance __.

First of all, a limit region is calculated as the initial mBestCandidateRect __. Of course, since this region does not meet the criteria at all, any candidate View that meets the criteria will replace it (this is just like when we filter a minimum value, we initialize that value to a maximum value and then compare it to other values).

So how do we calculate that? Find the first region in the opposite direction that doesn’t satisfy this condition. For example, when the focus event is FOCUS_LEFT, shift the current focus area to the right with focusedRect.width() + 1, which is the limit area.

Then, loop through all the views in the Focusables (excluding the current FocusedView and RootView) to find the candidate View with the most appropriate orientation and distance. Notice that we are comparing getFocusedRect(…) __. So what are the conditions to determine which View is the most appropriate?

isBetterCandidate
If rect1 is more appropriate than rect2, return true for rect1 and false for rect2
boolean isBetterCandidate(int direction, Rect source, Rect rect1, Rect rect2) {
        // First judge by the [direction] relative to the current focal area
        // to be a better candidate, need to at least be a candidate in the first
        // place :)
        if(! isCandidate(source, rect1, direction)) {return false;
        }
        // we know that rect1 is a candidate.. if rect2 is not a candidate,
        // rect1 is better
        if(! isCandidate(source, rect2, direction)) {return true;
        }
        // Then judge by the position relative to the current focal area
        // if rect1 is better by beam, it wins
        if (beamBeats(direction, source, rect1, rect2)) {
            return true;
        }
        // if rect2 is better, then rect1 cant' be :)
        if (beamBeats(direction, source, rect2, rect1)) {
            return false;
        }
        // Finally, judge by the distance relative to the current focal area
        // otherwise, do fudge-tastic comparison of the major and minor axis
        return (getWeightedDistanceFor(
                        majorAxisDistance(direction, source, rect1),
                        minorAxisDistance(direction, source, rect1))
                < getWeightedDistanceFor(
                        majorAxisDistance(direction, source, rect2),
                        minorAxisDistance(direction, source, rect2)));
    }
Copy the code

First of all, throughisCandidate(…)Methods To judge whether the candidate area meets the conditions in the direction relative to the current focal area, that is, whether the candidate area is closer to the target direction than the current focal area.

Then, if both candidate regions meet the criteria, beamBeats(…) Will be judged by more stringent position conditions, detailed logic move below #beamBeats# :

Finally, getWeightedDistanceFor(…) if you still can’t decide which candidate area is better. The method calculates the distance between the two candidate areas and the current focal area with a certain weight ratio, and selects the candidate area with a closer distance. Note that this distance does not simply calculate the distance between two center points, but the distance that includes a special weight ratio.

beamBeats
    // If rect1 is more appropriate than rect2, return true if rect1 is more appropriate; [Note: Returning false means unable to determine]
    boolean beamBeats(int direction, Rect source, Rect rect1, Rect rect2) {
        // Determine whether rect1 and source overlap in non-destination directions
        final boolean rect1InSrcBeam = beamsOverlap(direction, source, rect1);
        // Determine whether rect2 and source overlap in non-destination directions
        final boolean rect2InSrcBeam = beamsOverlap(direction, source, rect2);
        // If rect1 has no intersection with source in the non-target direction or recT2 has intersection with source in the non-target direction, it cannot be determined
        if(rect2InSrcBeam || ! rect1InSrcBeam) {return false;
        }
        // Rect1 has an intersection with source in non-destination directions and recT2 has no intersection with source in non-destination directions
        // If rect2 and source overlap in the destination direction, rect1 is more appropriate
        if(! isToDirectionOf(direction, source, rect2)) {return true;
        }
        // For horizontal movement, "left or right" takes precedence over "upper left (lower) or upper right (lower) Angle"
        if ((direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT)) {
            return true;
        }        
        // For vertical movement, the boundary distance is used to determine which candidate region is more suitable: "up or down" versus "left (right) upper corner or left (right) lower corner"
        return (majorAxisDistance(direction, source, rect1)
                < majorAxisDistanceToFarEdge(direction, source, rect2));
    }
Copy the code

To sum up the beamBeats(…) Judgment logic of:

  • If there is intersection between the two candidate areas and the current focal area in the target direction, it cannot be judged, and distance judgment is required in the last step.

  • If one candidate area (RECT1) intersects with the current focal area in the target direction and the other candidate area (RECT2) does not intersect with the current focal area in the target direction, then:

    • If __rect2__ intersects the current focal area in a non-target direction, __rect1__ is more appropriate. Why? __rect2__ is more suitable as a candidate View for focus movement in a non-target direction.

    • If __rect2__ does not overlap with the current focal area in the non-target direction (when __rect2__ is on the opposite corner) then:

      • If the movement is horizontal, __rect1__ is more appropriate

      • If the movement is vertical, determine whether __rect1__ is closer to the current focal area vertically than __rect2__ (the current focal area is closer to __rect1__’s near boundary than to __rect2__’s far boundary.

      • That is, __ is allowed to move across columns horizontally, but not across rows vertically

Here’s an example:

| * * * * * * * * * * * * * * | | *2.1 *    * 2.2* | | * * * * * * * * * * * * * * | | | | | | * * * * * * * | | -- -- -- -- -- -- -- -- -- - | - *1.2 *-----------*****************----------------------
          | *******           *               *
          |                   *               *
    *******      <===============  focused    *
    * 1.1 *       FOCUS_LEFT  *               *
    *******                   *               *
------------------------------*****************-----------------------
                              |               |
                              |               |
                              |               |
                              |               |
Copy the code

As shown in the figure:

  • Areas 1.1 and 1.2 can only be judged by the distance with weight ratio because they intersect with the current focal area in the horizontal direction

  • Area 2.2 is more suitable as a candidate area for FOCUS_UP because it intersects with the current FOCUS_UP area in the vertical direction but not in the horizontal direction.

  • Regions 1.1 and 2.1 are selected because horizontal movement of focus across columns is allowed and 2.2 is a candidate region for FOCUS_UP

Here’s another chestnut:

* * * * * * * *2.2* * * * * * * * | | | * * * * * * * * * * * * * * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- - *2.1 *     |
       * 1.2 *                |      *******     |  
       *******                |                  |
                              |     FOCUS_UP     |
                              |        ^         |
       *******                |        |         |
-------* 1.1*----------------*********|**********--------------------- ******* * | * * * * focused * * * * * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- * * * * * * * * * * * * * * * * * * * * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | | | | | | |Copy the code

As shown in the figure:

  • Areas 2.1 and 2.2 can only be judged by the distance with weight ratio because they intersect with the current focal area vertically

  • Area 1.1 is more suitable as the candidate area for FOCUS_LEFT because it has no intersection with the current FOCUS_LEFT area in the vertical direction, but has intersection in the horizontal direction

  • Zones 2.1 and 1.2. Since 2.1 and 1.2 are on the same line, zone 2.1 is more appropriate

  • Area 2.2 and 1.2. Since 2.2 and 1.2 are not in the same line, and cross-line movement of focus is not allowed in the vertical direction, area 1.2 is more suitable.

Attached: FocusFinder partial method resolution

isCandidate
    boolean isCandidate(Rect srcRect, Rect destRect, int direction) {
        switch (direction) {
            case View.FOCUS_LEFT:
                return(srcRect.right > destRect.right || srcRect.left >= destRect.right) && srcRect.left > destRect.left; . }Copy the code

isCandidate(…) The main method is __ to determine whether the candidate region (DEST) is closer to the target direction than the current focal region (SRC) __.

For example, FOCUS_LEFT focus event, by judging whether the left boundary of the candidate region is more left than the left boundary of the current focusing region, and the right boundary also needs to be more left than the right boundary of the current focusing region (if width of the focusing region is 0, the right boundary is allowed to overlap), as shown in the figure:

        |        |
        |  src   |
        |        |
      * |      * | 
      * |      * |
      *        *
      *  dest  *
      *        *
Copy the code
beamsOverlap
    boolean beamsOverlap(int direction, Rect rect1, Rect rect2) {
        switch (direction) {
            case View.FOCUS_LEFT:
            case View.FOCUS_RIGHT:
                return (rect2.bottom >= rect1.top) && (rect2.top <= rect1.bottom);
            case View.FOCUS_UP:
            case View.FOCUS_DOWN:
                return (rect2.right >= rect1.left) && (rect2.left <= rect1.right);
        }
        throw new IllegalArgumentException("direction must be one of "
                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
    }
Copy the code

beamsOverlap(…) The main method is __ to determine whether the candidate region (DEST) intersects with the current focal region (SRC) in the target direction __.

  • For FOCUS_LEFT or FOCUS_RIGHT, judge whether there is intersection between this area and the current focus area in the horizontal direction, as shown in the figure:

  • For FOCUS_UP or FOCUS_DOWN, determine whether this area intersects with the current focus area in the vertical direction.

-- -- -- -- -- -- -- -- -- -- - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -- -- -- -- -- -- -- -- -- -- dest -- -- -- -- -- -- -- -- -- -- dest SRC or dest SRC or * * * * * * * * * * SRC * * * * * * * * * * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * * * * * * * * * *Copy the code
isToDirectionOf
    /** * e.g for left, is 'to left of' */
    boolean isToDirectionOf(int direction, Rect src, Rect dest) {
        switch (direction) {
            case View.FOCUS_LEFT:
                return src.left >= dest.right;
            case View.FOCUS_RIGHT:
                return src.right <= dest.left;
            case View.FOCUS_UP:
                return src.top >= dest.bottom;
            case View.FOCUS_DOWN:
                return src.bottom <= dest.top;
        }
        throw new IllegalArgumentException("direction must be one of "
                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
    }
Copy the code

isToDirectionOf(…) The main method is __ to determine whether there is no intersection between candidate region (DEST) and current focal region (SRC) in the non-target direction (boundary overlap is allowed)__. For example, when FOCUS_LEFT, determine whether the region is completely to the left of the current focal region (toLeftOf), as shown in the figure:

      *      *                    |     |
      *      *                    |     |
      * dest *     to left of     | src |
      *      *                    |     |
      *      *                    |     |
Copy the code
majorAxisDistance
    / * * *@return The distance from the edge furthest in the given direction
     *   of source to the edge nearest in the given direction of dest.  If the
     *   dest is not in the direction from source, return 0.
     */
    static int majorAxisDistance(int direction, Rect source, Rect dest) {
        return Math.max(0, majorAxisDistanceRaw(direction, source, dest));
    }
    static int majorAxisDistanceRaw(int direction, Rect source, Rect dest) {
        switch (direction) {
            case View.FOCUS_LEFT:
                return source.left - dest.right;
            case View.FOCUS_RIGHT:
                return dest.left - source.right;
            case View.FOCUS_UP:
                return source.top - dest.bottom;
            case View.FOCUS_DOWN:
                return dest.top - source.bottom;
        }
        throw new IllegalArgumentException("direction must be one of "
                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
    }
Copy the code

majorAxisDistance(…) The method is __ to calculate the distance __ between the current focus region (SRC) and the closest boundary of the candidate region (DEST) in the target direction. For example, when _FOCUS_LEFT_, the distance from the left boundary of the current focus region to the right boundary of the candidate region is calculated (0 is returned if less than 0), as shown in the figure below:

      *      * <--majorAxisDistance--- |     |
      *      *                         |     |
      * dest *                         | src |
      *      *                         |     |
      *      *                         |     |
Copy the code
majorAxisDistanceToFarEdge
    / * * *@return The distance along the major axis w.r.t the direction from the
     *   edge of source to the far edge of dest. If the
     *   dest is not in the direction from source, return 1 (to break ties with
     *   {@link #majorAxisDistance}).
     */
    static int majorAxisDistanceToFarEdge(int direction, Rect source, Rect dest) {
        return Math.max(1, majorAxisDistanceToFarEdgeRaw(direction, source, dest));
    }
    static int majorAxisDistanceToFarEdgeRaw(int direction, Rect source, Rect dest) {
        switch (direction) {
            case View.FOCUS_LEFT:
                return source.left - dest.left;
            case View.FOCUS_RIGHT:
                return dest.right - source.right;
            case View.FOCUS_UP:
                return source.top - dest.top;
            case View.FOCUS_DOWN:
                return dest.bottom - source.bottom;
        }
        throw new IllegalArgumentException("direction must be one of "
                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
    }
Copy the code

majorAxisDistanceToFarEdge(…) The method mainly calculates the distance from the current focus region (SRC) to the far boundary of the candidate region (DEST) in the target direction. For example, when FOCUS_LEFT is used, the distance between the left boundary of the current focus region and the left boundary of the candidate region is calculated (1 is returned if less than 1), as shown below:

      * <-------majorAxisDistanceToFarEdge---|     |
      *      *                               |     |
      * dest *                               | src |
      *      *                               |     |
      *      *                               |     |
Copy the code
minorAxisDistance
    /**
     * Find the distance on the minor axis w.r.t the direction to the nearest
     * edge of the destination rectangle.
     * @param direction the direction (up, down, left, right)
     * @param source The source rect.
     * @param dest The destination rect.
     * @return The distance.
     */
    static int minorAxisDistance(int direction, Rect source, Rect dest) {
        switch (direction) {
            case View.FOCUS_LEFT:
            case View.FOCUS_RIGHT:
                // the distance between the center verticals
                return Math.abs(
                        ((source.top + source.height() / 2) -
                        ((dest.top + dest.height() / 2))));
            case View.FOCUS_UP:
            case View.FOCUS_DOWN:
                // the distance between the center horizontals
                return Math.abs(
                        ((source.left + source.width() / 2) -
                        ((dest.left + dest.width() / 2))));
        }
        throw new IllegalArgumentException("direction must be one of "
                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
    }
Copy the code

minorAxisDistance(…) The method is __ to calculate the distance from the center point of the current focal area (SRC) to the center point of the candidate area (DEST) in the non-target direction __ :

  • For FOCUS_LEFT or FOCUS_RIGHT, the vertical distance (which can be negative) from the center point of the current focal area to the center point of the candidate area is calculated, as shown in the figure below.

  • For FOCUS_UP or FOCUS_DOWN, the horizontal distance (which can be negative) from the current focal area to the center point of the candidate area is calculated.

                                           src
                                       ===========
                                        
                                        
        dest              ------------------x        
    ***********           |                    
                  minorAxisDistance    
                          |            ===========
         x-----------------                
         
         
    ***********
Copy the code
getWeightedDistanceFor
    /** * Fudge-factor opportunity: how to calculate distance given major and minor * axis distances. Warning: this fudge factor is finely tuned, be sure to * run all focus tests if you dare tweak it. */
    int getWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance) {
        return 13 * majorAxisDistance * majorAxisDistance
                + minorAxisDistance * minorAxisDistance;
    }
Copy the code

getWeightedDistanceFor(…) Method __ calculates the distance between the target and non-target directions __ with a special weighting ratio of 13:1.