He||o, using Kotlin and Compose, I created a Column in a Box, I use android:windowSoftInputMode="adjustResize"
in the manifest file
The Column's Modifier uses .align(Alignment.BottomCenter)
and .offset(y = (-110).dp)
, it looks like this:
The Column's Modifier also uses .imePadding()
, it looks like this when the keyboard is shown:
Is there some way to use some offset so when the keyboard appears, the Column goes lower?
So there is no space between the Login button and the keyboard
Modifier.imePadding()
is just a padding that changes based on keyboard height. If there are 100.dp space between a button it still be a 100.dp when keyboard is opened.
As mentioned in question you need to have a layout
or offset
Modifier that do not change position in parent, in OP's case there are no siblings but offset does not change position of siblings as padding does so it works unlike imePadding
because when there is no space padding does not work as expected but it works as intended, because it's a padding modifier.
First you need to set
WindowCompat.setDecorFitsSystemWindows(window, false)
to be able to get keyboard size with
val density = LocalDensity.current
val imeBottom = WindowInsets.ime.getBottom(density)
Create an offset
Modifier that changes with keyboard height
fun Modifier.imeOffset(contentBottom: Int = 0) = composed {
val density = LocalDensity.current
val imeBottom = WindowInsets.ime.getBottom(density)
val navBarBottom = WindowInsets.navigationBars.getBottom(density)
Modifier.offset {
IntOffset(
x =0,
y = (-imeBottom + contentBottom + navBarBottom)
.coerceAtMost(0)
)
}
}
contentBottom is the bottom position of Composable that this Modifier will be assigned to which is calculated with
.onPlaced {
val parentHeight = it.parentLayoutCoordinates?.size?.height ?: 0
bottom = (parentHeight - it.boundsInParent().bottom.toInt())
}
Demo
@Composable
private fun ImeOffsetSample() {
var bottom by remember {
mutableIntStateOf(0)
}
Box(
modifier = Modifier.fillMaxSize()
.systemBarsPadding()
.border(2.dp, Color.Red),
contentAlignment = Alignment.Center
) {
Column(
modifier = Modifier
.offset(y = (150).dp)
.onPlaced {
if (bottom == 0) {
val parentHeight = it.parentLayoutCoordinates?.size?.height ?: 0
bottom = (parentHeight - it.boundsInParent().bottom.toInt())
.coerceAtLeast(0)
}
}
.imeOffset(bottom)
.border(2.dp, Color.Black)
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
var email by remember {
mutableStateOf("")
}
var password by remember {
mutableStateOf("")
}
TextField(
value = email,
onValueChange = { email = it },
label = { Text("email") }
)
TextField(
value = password,
onValueChange = { password = it },
label = { Text("password") }
)
Button(
onClick = {}
) {
Text("Login")
}
}
}
}
Another sample with a Column that contains other Composbles above the child to be assigned with imeOffset. In this case padding would shrink last Compoasble(TextField and Button in Column) because the amount of space is Column height - imePadding which is not enough to fit last composable.
Button and TextField can be moved above keyboard even if whole Composable is filled with other sibling Composables.
@Composable
private fun ImeOffsetSample() {
var bottom by remember {
mutableIntStateOf(0)
}
Column(
modifier = Modifier.fillMaxSize().systemBarsPadding()
) {
TopAppBar(
title = {
Text("TopAppbar")
}
)
Column(
modifier = Modifier.padding(horizontal = 16.dp)
) {
Spacer(Modifier.height(16.dp))
repeat(4) {
SomeComposable()
Spacer(Modifier.height(16.dp))
}
repeat(7) {
Text("Some Text")
Spacer(Modifier.height(16.dp))
}
Spacer(Modifier.weight(1f))
Column(
modifier = Modifier
.onPlaced {
if (bottom == 0) {
val parentHeight = it.parentLayoutCoordinates?.size?.height ?: 0
bottom = (parentHeight - it.boundsInParent().bottom.toInt())
.coerceAtLeast(0)
println("Parent height: $parentHeight, bottom: $bottom")
}
}
.imeOffset(bottom)
.fillMaxWidth()
.background(Color.White)
) {
SomeComposable()
Spacer(Modifier.height(16.dp))
Button(
modifier = Modifier
.fillMaxWidth()
.background(Color.White)
.padding(bottom = 16.dp),
onClick = {}
) {
Text("Login")
}
}
}
}
}
@Composable
fun SomeComposable() {
var text by remember {
mutableStateOf("")
}
TextField(
modifier = Modifier.fillMaxWidth(),
value = text,
onValueChange = { text = it },
label = { Text("text") }
)
}