如何在Jetpack的Compose中管理Focus状态

时间:2020-04-23 18:31:35

标签: android android-jetpack android-jetpack-compose focusmanager composable

我有一个自定义的可组合视图(本质上是表面+文本),我想根据焦点状态更改表面的颜色。 FocusManager#FocusNode被标记为内部,我不知道有任何方法可以实现此目的。这仅仅是不可用吗?还有其他人要解决吗?

3 个答案:

答案 0 :(得分:6)

dev11起,已弃用FocusManagerAmbient,而赞成FocusModifier。有关更多示例,请查看KeyInputDemoComposeInputFieldFocusTransitionFocusableDemo

@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;不过,我不确定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)

您还可以在focusIdentifier上设置FocusNode

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")

您也可以与孩子一起组成

@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")
        }
    }
}

gif

答案 1 :(得分:3)

使用1.0.0-alpha01Modifier.focusObserver
像这样:

@OptIn(ExperimentalFocus::class)
@Composable
fun sampleFocusObserveExample(){

    var color by remember { mutableStateOf(Black) }
    val focusRequester = FocusRequester()
    Text(
            modifier = Modifier
                    .focusRequester(focusRequester)
                    .focusObserver { color = if (it.isFocused) Green else Black }
                    .focus()
                    .tapGestureFilter { focusRequester.requestFocus() },
            text = "Text",
            color = color
    )
}

注意:您可以使用@OptIn(ExperimentalFocus::class)检查Focus API是实验性的,将来可能会更改。

答案 2 :(得分:2)

此代码对我有用

var haveFocus by remember { mutableStateOf(false) }
Surface(
    modifier = Modifier.onFocusEvent {
        haveFocus = it.isFocused
    },
    shape = RectangleShape
){...}