I want to get height of my button(or other element) in Jetpack Compose. Do you know how to get?
-
6Use [`Modifier.onSizeChanged()`](https://developer.android.com/reference/kotlin/androidx/compose/ui/layout/package-summary#onsizechanged). – CommonsWare Mar 22 '21 at 20:20
-
Do you need to use the size of one component to affect the size of another component? Or just to get the the height? – Gabriele Mariotti Mar 22 '21 at 22:41
-
@GabrieleMariotti, to affect the size of another component. CommonsWare, Thanks! – Renattele Renattele Mar 23 '21 at 10:00
-
2Use Layout in this case – Gabriele Mariotti Mar 23 '21 at 10:04
-
Best simplest Answer from @CommonsWare – Marine Droit Jan 20 '23 at 08:53
5 Answers
If you want to get the height of your button after composition, then you could use: onGloballyPositionedModifier.
It returns a LayoutCoordinates object, which contains the size of your button.
Example of using onGloballyPositioned
Modifier:
@Composable
fun OnGloballyPositionedExample() {
// Get local density from composable
val localDensity = LocalDensity.current
// Create element height in pixel state
var columnHeightPx by remember {
mutableStateOf(0f)
}
// Create element height in dp state
var columnHeightDp by remember {
mutableStateOf(0.dp)
}
Column(
modifier = Modifier
.onGloballyPositioned { coordinates ->
// Set column height using the LayoutCoordinates
columnHeightPx = coordinates.size.height.toFloat()
columnHeightDp = with(localDensity) { coordinates.size.height.toDp() }
}
) {
Text(text = "Column height in pixel: $columnHeightPx")
Text(text = "Column height in dp: $columnHeightDp")
}
}

- 2,281
- 1
- 7
- 16

- 1,036
- 10
- 12
-
10Note: onGloballyPositioned returns size in px so I converted it to dp before using(like setting paddings, heights, etc). – Renattele Renattele Mar 23 '21 at 14:05
-
-
6
-
1
-
If you are doing some UI operation like resizing or drawing you will experience a jump or flash on screen. Because using `Modifier.onSizeChanged` or `Modifier.onGloballyPositioned` requires you to have `MutableState` which triggers a recomposition when you set inside these function that can be noticeable in some cases. If that's the case either use `BoxWithConstraints`, and in some conditions max height constraint is not equal to actual height then you need to use a SubcomposeLayout – Thracian Sep 06 '22 at 13:27
A complete solution would be as follows:
@Composable
fun GetHeightCompose() {
// get local density from composable
val localDensity = LocalDensity.current
var heightIs by remember {
mutableStateOf(0.dp)
}
Box(modifier = Modifier.fillMaxSize()) {
// Important column should not be inside a Surface in order to be measured correctly
Column(
Modifier
.onGloballyPositioned { coordinates ->
heightIs = with(localDensity) { coordinates.size.height.toDp() }
}) {
Text(text = "If you want to know the height of this column with text and button in Dp it is: $heightIs")
Button(onClick = { /*TODO*/ }) {
Text(text = "Random Button")
}
}
}
}

- 2,838
- 28
- 39
Using Modifier.onSizeChanged{}
or Modifier.globallyPositioned{}
might cause infinite recompositions if you are not careful as in OPs question when size of one Composable effects another.
Using the onSizeChanged size value in a MutableState to update layout causes the new size value to be read and the layout to be recomposed in the succeeding frame, resulting in a one frame lag.
You can use onSizeChanged to affect drawing operations. Use Layout or SubcomposeLayout to enable the size of one component to affect the size of another.
Even though it's ok to draw if the change in frames is noticeable by user it won't look good
For instance
Column {
var sizeInDp by remember { mutableStateOf(DpSize.Zero) }
val density = LocalDensity.current
Box(modifier = Modifier
.onSizeChanged {
sizeInDp = density.run {
DpSize(
it.width.toDp(),
it.height.toDp()
)
}
}
.size(200.dp)
.background(Color.Red))
Text(
"Hello World",
modifier = Modifier
.background(Color.White)
.size(sizeInDp)
)
}
Background of Text moves from initial background that cover its bounds to 200.dp size on next recomposition. If you are doing something that changes any UI drawing from one dimension to another it might look as a flash or glitch.
First alternative for getting height of an element without recomposition is using BoxWithConstraints
BoxWithConstraints
' BoxScope
contains maxHeight
in dp and constraints.maxHeigh
t in Int.
However BoxWithConstraints returns constraints not exact size under some conditions like using Modifier.fillMaxHeight, not having any size modifier or parent having vertical scroll returns incorrect values
You can check this answer out about dimensions returned from BoxWithConstraints, Constraints section shows what you will get using BoxWithConstraints.
verticalScroll returns Constraints.Infinity for height.
Reliable way for getting exact size is using a SubcomposeLayout
How to get exact size without recomposition?
**
* SubcomposeLayout that [SubcomposeMeasureScope.subcompose]s [mainContent]
* and gets total size of [mainContent] and passes this size to [dependentContent].
* This layout passes exact size of content unlike
* BoxWithConstraints which returns [Constraints] that doesn't match Composable dimensions under
* some circumstances
*
* @param placeMainContent when set to true places main content. Set this flag to false
* when dimensions of content is required for inside [mainContent]. Just measure it then pass
* its dimensions to any child composable
*
* @param mainContent Composable is used for calculating size and pass it
* to Composables that depend on it
*
* @param dependentContent Composable requires dimensions of [mainContent] to set its size.
* One example for this is overlay over Composable that should match [mainContent] size.
*
*/
@Composable
fun DimensionSubcomposeLayout(
modifier: Modifier = Modifier,
placeMainContent: Boolean = true,
mainContent: @Composable () -> Unit,
dependentContent: @Composable (Size) -> Unit
) {
SubcomposeLayout(
modifier = modifier
) { constraints: Constraints ->
// Subcompose(compose only a section) main content and get Placeable
val mainPlaceables: List<Placeable> = subcompose(SlotsEnum.Main, mainContent)
.map {
it.measure(constraints.copy(minWidth = 0, minHeight = 0))
}
// Get max width and height of main component
var maxWidth = 0
var maxHeight = 0
mainPlaceables.forEach { placeable: Placeable ->
maxWidth += placeable.width
maxHeight = placeable.height
}
val dependentPlaceables: List<Placeable> = subcompose(SlotsEnum.Dependent) {
dependentContent(Size(maxWidth.toFloat(), maxHeight.toFloat()))
}
.map { measurable: Measurable ->
measurable.measure(constraints)
}
layout(maxWidth, maxHeight) {
if (placeMainContent) {
mainPlaceables.forEach { placeable: Placeable ->
placeable.placeRelative(0, 0)
}
}
dependentPlaceables.forEach { placeable: Placeable ->
placeable.placeRelative(0, 0)
}
}
}
}
enum class SlotsEnum { Main, Dependent }
Usage
val content = @Composable {
Box(
modifier = Modifier
.size(200.dp)
.background(Color.Red)
)
}
val density = LocalDensity.current
DimensionSubcomposeLayout(
mainContent = { content() },
dependentContent = { size: Size ->
content()
val dpSize = density.run {size.toDpSize() }
Box(Modifier.size(dpSize).border(3.dp, Color.Green))
},
placeMainContent = false
)
or
DimensionSubcomposeLayout(
mainContent = { content() },
dependentContent = { size: Size ->
val dpSize = density.run {size.toDpSize() }
Box(Modifier.size(dpSize).border(3.dp, Color.Green))
}
)

- 43,021
- 16
- 133
- 222
You could also use BoxWithConstraints
as follows:
Button(onClick = {}){
BoxWithConstraints{
val height = maxHeight
}
}
But I'm not sure it fits your specific usecase.

- 1,898
- 15
- 11
I managed to implement a workaround to reduce (not completely removes) the number of recomposition when using Modifier.onSizeChanged
by making the composable that reads from it now stateless. Not the best approach available but it gets the job done.
@Composable
fun TextToMeasure(
currentHeight: Dp,
onHeightChanged: (Dp) -> Unit,
modifier: Modifier = Modifier,
) {
val density = LocalDensity.current
Text(
text = "Hello Android",
modifier = modifier
.onSizeChanged { size ->
val newHeight = with(density) { size.height.toDp }
if (newHeight != currentHeight) {
onHeightChanged(newHeight)
}
},
)
}
@Composable
fun MainScreen() {
val height = remember { mutableStateOf(0.dp) }
TextToMeasure(
currentHeight = height.value,
onHeightChanged = { height.value = it },
)
}

- 546
- 1
- 6
- 9