4.1 触摸模式核心概念
触摸模式定义与作用
触摸模式是Android系统中处理触摸交互的特殊状态:
- 键盘模式:用户使用物理按键导航(如TV遥控器)
- 触摸模式:用户直接触摸屏幕交互(手机/平板)
- 关键区别:触摸模式下大多数视图默认不显示焦点高亮
触摸模式属性
// View.java
public boolean isInTouchMode() {
return mAttachInfo != null && mAttachInfo.mInTouchMode;
}
public boolean isFocusableInTouchMode() {
return (mViewFlags & FOCUSABLE_IN_TOUCH_MODE) == FOCUSABLE_IN_TOUCH_MODE;
}
4.2 触摸模式状态管理
全局状态跟踪
// ViewRootImpl.java
public void setTouchMode(boolean inTouchMode) {
if (mAttachInfo.mInTouchMode != inTouchMode) {
mAttachInfo.mInTouchMode = inTouchMode;
// 广播到所有视图
mView.dispatchTouchModeChange(inTouchMode);
// 更新输入系统
InputManager.getInstance().setInTouchMode(inTouchMode);
}
}
// View.java
public void dispatchTouchModeChange(boolean inTouchMode) {
onTouchModeChanged(inTouchMode);
if (mChildren != null) {
for (View child : mChildren) {
child.dispatchTouchModeChange(inTouchMode);
}
}
}
触摸事件触发模式切换
// ViewRootImpl.java
public boolean dispatchTouchEvent(MotionEvent event) {
// 触摸事件发生时自动进入触摸模式
if (event.isTouchEvent()) {
setTouchMode(true);
}
// ...事件分发逻辑
}
4.3 触摸模式下的焦点行为
焦点获取规则
// View.java
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 在触摸模式下自动请求焦点
if (isFocusable() &&
isFocusableInTouchMode() &&
!isFocused()) {
requestFocus();
}
break;
}
// ...其他处理
}
不同视图类型的行为差异
| 视图类型 | 触摸模式行为 | 键盘模式行为 |
|---|---|---|
| Button | 无焦点状态,点击触发点击事件 | 显示焦点框,方向键导航 |
| EditText | 获取焦点并弹出键盘 | 显示焦点框,方向键导航 |
| TextView | 无焦点状态 | 可获取焦点但不响应按键 |
| ListView | 无焦点状态,点击项高亮 | 显示焦点框,方向键导航项 |
| CheckBox | 无焦点状态,点击切换状态 | 显示焦点框,空格键切换状态 |
4.4 触摸模式切换机制
模式切换流程图
模式切换源码解析
// ViewRootImpl.java
public void setTouchMode(boolean inTouchMode) {
if (mAttachInfo.mInTouchMode == inTouchMode) return;
// 更新全局状态
mAttachInfo.mInTouchMode = inTouchMode;
// 通知所有视图
if (mView != null) {
mView.dispatchTouchModeChange(inTouchMode);
}
// 清除当前焦点(如果需要)
if (inTouchMode) {
View focused = mView.findFocus();
if (focused != null && !focused.isFocusableInTouchMode()) {
focused.clearFocus();
}
}
}
4.5 焦点恢复机制
Activity生命周期中的焦点管理
// Activity.java
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// 保存当前焦点视图ID
View focusedView = getCurrentFocus();
if (focusedView != null && focusedView.getId() != View.NO_ID) {
outState.putInt("android:viewHierarchyState:focusedViewId", focusedView.getId());
}
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// 恢复焦点视图
int focusedViewId = savedInstanceState.getInt("android:viewHierarchyState:focusedViewId", View.NO_ID);
if (focusedViewId != View.NO_ID) {
View toFocus = findViewById(focusedViewId);
if (toFocus != null) {
toFocus.requestFocus();
}
}
}
触摸模式下的特殊处理
// 在onResume中恢复焦点
@Override
protected void onResume() {
super.onResume();
// 仅当不在触摸模式时恢复焦点
if (!isInTouchMode()) {
View lastFocused = getLastFocusedView();
if (lastFocused != null) {
lastFocused.requestFocus();
}
}
}
4.6 开发实践与问题解决
常见问题解决方案
问题1:EditText在触摸模式下不自动获取焦点
<!-- 确保focusableInTouchMode设置为true -->
<EditText
android:focusableInTouchMode="true"
android:focusable="true"/>
问题2:ListView项在触摸模式下无反馈
// 设置选择器背景
listView.setSelector(R.drawable.list_selector);
// 代码中手动处理触摸反馈
listView.setOnItemClickListener((parent, view, position, id) -> {
view.setPressed(true);
view.postDelayed(() -> view.setPressed(false), 100);
});
问题3:TV应用需要禁用触摸模式
<!-- 在AndroidManifest.xml中设置 -->
<application
android:isTouchMode="false">
最佳实践
-
合理使用focusableInTouchMode:
- 文本输入控件(EditText)应启用
- 普通按钮(Button)应禁用
-
触摸模式下的视觉反馈:
// 使用StateListDrawable替代焦点状态 <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true" android:drawable="@drawable/btn_pressed"/> <item android:state_focused="true" android:drawable="@drawable/btn_focused"/> <item android:drawable="@drawable/btn_normal"/> </selector> -
混合模式处理:
// 同时支持触摸和键盘操作 view.setOnTouchListener((v, event) -> { if (event.getAction() == MotionEvent.ACTION_DOWN) { v.requestFocus(); } return false; }); view.setOnKeyListener((v, keyCode, event) -> { if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { performClick(); return true; } return false; });
4.7 调试工具与技术
触摸模式状态检查
// 检查当前触摸模式状态
boolean isTouchMode = getWindow().getDecorView().isInTouchMode();
// 打印视图焦点状态
void dumpFocusState() {
Log.d("FocusDebug", "Touch Mode: " + isInTouchMode());
View focused = findFocus();
if (focused != null) {
Log.d("FocusDebug", "Focused View: " + focused);
Log.d("FocusDebug", "Focusable in TM: " + focused.isFocusableInTouchMode());
}
}
ADB命令
# 查看当前窗口的触摸模式状态
adb shell dumpsys window windows | grep mInTouchMode
# 强制切换触摸模式
adb shell settings put global touch_mode 0 # 键盘模式
adb shell settings put global touch_mode 1 # 触摸模式
本章小结
-
触摸模式本质:
- 区分触摸交互和键盘交互的全局状态
- 影响焦点获取和视觉反馈
-
状态管理机制:
- ViewRootImpl维护全局状态
- 通过视图树广播状态变更
- 触摸事件自动触发模式切换
-
焦点行为差异:
- 触摸模式下只有
focusableInTouchMode的视图可获取焦点 - 键盘模式下所有
focusable视图可获取焦点
- 触摸模式下只有
-
恢复机制:
- Activity保存/恢复焦点视图ID
- 需区分触摸模式状态处理
-
开发实践:
- 合理配置
focusableInTouchMode - 使用按压状态替代焦点状态
- TV应用禁用触摸模式
- 合理配置
-
调试工具:
- ADB命令查看和修改触摸模式
- 代码检查视图焦点能力
关键源码路径:
frameworks/base/core/java/android/view/ViewRootImpl.java
frameworks/base/core/java/android/view/View.java
在下一章中,我们将深入分析ViewGroup的焦点策略,包括descendantFocusability机制和滚动容器中的焦点处理。