第二章:焦点查找算法源码剖析

62 阅读4分钟

2.1 FocusFinder 核心架构

类结构与单例模式

// FocusFinder.java
public class FocusFinder {
    // 线程安全的单例实现
    private static final ThreadLocal<FocusFinder> tlFocusFinder = 
        new ThreadLocal<FocusFinder>() {
            @Override
            protected FocusFinder initialValue() {
                return new FocusFinder();
            }
        };
    
    public static FocusFinder getInstance() {
        return tlFocusFinder.get();
    }
    
    // 算法核心组件
    private final FocusSorter mFocusSorter = new FocusSorter();
    private final ArrayList<View> mTempList = new ArrayList<>();
    private final Rect mFocusedRect = new Rect();
    private final Rect mOtherRect = new Rect();
    // ...
}

主要公有方法

public View findNextFocus(ViewGroup root, View focused, int direction);
public View findNextFocusFromRect(ViewGroup root, Rect focusedRect, int direction);
public View findNearestTouchable(ViewGroup root, int x, int y, int direction, int[] deltas);

2.2 焦点查找完整流程

算法流程图

deepseek_mermaid_20250712_3b43e3.png

源码实现(findNextFocus)

// FocusFinder.java
public View findNextFocus(ViewGroup root, View focused, int direction) {
    return findNextFocus(root, focused, null, direction);
}

private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
    // 1. 检查用户指定焦点
    View userSetNext = findNextUserSpecifiedFocus(root, focused, direction);
    if (userSetNext != null) return userSetNext;
    
    // 2. 收集候选视图
    ArrayList<View> candidates = mTempList;
    candidates.clear();
    root.addFocusables(candidates, direction);
    
    // 3. 没有候选视图时返回null
    if (candidates.isEmpty()) return null;
    
    // 4. 获取当前焦点位置
    if (focused != null) {
        if (focusedRect == null) {
            focusedRect = mFocusedRect;
        }
        focused.getFocusedRect(focusedRect);
        root.offsetDescendantRectToMyCoords(focused, focusedRect);
    } else {
        // 无当前焦点时使用根视图位置
        if (focusedRect == null) {
            focusedRect = mFocusedRect;
            focusedRect.set(root.getLeft(), root.getTop(), root.getRight(), root.getBottom());
        }
    }
    
    // 5. 查找最佳候选
    return findNextFocus(root, focused, focusedRect, direction, candidates);
}

2.3 用户指定焦点查找

XML属性解析流程

// View.java
public View findUserSetNextFocus(View root, int direction) {
    int nextId = -1;
    switch (direction) {
        case FOCUS_LEFT: nextId = mNextFocusLeftId; break;
        case FOCUS_RIGHT: nextId = mNextFocusRightId; break;
        case FOCUS_UP: nextId = mNextFocusUpId; break;
        case FOCUS_DOWN: nextId = mNextFocusDownId; break;
        case FOCUS_FORWARD: nextId = mNextFocusForwardId; break;
        case FOCUS_BACKWARD: nextId = mNextFocusBackwardId; break;
    }
    if (nextId == View.NO_ID) return null;
    return root.findViewById(nextId);
}

运行时动态设置

// 设置下一个焦点视图
button.setNextFocusRightId(R.id.next_button);

// 直接设置视图引用(API Level 30+)
button.setNextFocusRightView(nextButton);

2.4 几何位置搜索算法

候选视图筛选

// FocusFinder.java
private boolean isCandidate(Rect focusedRect, Rect candidateRect, int direction) {
    switch (direction) {
        case View.FOCUS_LEFT:
            return focusedRect.right > candidateRect.right && 
                   focusedRect.top <= candidateRect.bottom && 
                   focusedRect.bottom >= candidateRect.top;
        case View.FOCUS_RIGHT:
            return focusedRect.left < candidateRect.left && 
                   focusedRect.top <= candidateRect.bottom && 
                   focusedRect.bottom >= candidateRect.top;
        case View.FOCUS_UP:
            return focusedRect.bottom > candidateRect.bottom && 
                   focusedRect.left <= candidateRect.right && 
                   focusedRect.right >= candidateRect.left;
        case View.FOCUS_DOWN:
            return focusedRect.top < candidateRect.top && 
                   focusedRect.left <= candidateRect.right && 
                   focusedRect.right >= candidateRect.left;
    }
    return false;
}

最佳候选比较算法

