如何在 Jetpack Compose 中管理焦点状态
How to manage Focus state in Jetpack's Compose
我有一个自定义的可组合视图(本质上是表面 + 文本),我想根据焦点状态更改表面的颜色。 FocusManager#FocusNode 被标记为内部,我不知道有什么方法可以实现这一点。这只是还不可用吗?还有其他人必须解决这个问题吗?
自 dev11
起,FocusManagerAmbient
已被弃用,取而代之的是 FocusModifier
。有关更多示例,请查看 KeyInputDemo, ComposeInputFieldFocusTransition or FocusableDemo.
@Composable
private fun FocusableText(text: MutableState<String>) {
val focusModifier = FocusModifier()
Text(
modifier = focusModifier
.tapGestureFilter { focusModifier.requestFocus() }
.keyInputFilter { it.value?.let { text.value += it; true } ?: false },
text = text.value,
color = when (focusModifier.focusState) {
Focused -> Color.Green
NotFocused -> Color.Black
NotFocusable -> Color.Gray
}
)
}
Compose 已更新并成为 FocusManager
成员 public
;不过,我不确定 api 到 dev10
的最终结果如何。但截至目前,您可以创建一个 FocusNode
并使用 FocusManager.registerObserver
监听更改。
val focusNode = remember {
FocusNode().apply {
focusManager.registerObserver(node = this) { fromNode, toNode ->
if (toNode == this) {
// has focus
} else {
// lost focus
}
}
}
}
如果你想获得焦点,你现在可以调用 FocusManager.requestFocus
:
val focusManager = FocusManagerAmbient.current
focusManager.requestFocus(focusNode)
您还可以在 FocusNode
上设置 focusIdentifier
:
val focusNode = remember {
FocusNode().apply {
...
focusManager.registerFocusNode("your-focus-identifier", node = this)
}
}
要获得特定标识符的焦点,您只需调用 FocusManager.requestFocusById
使用它您可以轻松地创建一个 Composable
可以为您提供和请求焦点,例如:
@Composable
fun useFocus(focusIdentifier: String? = null): Pair<Boolean, () -> Unit> {
val focusManager = FocusManagerAmbient.current
val (hasFocus, setHasFocus) = state { false }
val focusNode = remember {
FocusNode().apply {
focusManager.registerObserver(node = this) { fromNode, toNode ->
setHasFocus(toNode == this)
}
focusIdentifier?.let { identifier ->
focusManager.registerFocusNode(identifier, node = this)
}
}
}
onDispose {
focusIdentifier?.let { identifier ->
focusManager.unregisterFocusNode(identifier)
}
}
return hasFocus to {
focusManager.requestFocus(focusNode)
}
}
val (hasFocus, requestFocus) = useFocus("your-focus-identifier")
你也可以和它一起组成children:
@Composable
fun FocusableTextButton(
text: String,
focusedColor: Color = Color.Unset,
unFocusedColor: Color = Color.Unset,
textColor: Color = Color.White,
focusIdentifier: String? = null
) {
val (hasFocus, requestFocus) = useFocus(focusIdentifier)
Surface(color = if (hasFocus) focusedColor else unFocusedColor) {
TextButton(onClick = requestFocus) {
Text(text = text, color = textColor)
}
}
}
或者,还有 FocusModifier
,目前是:
/**
* A [Modifier.Element] that wraps makes the modifiers on the right into a Focusable. Use a
* different instance of [FocusModifier] for each focusable component.
*
* TODO(b/152528891): Write tests for [FocusModifier] after we finalize on the api (API
* review tracked by b/152529882).
*/
但我认为您现在不能使用它来应用标识符。
val focusModifier = FocusModifier()
val hasFocus = focusModifier.focusDetailedState == FocusDetailedState.Active
Surface(
modifier = focusModifier,
color = if (hasFocus) focusedColor else unFocusedColor
) {
TextButton(onClick = { focusModifier.requestFocus() }) {
Text(text = text, color = textColor)
}
}
综上所述,我不是 100% 确定这是现在处理焦点的预期方式。我引用了很多 CoreTextField 来了解它是如何被处理的。
示例:
@Composable
fun FocusTest() {
val focusManager = FocusManagerAmbient.current
val selectRandomIdentifier: () -> Unit = {
focusManager.requestFocusById(arrayOf("red,", "blue", "green", "yellow").random())
}
Column(verticalArrangement = Arrangement.SpaceBetween) {
FocusableTextButton(
text = "When I gain focus, I turn red",
focusedColor = Color.Red,
focusIdentifier = "red"
)
FocusableTextButton(
text = "When I gain focus, I turn blue",
focusedColor = Color.Blue,
focusIdentifier = "blue"
)
FocusableTextButton(
text = "When I gain focus, I turn green",
focusedColor = Color.Green,
focusIdentifier = "green"
)
FocusableTextButton(
text = "When I gain focus, I turn yellow",
focusedColor = Color.Yellow,
focusIdentifier = "yellow"
)
Button(onClick = selectRandomIdentifier) {
Text(text = "Click me to randomly select a node to focus")
}
}
}
通过1.0.x
您可以使用Modifier.onFocusChanged
观察焦点状态事件。
类似于:
var color by remember { mutableStateOf(Black) }
val focusRequester = FocusRequester()
Text(
modifier = Modifier
.focusRequester(focusRequester)
.onFocusChanged { color = if (it.isFocused) Green else Black }
.focusModifier()
.pointerInput(Unit) { detectTapGestures { focusRequester.requestFocus() } },
text = "Text",
color = color
)
这段代码对我有用
var haveFocus by remember { mutableStateOf(false) }
Surface(
modifier = Modifier.onFocusEvent {
haveFocus = it.isFocused
},
shape = RectangleShape
){...}
此答案基于 Gabriele Mariotti 的答案,但我有更改
- 第二次点击
Text
后,使用 remember { FocusRequester() }
防止崩溃 FocusRequester is not initialized
- 使用
focusTarget
而不是 focusModifier
因为已弃用
.
var color by remember { mutableStateOf(Color.Black) }
val focusRequester = remember { FocusRequester() }
Text(
text = "Hello",
modifier = Modifier
.focusRequester(focusRequester)
.onFocusChanged { color = if (it.isFocused) Color.Green else Color.Black }
.focusTarget()
.pointerInput(Unit) { detectTapGestures { focusRequester.requestFocus() } }
)
我有一个自定义的可组合视图(本质上是表面 + 文本),我想根据焦点状态更改表面的颜色。 FocusManager#FocusNode 被标记为内部,我不知道有什么方法可以实现这一点。这只是还不可用吗?还有其他人必须解决这个问题吗?
自 dev11
起,FocusManagerAmbient
已被弃用,取而代之的是 FocusModifier
。有关更多示例,请查看 KeyInputDemo, ComposeInputFieldFocusTransition or FocusableDemo.
@Composable
private fun FocusableText(text: MutableState<String>) {
val focusModifier = FocusModifier()
Text(
modifier = focusModifier
.tapGestureFilter { focusModifier.requestFocus() }
.keyInputFilter { it.value?.let { text.value += it; true } ?: false },
text = text.value,
color = when (focusModifier.focusState) {
Focused -> Color.Green
NotFocused -> Color.Black
NotFocusable -> Color.Gray
}
)
}
Compose 已更新并成为 FocusManager
成员 public
;不过,我不确定 api 到 dev10
的最终结果如何。但截至目前,您可以创建一个 FocusNode
并使用 FocusManager.registerObserver
监听更改。
val focusNode = remember {
FocusNode().apply {
focusManager.registerObserver(node = this) { fromNode, toNode ->
if (toNode == this) {
// has focus
} else {
// lost focus
}
}
}
}
如果你想获得焦点,你现在可以调用 FocusManager.requestFocus
:
val focusManager = FocusManagerAmbient.current
focusManager.requestFocus(focusNode)
您还可以在 FocusNode
上设置 focusIdentifier
:
val focusNode = remember {
FocusNode().apply {
...
focusManager.registerFocusNode("your-focus-identifier", node = this)
}
}
要获得特定标识符的焦点,您只需调用 FocusManager.requestFocusById
使用它您可以轻松地创建一个 Composable
可以为您提供和请求焦点,例如:
@Composable
fun useFocus(focusIdentifier: String? = null): Pair<Boolean, () -> Unit> {
val focusManager = FocusManagerAmbient.current
val (hasFocus, setHasFocus) = state { false }
val focusNode = remember {
FocusNode().apply {
focusManager.registerObserver(node = this) { fromNode, toNode ->
setHasFocus(toNode == this)
}
focusIdentifier?.let { identifier ->
focusManager.registerFocusNode(identifier, node = this)
}
}
}
onDispose {
focusIdentifier?.let { identifier ->
focusManager.unregisterFocusNode(identifier)
}
}
return hasFocus to {
focusManager.requestFocus(focusNode)
}
}
val (hasFocus, requestFocus) = useFocus("your-focus-identifier")
你也可以和它一起组成children:
@Composable
fun FocusableTextButton(
text: String,
focusedColor: Color = Color.Unset,
unFocusedColor: Color = Color.Unset,
textColor: Color = Color.White,
focusIdentifier: String? = null
) {
val (hasFocus, requestFocus) = useFocus(focusIdentifier)
Surface(color = if (hasFocus) focusedColor else unFocusedColor) {
TextButton(onClick = requestFocus) {
Text(text = text, color = textColor)
}
}
}
或者,还有 FocusModifier
,目前是:
/**
* A [Modifier.Element] that wraps makes the modifiers on the right into a Focusable. Use a
* different instance of [FocusModifier] for each focusable component.
*
* TODO(b/152528891): Write tests for [FocusModifier] after we finalize on the api (API
* review tracked by b/152529882).
*/
但我认为您现在不能使用它来应用标识符。
val focusModifier = FocusModifier()
val hasFocus = focusModifier.focusDetailedState == FocusDetailedState.Active
Surface(
modifier = focusModifier,
color = if (hasFocus) focusedColor else unFocusedColor
) {
TextButton(onClick = { focusModifier.requestFocus() }) {
Text(text = text, color = textColor)
}
}
综上所述,我不是 100% 确定这是现在处理焦点的预期方式。我引用了很多 CoreTextField 来了解它是如何被处理的。
示例:
@Composable
fun FocusTest() {
val focusManager = FocusManagerAmbient.current
val selectRandomIdentifier: () -> Unit = {
focusManager.requestFocusById(arrayOf("red,", "blue", "green", "yellow").random())
}
Column(verticalArrangement = Arrangement.SpaceBetween) {
FocusableTextButton(
text = "When I gain focus, I turn red",
focusedColor = Color.Red,
focusIdentifier = "red"
)
FocusableTextButton(
text = "When I gain focus, I turn blue",
focusedColor = Color.Blue,
focusIdentifier = "blue"
)
FocusableTextButton(
text = "When I gain focus, I turn green",
focusedColor = Color.Green,
focusIdentifier = "green"
)
FocusableTextButton(
text = "When I gain focus, I turn yellow",
focusedColor = Color.Yellow,
focusIdentifier = "yellow"
)
Button(onClick = selectRandomIdentifier) {
Text(text = "Click me to randomly select a node to focus")
}
}
}
通过1.0.x
您可以使用Modifier.onFocusChanged
观察焦点状态事件。
类似于:
var color by remember { mutableStateOf(Black) }
val focusRequester = FocusRequester()
Text(
modifier = Modifier
.focusRequester(focusRequester)
.onFocusChanged { color = if (it.isFocused) Green else Black }
.focusModifier()
.pointerInput(Unit) { detectTapGestures { focusRequester.requestFocus() } },
text = "Text",
color = color
)
这段代码对我有用
var haveFocus by remember { mutableStateOf(false) }
Surface(
modifier = Modifier.onFocusEvent {
haveFocus = it.isFocused
},
shape = RectangleShape
){...}
此答案基于 Gabriele Mariotti 的答案,但我有更改
- 第二次点击
Text
后,使用 - 使用
focusTarget
而不是focusModifier
因为已弃用
remember { FocusRequester() }
防止崩溃 FocusRequester is not initialized
.
var color by remember { mutableStateOf(Color.Black) }
val focusRequester = remember { FocusRequester() }
Text(
text = "Hello",
modifier = Modifier
.focusRequester(focusRequester)
.onFocusChanged { color = if (it.isFocused) Color.Green else Color.Black }
.focusTarget()
.pointerInput(Unit) { detectTapGestures { focusRequester.requestFocus() } }
)