浅谈 Android焦点管理机制 & 事件分发机制

什么是焦点

  • 焦点能够让 视图和窗口 可以接受和处理 按键事件和导航事件
  • 在 Android 中,按键事件和导航事件通常指的是 与物理按键和输入设备(如键盘、遥控器、游戏手柄等)相关的交互事件
    焦点的处理对于非触摸屏设备(如电视盒子、某些游戏设备等)非常重要。

有哪些些常见的按键事件和导航事件

按键事件 (Key Events)

KeyEvent.KEYCODE_BACK: 返回键。用于导航回上一个屏幕或关闭弹出的菜单或对话框。

KeyEvent.KEYCODE_MENU: 菜单键。通常用于打开一个上下文菜单或应用的主菜单。

KeyEvent.KEYCODE_HOME: 主页键。通常用于返回设备的主屏幕。

KeyEvent.KEYCODE_VOLUME_UP & KeyEvent.KEYCODE_VOLUME_DOWN: 音量增加和减少键。用于控制设备的音量。

KeyEvent.KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_RIGHT: 方向键。用于在界面中导航,特别是在非触摸屏设备上。

KeyEvent.KEYCODE_DPAD_CENTER or KeyEvent.KEYCODE_ENTER: 确认键。用于选择当前聚焦的项目。

KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KEYCODE_MEDIA_NEXT, KEYCODE_MEDIA_PREVIOUS: 媒体控制键。用于控制媒体播放。

导航事件 (Navigation Events)

轨迹球导航 (Trackball Navigation): 一些设备配备了轨迹球,用于在界面上导航。

键盘导航 (Keyboard Navigation): 使用键盘的方向键进行界面导航。

游戏手柄导航 (Gamepad Navigation): 使用游戏手柄上的摇杆或方向键进行界面导航。

处理按键事件

在 Android 应用中处理按键事件通常涉及重写 Activity 或 View 的 onKeyDown 和 onKeyUp 方法。例如:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
// 处理返回键的操作
return true;
}
return super.onKeyDown(keyCode, event);
}

焦点的分类

焦点可以分为两种:窗口焦点和视图焦点

  • 窗口焦点(Window Focus):通常指的是一个 Activity、Dialog 或者 PopupWindow 等窗口是否能接收键盘输入和其他导航事件

一个窗口只有在获得焦点时,才能接收到按键事件(如返回键、菜单键等)。

  • 视图焦点(View Focus):指的是用户界面中的某个特定视图(如 EditText、Button 等) 是否能接收键盘输入。

为什么视图需要申请焦点?

一个目的 :确保视图能够 正常 接收和处理 用户的输入

三个目标:

  1. 输入事件处理:只有拥有焦点的视图才能接收键盘输入事件,这些事件包括文字输入、按键事件(如返回键、菜单键等)。
  2. 物理按键和输入设备的响应:对于那些可以响应物理按键(如游戏手柄、遥控器、硬件键盘等)的设备来说,焦点控制对于视图来接收和处理这些按键事件至关重要。
  3. 导航和可访问性:焦点状态也用于辅助功能,如屏幕阅读器,帮助视障用户导航应用界面。同时,使用方向键(如 D-pad 或键盘上的箭头键)在不同控件间导航时,系统也是根据哪个控件拥有焦点来确定下一个获得焦点的控件。

在代码中,可以调用视图的 requestFocus() 方法来申请焦点。例如,如果你想要一个 Button 在 Activity 启动时就获取焦点,你可以:

如何申请焦点?

调用requestFocus方法

Button button = findViewById(R.id.my_button);
button.requestFocus();

也可以在 XML 布局文件中使用属性来指定某个视图应当获得焦点:

<Button
android:id="@+id/my_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Press Me"
android:focusable="true"
android:focusableInTouchMode="true" />

focusable 设置为 true 表示这个 Button 可以获得焦点,focusableInTouchMode 设置为 true 表示这个 Button 在触摸模式下也可以获得焦点。

申请焦点带来的异常情况

当一个应用程序或视图请求焦点时,可能会触发系统行为,如显示或隐藏导航栏(也被称作 dock 栏)。