private boolean isBetterCandidate(int direction, Rect source, Rect candidate, Rect currentBest) {
    // 规则1:检查是否在更接近的"主方向"上
    if (beamBeats(direction, source, candidate, currentBest)) {
        return true;
    }
    
    // 规则2:当前候选在更接近的主方向上
    if (beamBeats(direction, source, currentBest, candidate)) {
        return false;
    }
    
    // 规则3:检查距离比例
    if (majorAxisDistance(direction, source, candidate) < 
        majorAxisDistance(direction, source, currentBest)) {
        return true;
    }
    
    // 规则4:相同距离时检查次要轴距离
    if (majorAxisDistance(direction, source, candidate) == 
        majorAxisDistance(direction, source, currentBest)) {
        return minorAxisDistance(direction, source, candidate) < 
               minorAxisDistance(direction, source, currentBest);
    }
    
    return false;
}

距离计算实现

// 主轴距离计算(方向上的距离)
int majorAxisDistance(int direction, Rect source, Rect dest) {
    switch (direction) {
        case FOCUS_LEFT: return source.left - dest.right;  // 左方向:源左 - 目标右
        case FOCUS_RIGHT: return dest.left - source.right; // 右方向:目标左 - 源右
        case FOCUS_UP: return source.top - dest.bottom;    // 上方向:源上 - 目标下
        case FOCUS_DOWN: return dest.top - source.bottom;  // 下方向:目标上 - 源下
    }
    throw new IllegalArgumentException("direction must be one of {FOCUS_UP, FOCUS_DOWN, ...}");
}

// 次轴距离计算(垂直方向上的距离)
int minorAxisDistance(int direction, Rect source, Rect dest) {
    switch (direction) {
        case FOCUS_LEFT:
        case FOCUS_RIGHT:
            // 垂直方向距离 = |源中心Y - 目标中心Y|
            return Math.abs(source.centerY() - dest.centerY());
        case FOCUS_UP:
        case FOCUS_DOWN:
            // 水平方向距离 = |源中心X - 目标中心X|
            return Math.abs(source.centerX() - dest.centerX());
    }
    throw new IllegalArgumentException("direction must be one of {FOCUS_UP, FOCUS_DOWN, ...}");
}

2.5 特殊方向处理

前向/后向焦点查找

// ViewGroup.java
@Override
public View focusSearch(View focused, int direction) {
    if (direction == FOCUS_FORWARD || direction == FOCUS_BACKWARD) {
        // 使用默认的Tab键顺序
        return FocusFinder.getInstance().findNextFocus(this, focused, direction);
    }
    return super.focusSearch(focused, direction);
}

RecyclerView特殊处理

// RecyclerView.java
@Override
public View focusSearch(View focused, int direction) {
    // 特殊处理网格布局
    if (mLayout instanceof GridLayoutManager) {
        return handleGridFocusSearch(focused, direction);
    }
    return super.focusSearch(focused, direction);
}

private View handleGridFocusSearch(View focused, int direction) {
    // 实现网格布局中的环形焦点逻辑
    // ...
}

2.6 算法优化技巧

性能优化策略

  1. 重用对象:避免在循环中创建临时Rect对象

    private final Rect mTempRect1 = new Rect();
    private final Rect mTempRect2 = new Rect();
    
  2. 提前过滤:在addFocusables中过滤不可见/不可用视图

    // ViewGroup.java
    public void addFocusables(ArrayList<View> views, int direction) {
        if (getVisibility() != VISIBLE) return;
        // ...
    }
    
  3. 空间分区:对复杂布局使用空间索引(如RecyclerView)

调试日志

// 启用详细日志
if (DEBUG) {
    Log.v(TAG, "Looking for next focus in direction " + direction);
    Log.v(TAG, "Focused rect: " + focusedRect);
    for (View candidate : candidates) {
        Rect rect = new Rect();
        candidate.getFocusedRect(rect);
        Log.v(TAG, "Candidate " + candidate + " rect: " + rect);
    }
}

本章小结

  1. 查找流程

    • 优先检查用户指定焦点(nextFocusXXX)
    • 收集所有候选视图
    • 应用几何位置算法筛选最佳候选
  2. 核心算法

    • isCandidate():方向性筛选
    • isBetterCandidate():距离比较规则
    • 主轴/次轴距离计算
  3. 特殊处理

    • 前向/后向导航(FOCUS_FORWARD/FOCUS_BACKWARD)
    • RecyclerView等容器的自定义实现
  4. 优化策略

    • 对象重用减少GC
    • 提前过滤无效候选
    • 详细调试日志

源码位置
frameworks/base/core/java/android/view/FocusFinder.java
frameworks/base/core/java/android/view/ViewGroup.java

在下一章中,我们将深入分析焦点分发全链路流程,从按键事件触发到焦点视图处理事件的完整过程。