6.1 重写焦点查找行为
自定义焦点查找逻辑
@Override
public View focusSearch(View focused, int direction) {
// 特殊处理右方向键
if (direction == View.FOCUS_RIGHT) {
// 跳转到紧急目标视图
View emergencyTarget = findViewById(R.id.emergency_target);
if (emergencyTarget != null && emergencyTarget.isFocusable()) {
return emergencyTarget;
}
}
// 处理环形导航
if (mCircularNavigationEnabled) {
return handleCircularNavigation(focused, direction);
}
return super.focusSearch(focused, direction);
}
private View handleCircularNavigation(View focused, int direction) {
int currentIndex = indexOfChild(focused);
int childCount = getChildCount();
switch (direction) {
case FOCUS_RIGHT:
return getChildAt((currentIndex + 1) % childCount);
case FOCUS_LEFT:
return getChildAt((currentIndex - 1 + childCount) % childCount);
case FOCUS_DOWN:
return getChildAt((currentIndex + COLUMNS) % childCount);
case FOCUS_UP:
return getChildAt((currentIndex - COLUMNS + childCount) % childCount);
}
return null;
}
6.2 动态焦点链构建
运行时焦点链管理
// 创建动态焦点链
public void buildDynamicFocusChain() {
View v1 = findViewById(R.id.view1);
View v2 = findViewById(R.id.view2);
View v3 = findViewById(R.id.view3);
// 设置焦点顺序
setNextFocus(v1, FOCUS_RIGHT, v2);
setNextFocus(v2, FOCUS_RIGHT, v3);
setNextFocus(v3, FOCUS_RIGHT, v1); // 形成环
// 设置回退链
setNextFocus(v2, FOCUS_LEFT, v1);
setNextFocus(v3, FOCUS_LEFT, v2);
}
// 辅助方法设置焦点关系
private void setNextFocus(View view, int direction, View target) {
switch (direction) {
case FOCUS_RIGHT:
view.setNextFocusRightId(target.getId());
break;
case FOCUS_LEFT:
view.setNextFocusLeftId(target.getId());
break;
// 其他方向类似
}
}
6.3 焦点劫持与重定向
焦点事件拦截
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) {
// 拦截确定键事件
handleSpecialAction();
return true; // 事件已消费
}
return super.dispatchKeyEvent(event);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
// 强制焦点跳转到特定视图
View target = findViewById(R.id.special_target);
if (target != null && target.requestFocus()) {
return true;
}
}
return super.onKeyDown(keyCode, event);
}
6.4 焦点状态同步技术
跨视图焦点状态同步
// 创建焦点组
public class FocusGroup {
private final List<View> views = new ArrayList<>();
private View currentFocused;
public void addView(View view) {
views.add(view);
view.setOnFocusChangeListener((v, hasFocus) -> {
if (hasFocus) {
currentFocused = v;
onFocusChanged(v);
}
});
}
private void onFocusChanged(View focused) {
// 更新组内其他视图状态
for (View view : views) {
if (view != focused) {
view.setSelected(view == focused);
}
}
}
public void requestFocus() {
if (currentFocused != null) {
currentFocused.requestFocus();
} else if (!views.isEmpty()) {
views.get(0).requestFocus();
}
}
}
6.5 复杂布局焦点优化
网格布局焦点控制
// 实现网格导航逻辑
@Override
public View focusSearch(View focused, int direction) {
int position = getPosition(focused);
if (position == -1) return super.focusSearch(focused, direction);
int row = position / COLUMNS;
int col = position % COLUMNS;
switch (direction) {
case FOCUS_RIGHT:
if (col < COLUMNS - 1) return getViewAt(row, col + 1);
break;
case FOCUS_LEFT:
if (col > 0) return getViewAt(row, col - 1);
break;
case FOCUS_DOWN:
if (row < ROWS - 1) return getViewAt(row + 1, col);
break;
case FOCUS_UP:
if (row > 0) return getViewAt(row - 1, col);
break;
}
// 边界处理:跳转到其他区域或返回null
return handleEdgeCases(row, col, direction);
}
6.6 焦点冲突解决方案
常见冲突场景及解决方案
场景1:多个视图同时请求焦点
// 使用优先级系统
public class FocusManager {
private static final Map<View, Integer> priorityMap = new HashMap<>();
public static void requestFocusWithPriority(View view, int priority) {
View currentFocus = view.findFocus();
if (currentFocus == null ||
priority > priorityMap.getOrDefault(currentFocus, 0)) {
view.requestFocus();
}
}
}
// 使用示例
FocusManager.requestFocusWithPriority(importantView, 10);
场景2:对话框与背景视图焦点冲突
@Override
public void showDialog() {
// 显示对话框前保存当前焦点
mLastFocusedView = getCurrentFocus();
// 显示对话框
dialog.show();
// 对话框显示后清除背景焦点
if (mLastFocusedView != null) {
mLastFocusedView.clearFocus();
}
}
@Override
public void dismissDialog() {
dialog.dismiss();
// 恢复焦点
if (mLastFocusedView != null) {
mLastFocusedView.requestFocus();
}
}
场景3:动态加载视图的焦点控制
// 动态添加视图后设置焦点链
public void addDynamicView(View newView) {
container.addView(newView);
// 设置新视图的焦点关系
View lastView = findLastFocusable();
if (lastView != null) {
lastView.setNextFocusRightId(newView.getId());
newView.setNextFocusLeftId(lastView.getId());
}
// 设置新视图为首个焦点目标
if (container.getChildCount() == 1) {
newView.setNextFocusLeftId(newView.getId());
newView.setNextFocusRightId(newView.getId());
}
}
6.7 调试与测试工具
焦点可视化工具
// 在View上绘制焦点状态
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.isFocused()) {
// 绘制焦点框
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(4);
Rect rect = new Rect();
child.getDrawingRect(rect);
offsetDescendantRectToMyCoords(child, rect);
canvas.drawRect(rect, paint);
}
}
}
焦点单元测试
// 使用Espresso测试焦点
@RunWith(AndroidJUnit4.class)
public class FocusTest {
@Rule
public ActivityTestRule<MainActivity> rule = new ActivityTestRule<>(MainActivity.class);
@Test
public void testFocusNavigation() {
// 初始焦点检查
onView(withId(R.id.first_button)).check(matches(isFocused()));
// 模拟右方向键
pressKey(KeyEvent.KEYCODE_DPAD_RIGHT);
// 检查焦点是否移动
onView(withId(R.id.second_button)).check(matches(isFocused()));
// 模拟确定键
pressKey(KeyEvent.KEYCODE_DPAD_CENTER);
// 检查点击事件
onView(withId(R.id.result_text)).check(matches(withText("Button clicked")));
}
}
本章小结
-
自定义焦点查找:
- 重写
focusSearch方法实现特殊导航逻辑 - 实现环形导航等高级模式
- 重写
-
动态焦点链:
- 运行时构建焦点关系
- 使用
setNextFocusXxxId动态配置
-
焦点劫持技术:
- 拦截按键事件实现特殊行为
- 强制重定向焦点到特定视图
-
焦点状态同步:
- 创建焦点组实现状态同步
- 跨视图焦点状态管理
-
复杂布局优化:
- 网格布局导航算法
- 位置计算与边界处理
-
焦点冲突解决:
- 优先级系统管理焦点请求
- 对话框与背景视图焦点隔离
- 动态添加视图的焦点链维护
-
调试与测试:
- 可视化焦点状态
- Espresso焦点导航测试
关键技巧总结:
- 优先使用系统焦点机制,必要时才自定义
- 复杂布局中明确焦点路径
- 动态内容需同步更新焦点关系
- 始终考虑无障碍需求
在下一章中,我们将深入探讨TV开发中的焦点特殊处理,包括焦点放大效果、D-Pad导航优化和无障碍焦点支持