使用Jetpack Compose构建软件键盘 - 使用Jetpack Compose实现输入法(IME)

7

在Jetpack Compose中构建一个简单的键盘非常简单和直接。

编辑:工作示例:https://github.com/THEAccess/compose-keyboard-ime

我使用以下方法构建了一个非常简单的KeyRow:

Key.kt

@Composable
fun Key(modifier: Modifier = Modifier, label: String, onClick: () -> Unit) {
    val shape = RoundedCornerShape(4.dp)
    //TODO: make clickable outside but don't show ripple
    Box(modifier = modifier
            .padding(2.dp)
            .clip(shape)
            .clickable(onClick = onClick)
            .background(Color.White)
            .padding(vertical = 12.dp, horizontal = 4.dp), contentAlignment = Alignment.Center) {
        Text(text = label, fontSize = 20.sp)
    }
}

KeyRow.kt

@Composable
fun KeyRow(keys: List<String>) {
    Row(modifier = Modifier.fillMaxWidth().background(color = grey200)) {
        keys.forEach {
            Key(modifier = Modifier.weight(1f), label = it, onClick = {  })
        }
    }
}

这就是它的样子:

2

我想实现这个动画:

3

然而,我目前卡在这里了

![4]

层次结构

-Keyboard
--KeyRow
---KeyLayout
----Key
----KeyPressedOverlay (only visible when pressed)

我的主要问题是我不知道如何显示KeyPressedOverlay Composale(它比Key Composable更大)而不使父布局变大。因此,我需要以某种方式溢出父布局。


你能发布这个的源代码吗?我找不到使用Compose构建键盘的例子。 - dessalines
@thouliha https://github.com/THEAccess/compose-keyboard-ime - Yannick
有一个PR可以让它在当前版本的Compose上运行。 - dessalines
2个回答

6

我不确定这是否是最好的方法(可能不是),但我发现使用ConstraintLayout可以解决问题...

val keys = listOf("A", "B", "C", "D")
ConstraintLayout(
    modifier = Modifier.graphicsLayer(clip = false)
) {
    val refs = keys.map { createRef() }
    refs.forEachIndexed { index, ref ->
        val modifier = when (index) {
            0 -> Modifier.constrainAs(ref) {
                start.linkTo(parent.start)
            }
            refs.lastIndex -> Modifier.constrainAs(ref) {
                start.linkTo(refs[index - 1].end)
                end.linkTo(parent.end)
            }
            else -> Modifier.constrainAs(ref) {
                start.linkTo(refs[index - 1].end)
                end.linkTo(refs[index + 1].start)
            }
        }
        val modifierPressed = Modifier.constrainAs(createRef()) {
            start.linkTo(ref.start)
            end.linkTo(ref.end)
            bottom.linkTo(ref.bottom)
        }
        KeyboardKey(
            keyboardKey = keys[index],
            modifier = modifier,
            modifierPressed = modifierPressed,
            pressed = { s -> /* Do something with the key */}
        )
    }
}

这里的一个重要细节是graphicLayer(clip = false)(类似于View工具包中的clipChildren)。接着,我为每个键和按下的键创建了一个修饰符。注意到modifierPressed与其他修饰符的中心/底部对齐。

最后,下面描述了KeyboardKey

@Composable
fun KeyboardKey(
    keyboardKey: String,
    modifier: Modifier,
    modifierPressed: Modifier,
    pressed: (String) -> Unit
) {
    var isKeyPressed by remember { mutableStateOf(false) }
    Text(keyboardKey, Modifier
        .then(modifier)
        .pointerInput(Unit) {
            detectTapGestures(onPress = {
                isKeyPressed = true
                val success = tryAwaitRelease()
                if (success) {
                    isKeyPressed = false
                    pressed(keyboardKey)
                } else {
                    isKeyPressed = false
                }
            })
        }
        .background(Color.White)
        .padding(
            start = 12.dp,
            end = 12.dp,
            top = 16.dp,
            bottom = 16.dp
        ),
        color = Color.Black
    )
    if (isKeyPressed) {
        Text(
            keyboardKey, Modifier
                .then(modifierPressed)
                .background(Color.White)
                .padding(
                    start = 16.dp,
                    end = 16.dp,
                    top = 16.dp,
                    bottom = 48.dp
                ),
            color = Color.Black
        )
    }
}

这是我得到的结果: 键盘效果 编辑: 添加一些逻辑后,我得到了这个... 在此输入图像描述 希望这次有所帮助 ;) 如有需要,以下是要点总结... https://gist.github.com/nglauber/4cb1573efba9024c008ea71f3320b4d8

1
谢谢你的回答!使用扁平布局层次结构是个好主意。有点小技巧,但它能够工作 :) - Yannick
有没有一种使用自定义布局来实现这个的方法?在复杂情况下,ConstraintLayout 可能会变得非常缓慢。 - Yannick
我成功实现了“重叠”行为,而无需使用复杂的约束(在我的情况下,由于速度太慢,我无法使用它),只需使用一个具有固定高度的简单自定义布局即可。https://gist.github.com/THEAccess/b71e17d58d830ed6deaed7066a929576 - Yannick
唯一的问题是它向下扩展而不是向上。 - Yannick
我有一个完整的应用程序,你可以在这里看到 https://github.com/THEAccess/compose-keyboard-ime/blob/master/app/src/main/java/com/example/composeime/KeyboardScreen.kt 和一个 gif https://imgur.com/a/GGL2ziI ,也许你知道如何解决这个小问题。 - Yannick

3

我猜你正在寻找pressIndicatorGestureFilter修饰符...我尝试了这个方法,它对我有效...

var pressed by remember { mutableStateOf(false) }
val padding = if (pressed) 32.dp else 16.dp
Text("A", Modifier
    .pressIndicatorGestureFilter(
        onStart = {
            pressed = true
        },
        onStop = {
            pressed = false
        },
        onCancel = {
            pressed = false
        }
    )
    .background(Color.White)
    .padding(start = 16.dp, end = 16.dp, top = padding, bottom = padding)
)

谢谢您的回答。我已经使用interactionState实现了点击行为。我已经更新了我的问题,以更清楚地说明我的特定问题。 - Yannick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接