2. 电视端焦点导航与 D-pad 按键响应处理
电视端焦点导航的核心机制
在 Android TV 上,遥控器 D-pad 的方向键不会像手机触摸那样直接命中控件,而是通过 焦点系统在可聚焦视图之间移动。每个可聚焦控件(focusable="true")在获得焦点时触发 onFocusChange,按下确认键(KEYCODE_DPAD_CENTER / KEYCODE_ENTER)时触发 onClick。因此电视交互的关键是:焦点落在哪里、如何移动、按下后响应什么。
实现步骤
下面用一个 LinearLayout 嵌套两行按钮来演示焦点移动与按键响应。每个按钮设置 focusable="true",并通过 StateListDrawable 或 foreground 实现焦点高亮。
1. 布局文件 activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:background="#1a1a2e">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_a"
android:layout_width="160dp"
android:layout_height="80dp"
android:text="按钮 A"
android:focusable="true"
android:background="@drawable/focus_bg"
android:layout_margin="12dp"/>
<Button
android:id="@+id/btn_b"
android:layout_width="160dp"
android:layout_height="80dp"
android:text="按钮 B"
android:focusable="true"
android:background="@drawable/focus_bg"
android:layout_margin="12dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_c"
android:layout_width="160dp"
android:layout_height="80dp"
android:text="按钮 C"
android:focusable="true"
android:background="@drawable/focus_bg"
android:layout_margin="12dp"/>
<Button
android:id="@+id/btn_d"
android:layout_width="160dp"
android:layout_height="80dp"
android:text="按钮 D"
android:focusable="true"
android:background="@drawable/focus_bg"
android:layout_margin="12dp"/>
</LinearLayout>
</LinearLayout>
2. 焦点高亮背景 focus_bg.xml(drawable 目录)
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true">
<shape>
<solid android:color="#3a3a6e"/>
<stroke android:width="3dp" android:color="#f5c86e"/>
<corners android:radius="10dp"/>
</shape>
</item>
<item>
<shape>
<solid android:color="#2a2a4e"/>
<stroke android:width="1dp" android:color="#4a6a8a"/>
<corners android:radius="10dp"/>
</shape>
</item>
</selector>
3. MainActivity.java 按键响应与焦点日志
public class MainActivity extends Activity {
private static final String TAG = "TVFocus";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int[] ids = {R.id.btn_a, R.id.btn_b, R.id.btn_c, R.id.btn_d};
for (int id : ids) {
Button btn = findViewById(id);
btn.setOnClickListener(v ->
Log.d(TAG, "点击: " + ((Button) v).getText()));
btn.setOnFocusChangeListener((v, hasFocus) ->
Log.d(TAG, ((Button) v).getText()
+ (hasFocus ? " 获得焦点" : " 失去焦点")));
}
// 启动后默认让按钮 A 获得焦点
findViewById(R.id.btn_a).requestFocus();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_DPAD_DOWN:
case KeyEvent.KEYCODE_DPAD_LEFT:
case KeyEvent.KEYCODE_DPAD_RIGHT:
Log.d(TAG, "方向键: " + KeyEvent.keyCodeToString(keyCode));
break;
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
Log.d(TAG, "确认键按下");
break;
}
return super.onKeyDown(keyCode, event);
}
}
电视模拟器中,方向键对应键盘方向键,确认键对应回车键。运行后观察 Logcat 中
TVFocus 标签的输出,确认焦点在 A→B→D→C 之间循环移动,且每次按下确认键都有对应点击日志。验证要点
| 操作 | 预期现象 | 日志验证 |
|---|---|---|
| 启动应用 | 按钮 A 显示黄色焦点边框 | 按钮 A 获得焦点 |
| 按 → 键 | 焦点从 A 移到 B | A 失去焦点,B 获得焦点 |
| 按 ↓ 键 | 焦点从 B 移到 D | B 失去焦点,D 获得焦点 |
| 按 ← 键 | 焦点从 D 移到 C | D 失去焦点,C 获得焦点 |
| 按确认键 | 当前焦点按钮触发点击 | 输出“点击: 按钮 X” |
如果焦点没有按预期移动,检查按钮是否设置了
android:focusable="true",以及父布局是否意外拦截了焦点。电视上默认焦点顺序由布局层级和 nextFocusUp/Down/Left/Right 属性共同决定,必要时可显式指定。