Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get height of element Jetpack Compose

I want to get height of my button(or other element) in Jetpack Compose. Do you know how to get?

like image 273
Renattele Renattele Avatar asked Mar 22 '21 19:03

Renattele Renattele


People also ask

How do you get the device width in jetpack compose?

If you want to get the size in pixels: val screenDensity = configuration. densityDpi / 160f and multiply with dp, for example val screenHeight = configuration. screenHeightDp.

How do I get TextField value in jetpack compose?

To read value entered in TextField in Android Compose, declare a variable, and assign this variable the value in the TextField whenever there is a change in the value. The same process holds for reading value entered in OutlineTextField composable.

What is LazyColumn in jetpack compose?

A LazyColumn is a vertically scrolling list that only composes and lays out the currently visible items. It's similar to a Recyclerview in the classic Android View system.

What is scaffold in jetpack compose?

Scaffold provides a slot for a floating action button. You can use the floatingActionButton slot and a FloatingActionButton : Scaffold( floatingActionButton = { FloatingActionButton(onClick = { /* ...


4 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.

like image 142
user3872620 Avatar answered Oct 13 '22 17:10

user3872620


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")
            }
        }
    }
}
like image 32
F.Mysir Avatar answered Oct 13 '22 17:10

F.Mysir


You could also use BoxWithConstraints as follows:

Button(onClick = {}){

   BoxWithConstraints{
     val height = maxHeight

   }
}

But I'm not sure it fits your specific usecase.

like image 1
Mauro Banze Avatar answered Oct 13 '22 18:10

Mauro Banze


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.

https://developer.android.com/reference/kotlin/androidx/compose/ui/layout/package-summary#(androidx.compose.ui.Modifier).onSizeChanged(kotlin.Function1)

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.maxHeight 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))
    }
)
like image 1
Thracian Avatar answered Oct 13 '22 17:10

Thracian