Jetpack Compose中的焦点管理

3,109 阅读3分钟

Jetpack Compose中的焦点管理

简介

Jetpack Compose中的焦点管理和传统View系统有所不同,虽然不常用,但是不了解的话用起来还真不知道该咋用。今天写个验证码控件的时候就整懵了,于是学习了下,分享出来帮大家快速上手Compose.

由于我也是边学边写的这篇文章,因此可能存在错误,欢迎指正。🤗

焦点是什么

焦点可能在手机上不常见,但在TV上还是很常见的,用遥控器操作UI的时候,焦点就很重要了。用户通过用遥控器上下左右来移动焦点选择控件,然后进行操作。

至于在手机上,焦点通常用于用户输入的时候移动输入框焦点了。

快速上手

1. 移动焦点

最简单的操作焦点的需求就是移动焦点了,比如将焦点移动到下一个输入框

首先我们需要获取一个FocusManager, 通过:

val focusManager = LocalFocusManager.current

来获取, 然后在需要移动焦点时调用 focusManager.moveFocus()来移动焦点, 传入方向参数来指定向哪个方向移动焦点 例如向右移动焦点:

focusManager.moveFocus(FocusDirection.Right)

2. 添加焦点

如果你想给你自己的控件添加焦点,你可以使用 Modifier.focusable() 即可为控件添加焦点, 例如:

Box(
  modifier = Modifier
    .focusable()
    .size(400.dp)
){
  Text("Hello World!")
}

这样,这个Box就是可聚焦的了

注意: Compose中一些本来就需要焦点的控件已经自带焦点,请不要再给他们添加焦点了
例如TextField

3. 监听焦点变化

非常简单,通过 Modifier.onFocusChanged 或者 Modifier.onFocusEvent 即可,两者几乎相同,区别在于onFucusChanged会判断状态是否真正变化了再通知你,而onFucusEvent不会判断。

举个例子:

TextField(
  value = ...
  onValueChange = { .. }
  modifier = Modifier.onFocusChanged {
    when {
        it.isFocused -> // 我被聚焦了
        it.hasFocused -> // 我的子项被聚焦了
        it.isCaptured -> // 是否处于捕获状态
    }
  }

捕获: 焦点在被聚焦后,还可以设置捕获,在捕获状态下,其他组件不能请求焦点,直到主动释放焦点

提示: onFucusChanged 内部也是基于onFucusEvent的

4. 设置焦点顺序

前面说到可以通过 focusManager.moveFocus() 来移动焦点,但是很显然,这个顺序是Compose定好的,我们如何自定义焦点的顺序呢, 这里我们需要用到Modifier.focusOrder()来自定义焦点顺序, 上代码:

// 首先我们获取2个新的焦点引用, 代表2个不同的焦点 (这里用到了kotlin的解构特性)
val (cat, dog) = FocusRequester.createRefs()

Box(
  modifier = Modifier
  .focusOrder(cat) { // 这里将cat引用传了进去
    next = dog // 设置cat下一个焦点是dog
    down = dog // 设置cat下面的焦点是dog
    // ..
  }
  .focusable() // 设置可被聚焦
)

Box(
  modifier = Modifier
  .focusOrder(dog) {
    previous = cat // 设置dog上一个角度是cat
    up = cat // 设置dog上面的焦点是cat
  }
  .focusable() // 设置可被聚焦
)

这里是不是非常像Compose中的约束布局API? 哈哈

注意: focusable() 一定要放在其他焦点Modifier后面,否则不会起作用!例如 focusable() 需要放在 focusOrder下面。因为Modifier使用盒模型,focusable放在外部其他焦点Modifier将无法获取焦点信息.

5. 以编程方式直接请求焦点

利用FocusManager来移动焦点可能在某些情况下还是不能符合需求,我们可能需要直接请求某个特定的焦点。这里和设置焦点顺序其实是差不多,也需要设置焦点请求的引用,然后调用焦点引用来请求焦点:

// 这里直接new一个FocusRequester了, 因为 FocusRequester.createRefs() 内部其实就是new了这个类的实例
val requester = FocusRequester()

Box(
    modifier = Modifier
    .focusOrder(requester)
    .focusable()
)

SideEffect {
  // 直接在重组完成后请求Box的焦点
  requester.requestFocus()
}

其他FocusRequester的API:

  • freeFocus 释放捕获的焦点
  • captureFocus 捕获焦点 (需要用requestFocus()获取到了焦点再调用)