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 焦点查找完整流程
算法流程图
源码实现(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 算法优化技巧
性能优化策略
-
重用对象:避免在循环中创建临时Rect对象
private final Rect mTempRect1 = new Rect(); private final Rect mTempRect2 = new Rect(); -
提前过滤:在addFocusables中过滤不可见/不可用视图
// ViewGroup.java public void addFocusables(ArrayList<View> views, int direction) { if (getVisibility() != VISIBLE) return; // ... } -
空间分区:对复杂布局使用空间索引(如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);
}
}
本章小结
-
查找流程:
- 优先检查用户指定焦点(nextFocusXXX)
- 收集所有候选视图
- 应用几何位置算法筛选最佳候选
-
核心算法:
isCandidate():方向性筛选isBetterCandidate():距离比较规则- 主轴/次轴距离计算
-
特殊处理:
- 前向/后向导航(FOCUS_FORWARD/FOCUS_BACKWARD)
- RecyclerView等容器的自定义实现
-
优化策略:
- 对象重用减少GC
- 提前过滤无效候选
- 详细调试日志
源码位置:
frameworks/base/core/java/android/view/FocusFinder.java
frameworks/base/core/java/android/view/ViewGroup.java
在下一章中,我们将深入分析焦点分发全链路流程,从按键事件触发到焦点视图处理事件的完整过程。