Stylianos Gakis
05/24/2023, 10:16 PMStylianos Gakis
05/24/2023, 10:17 PMStylianos Gakis
05/24/2023, 10:18 PMStylianos Gakis
05/24/2023, 10:19 PMColumn {
Spacer(weight(1f))
TextField()
Spacer(weight(1f))
Button()
}
Then I’d get something like this instead. Where the TextField obviously goes really high up.Stylianos Gakis
05/24/2023, 10:20 PMOdife K
05/24/2023, 10:23 PMStylianos Gakis
05/24/2023, 10:25 PMOdife K
05/24/2023, 10:39 PMOdife K
05/24/2023, 10:41 PMStylianos Gakis
05/24/2023, 10:42 PMOdife K
05/24/2023, 10:43 PMStylianos Gakis
05/24/2023, 10:43 PMStylianos Gakis
05/24/2023, 10:44 PMAnother approach would be to observe the softkeyboard state and reduce the Spacer weight when it opens.I tried something like this too, but the values I get from the ime is a value in pixels on how big the IME is. I then had to take this value and kinda normalize it between 0 and 1. And even after I did so, the TextField was first moving a bit down and then up again, like bouncing around a bit, so I scrapped that idea too.
Stylianos Gakis
05/24/2023, 10:45 PMSean Proctor
05/24/2023, 10:47 PMColumn {
Box(Modifier.weight(1f) {
TextField(Modifier.align(Alignment.Center))
}
Button()
}
Travis Griggs
05/24/2023, 10:49 PMStylianos Gakis
05/24/2023, 10:49 PMStylianos Gakis
05/24/2023, 10:50 PMSean Proctor
05/24/2023, 10:51 PMSean Proctor
05/24/2023, 10:52 PMTravis Griggs
05/24/2023, 11:04 PMAlbert Chang
05/25/2023, 2:42 AMColton Idle
05/25/2023, 4:11 AMStylianos Gakis
05/25/2023, 10:42 AMprivate const val TextFieldId = "TextFieldId"
private const val ContinueButtonId = "ContinueButtonId"
private const val BottomWindowInsetsId = "BottomWindowInsetsId"
Layout(
content = {
InputTextField(
...
modifier = Modifier.layoutId(TextFieldId),
)
ContinueButton(
...
modifier = Modifier.layoutId(ContinueButtonId),
)
Spacer(
Modifier
.layoutId(BottomWindowInsetsId)
.windowInsetsPadding(
WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom),
),
)
},
modifier = Modifier.weight(1f),
) { measurables, constraints ->
val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
val textFieldPlaceable = measurables.first { it.layoutId == TextFieldId }.measure(looseConstraints)
val buttonPlaceable = measurables.first { it.layoutId == ContinueButtonId }.measure(looseConstraints)
val insetsPlaceable =
measurables.first { it.layoutId == BottomWindowInsetsId }.measure(looseConstraints)
val maxWidth = constraints.maxWidth
val maxHeight = constraints.maxHeight
val spacingHeight = 16.dp.roundToPx() // The space between the three items
layout(maxWidth, maxHeight) {
val insetsYPosition = maxHeight - insetsPlaceable.height
insetsPlaceable.place(0, insetsYPosition)
val buttonYPosition = insetsYPosition - spacingHeight - buttonPlaceable.height
buttonPlaceable.place(0, buttonYPosition)
val textFieldYPosition = minOf(
(maxHeight / 2) - (textFieldPlaceable.height / 2),
buttonYPosition - spacingHeight - textFieldPlaceable.height,
)
textFieldPlaceable.place(0, textFieldYPosition)
}
}
The explanation in words is that it takes the available space (Below the topAppBar all the way to the bottom of the screen, since I’m going edge to edge), and places the insets at the bottom. Then a 16.dp space over it. Then the bottom attached button. Then a 16.dp space over that.
Then for the text-field, all the magic happens in this line
val textFieldYPosition = minOf(
(maxHeight / 2) - (textFieldPlaceable.height / 2),
buttonYPosition - spacingHeight - textFieldPlaceable.height,
)
It finds the center of that available screen space (Yes this isn’t the absolute center of the phone screen, but of the available space, so minus topAppBar which also includes the status bar, but this is good enough for me for now. if I wanted to I could add that TopAppBar into the Layout too and it’d be super simple to do as well).
And it also finds the position where it’d be 16dps above the botton.
And then it simply places it at the one which is higher up. This also makes it so that as the keyboard is animating up in the latest OS versions, I get it to feel like it gets attached to the button and animates up with it.
So cool, and it was actually so simple to write anyway. I was just initially scared since I didn’t know how it’d play with keyboard animations, but nah, it plays super well with it. I’ll just try to tweak it a bit so that the top app bar doesn’t get hidden like that on the small screen scenario as seen in the video
p.s. Thanks everyone for chiming in here, I really appreciate all of you 🤗Odife K
05/25/2023, 10:46 AMStylianos Gakis
05/25/2023, 11:34 AMefemoney
05/25/2023, 4:30 PMArrangement.Vertical
. Here is an arrangement that does something similar but not exactly the same.
private object SearchEmptyArrangementWith16Spacing : Arrangement.Vertical {
override val spacing = 16.dp
override fun Density.arrange(totalSize: Int, sizes: IntArray, outPositions: IntArray) {
require(sizes.size > 2)
val spacing = spacing.roundToPx()
val buttonIndex = sizes.lastIndex
val buttonHeight = sizes.last()
outPositions[buttonIndex] = totalSize - buttonHeight // Place button at the bottom
val remainingSize = totalSize - buttonHeight - spacing
var groupSize = (sizes.size - 2) * spacing // Add spacing between elements excl button
for (i in 0 until buttonIndex) { // Add sizes of elements excl button
groupSize += sizes[i]
}
if (groupSize <= remainingSize) {
var current = CenterVertically.align(groupSize, remainingSize)
for (i in 0 until buttonIndex) {
outPositions[i] = current
current += sizes[i] + spacing
}
} else {
var current = remainingSize
for (i in buttonIndex - 1 downTo 0) {
current -= sizes[i]
outPositions[i] = current
current -= spacing
}
}
}
}
It places a button (last node in the column) at the bottom and for the rest of the content, if there is enough space, they are all grouped & centered or else if there is not enough space for all of them, we arrange them bottom up (the bottom elements are the most important, so we make sure they show first)