开发中经常会碰到这种需求:弹框弹出的时候不让dock栏一同弹出,当申请焦点 dock栏自动弹出分为下面几种情况:

  • 键盘显示:在某些情况下,当一个输入框(EditText)获得焦点时,系统会自动显示软键盘。随着软键盘的弹出,导航栏也会一并显示,因为用户可能需要用到导航键(比如返回键)来关闭软键盘。
  • 全屏模式下焦点变更:如果应用处于全屏模式,当申请焦点时,系统可能会认为用户需要进行某种交互,因此会系统会自动退出全屏模式并显示导航栏供用户使用。
  • 焦点改变监听:如果某些视图上设置了焦点改变监听器(OnFocusChangeListener),在这些监听器中,可能有代码触发了导航栏的显示。
  • 系统窗口标志更改:申请焦点可能导致窗口的某些 UI 标志被重置,特别是那些控制系统 UI 可见性的标志

如何解决

  • 维持窗口的全屏Flag:如果问题是在全屏模式下发生的,在申请焦点之后需要去 重新设置全屏模式相关的窗口标志。
  • 调整焦点策略不需要时不要申请焦点
  • 监听焦点变化重新设置可见性:设置焦点变化监听器,当焦点变化时,重新应用 UI 标志,以保持 dock 栏的隐藏状态

代码如下:

View decorView = getWindow().getDecorView();
decorView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
// Set the system UI visibility flags
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
}
});

什么是触摸事件?

触摸事件(Touch Events)是 用户与屏幕 的交互,比如点击(tap)、长按(long press)、滑动(swipe)等。

触摸事件涉及到事件分发,相关规则可以查看Andorid开发艺术探索

焦点与触摸事件的关系

  • 焦点不是触摸事件的必要条件:一个视图不需要有焦点就能接收到触摸事件

比如,在一个 ScrollView 中滑动屏幕,视图可以处理滑动事件,即使它没有焦点。

  • 可聚焦视图可以响应按键事件:一个视图如果可以获得焦点(isFocusable 为 true),则它可以响应如按键事件(key events)。

例如,当一个 Button 获得焦点后,用户可以通过按下硬件键盘的 Enter 键来触发点击事件。

  • 焦点转移:用户触摸屏幕上的一个可聚焦的视图时,该视图可能获取焦点(如果它的 isFocusableInTouchMode 设置为 true)。

  • 焦点决定键盘输入:例如,当一个 EditText 获得焦点后,它可以接收键盘输入

  • 触摸事件可以改变焦点状态

例如,当用户在一个 ListView 中触摸一个条目时,该条目可能获得焦点。

不同情况下 焦点 的触摸事件的 处理

  1. PopupWindow 或 Dialog 等窗口:
  • 如果窗口是可以聚焦的 则 可以响应按键事件,其内部的视图也可以响应触摸事件。

  • 如果窗口不可以聚焦 ,触摸事件仍然可以被内部视图捕获,但按键事件将可能被后面的窗口处理。

  1. 具体的某个视图:
  • 设置不可聚焦不获取焦点的视图:如果触摸了一个 isFocusable 设置为 false 的视图,它将不会获得焦点,但它仍然可以处理触摸事件。

  • 已经获取了焦点,该视图可以直接处理触摸事件,焦点状态不会改变。

  • 当前没有焦点但是设置了可聚焦的状态,该视图可能会获得焦点,并处理触摸事件。

总结一下:
一个没有焦点的视图可以接收触摸事件,但无法接收键盘等导航事件。
反之,一个有焦点的视图可以响应键盘事件,并且如果它是可触摸的,也可以响应触摸事件。

焦点只是决定你是否能够响应物理按键和键盘和无障碍服务,且任何一个时间只能有一个视图持有焦点,而触摸事件 可以决定让哪个视图拥有焦点(通过可聚焦状态和触摸事件的位置)

多窗口情况下 的 焦点管理和事件分发

事件分发和视图是否持有焦点无关,在多窗口环境中,触摸事件首先被最上层的窗口捕获,如果这个窗口不消费(consume)该事件,事件可以继续向下传递至下一个窗口。

在多窗口环境中,焦点是用来确定哪个视图或窗口将接收键盘输入或导航事件。如果新弹出的窗口没有申请焦点,那么焦点还在原窗口上;否则就会进行流转。

你可能感兴趣的:(Andorid,进阶之路,android,java)