Build Software Keyboard with Jetpack Compose – IME Input Method with Jetpack Compose

Issue

Building a simple keyboard is fairly simple and straightforward in Jetpack Compose.
I built a really simple KeyRow by using this:

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 = {  })
        }
    }
}

That’s what it looks like:

2

I want to achieve this animation:

3

However, I’m currently stuck with this

![4]

Hierachy

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

My main problem is that I don’t know how to show the KeyPressedOverlay Composale (which is larger than the Key Composable) without making the parent Layout larger. As a result, I need to overflow the parent layout in some way.

Solution

Not sure if it’s the best way (probably not), but I found a solution using 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 */}
        )
    }
}

One important detail here is graphicLayer(clip = false) (which is similar to the clipChildren in View Toolkit). Then, I’m creating a modifier to each key and to the pressed key. Noticed that the modifierPressed is aligned to the center/bottom of the other modifier.
Finally the KeyboardKey is described below.

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

This is the result I got:
Keyboard effect

Edit:
Adding some more logic, I was able to get this…
enter image description here

I hope it helps this time 😉
Here’s the gist just in case…
https://gist.github.com/nglauber/4cb1573efba9024c008ea71f3320b4d8

Answered By – nglauber

Answer Checked By – Gilberto Lyons (FlutterFixes Admin)

Leave a Reply

Your email address will not be published. Required fields are marked *