28

I am working on my first app fully designed by Compose.

I need to centralize vertically text with the Text() compose component. In traditional Android dev practice I did this over alignment property. Text() compose also has alignment property, but at this moment it has limited capacity (Alignment.Horizontal() is only allowed), despite on I noticed different alignment values within Text() when made a research in web. Similar case for Column() - it also has alignment property .wrapContentSize() and also has limitation on values that could be used here, despite on quick research in web shows it may receive CenterVertically too.

What is your way to achieve this visual effect? The full code piece is below

@ExperimentalUnitApi
@Composable
fun TripBookingContent(state: PassengerTripUiState.TripBookUiState) {
    Log.d(App.TAG, "[screen] TripBookingContent")
    val baselineGrid = dimensionResource(id = R.dimen.baseline_grid)
    val mainPadding = dimensionResource(id = R.dimen.main_margin_compact)
    var componentSpace = dimensionResource(id = R.dimen.component_space)
    Column(
        modifier = Modifier
            .wrapContentHeight()
            .fillMaxWidth()
            .padding(
                paddingValues = PaddingValues(
                    horizontal = mainPadding,
                    vertical = baselineGrid
                )
            )
    ) {
        TripViewItem(
            data = state.trip,
            {},
            modifier = Modifier.padding(vertical = baselineGrid)
        )
        Text(
            text = stringResource(id = R.string.booking_screen_driver),
            color = colorResource(id = R.color.white),
            style = TextStyle(textIndent = TextIndent(firstLine = TextUnit(16F, TextUnitType.Sp))),
            modifier = Modifier
                .height(componentSpace)
                .padding(start = baselineGrid)
                .fillMaxWidth()
                .background(color = colorResource(id = R.color.design_default_color_secondary_variant))
        )
        Log.d(App.TAG, "[state] state.driver - ${state}")
        Log.d(App.TAG, "[state] state.driver.toDriver() - ${state.driver}")
        DriverCardContent(data = state.driver)

        Text(
            text = stringResource(id = R.string.booking_screen_msg),
            color = colorResource(id = R.color.white),
            style = TextStyle(textIndent = TextIndent(firstLine = TextUnit(16F, TextUnitType.Sp))),
            textAlign = TextAlign.Center,
            modifier = Modifier
//                .height(componentSpace)
                .height(32.dp)
                .padding(start = baselineGrid)
                .fillMaxWidth()
                .background(color = colorResource(id = R.color.colorPrimaryDark))
        )
        /**
         * Disable book button at current (alfa version), for more details
         * */
        Button(
            onClick = { /* no op */ },
            modifier = Modifier
                .alpha(0F)
                .fillMaxWidth()
                .padding(baselineGrid)
                .height(dimensionResource(id = R.dimen.button_height)),
            colors = ButtonDefaults.buttonColors(
                backgroundColor = Color.Blue,
                contentColor = Color.White
            ),
        ) {
            Text(
                text = stringResource(id = R.string.booking_screen_confirm_button),
                modifier = Modifier.align(Alignment.CenterVertically),
                fontWeight = FontWeight.Bold
            )
        }
    }
}

UPDATE My final solution become this one below. I have to move padding and background to the Box() layer to achieve text in center both vertically and horizontally.

        Box(
            contentAlignment = Alignment.Center,
            modifier = Modifier
                .height(32.dp)
                .padding(start = baselineGrid)
                .fillMaxWidth()
                .background(color = colorResource(id = R.color.colorPrimaryDark))
        ) {
            Text(
                text = stringResource(id = R.string.booking_screen_msg),
                color = colorResource(id = R.color.white),
                style = TextStyle(textIndent = TextIndent(firstLine = TextUnit(16F, TextUnitType.Sp))),
                textAlign = TextAlign.Center,
/*                modifier = Modifier
//                .height(componentSpace)
                    .height(32.dp)
                    .padding(start = baselineGrid)
                    .fillMaxWidth()
                    .background(color = colorResource(id = R.color.colorPrimaryDark))*/
            )
        } 
Joe Dow
  • 583
  • 1
  • 3
  • 12

3 Answers3

33

This can be done in several ways. As mentioned in other answer you can use Modifier.wrapContentSize(), Modifier.layout which is very similar to how wrapContentSize modifier implemented , it's how size Modifers are implemented, they basically change, update or limit Constraints and measure and layout their contents.

Modifier.wrapContentSize changes minimum width and height to 0, and because of that you would be able measure content in that range instead of size Modifier or Constraints that return fixed min,max width range. such as min=200.dp, max=200.dp. If you change min range to 0 your content gets measured with it's actual content dimensions and it gets placed in any aligment.

@Preview
@Composable
private fun Test() {
    Text(
        text = "Hello world",
        modifier = Modifier
            .border(2.dp, Color.Red)
            .size(200.dp)
            .layout { measurable, constraints ->
                val placeable =
                    measurable.measure(
                        // This is how wrapContent works
                        constraints.copy(minWidth = 0, minHeight = 0)
                    )
                layout(constraints.maxWidth, constraints.maxHeight) {
                 // This is how wrapContent alignment works
                    val x = (constraints.maxWidth - placeable.width) / 2
                    val y = (constraints.maxHeight - placeable.height) / 2
                    placeable.placeRelative(x, y)
                }
            },
        textAlign = TextAlign.Center
    )
} 

or use Box() with contentAlignment = Alignment.Center. There are also CenterStart and CenterEnd options for alignment either.

   Box(
        contentAlignment = Alignment.Center,
    ) {
        Text(
            text = "Text",
            textAlign = TextAlign.Center
        )
    }
Thracian
  • 43,021
  • 16
  • 133
  • 222
  • Thank you for sharing. Is the Box the only option for this? Text(), Column() are misfit for this task, but what about Card() or some other component? – Joe Dow Jan 14 '22 at 09:24
  • 2
    If you click `Card`with cmd/crtl + click you will see that Card source is a `Surface` and Surface itself is also a Box with `contentAlignment: Alignment = Alignment.TopStart`. So it's possible with Box or you can create your own composable with `Layout`and use place according to your needs. I have tutorial about layouts and many other things if you are interested. – Thracian Jan 14 '22 at 09:32
  • 2
    With custom `Layout` and `Modifier` you can have your own alignment scheme like in this [image](https://github.com/SmartToolFactory/Jetpack-Compose-Tutorials/blob/master/screenshots/tutorial3_3_1.png) – Thracian Jan 14 '22 at 09:34
28

It is possible to center text vertically IF your Text widget has a height modifier for example - Text(text="example", modifier=Modifier.height(100.dp) You can use the wrapContentHeight modifier to align text vertically for example

Text(text = "Example",modifier = Modifier.width(100.dp).height(100.dp).background(color = Color.Cyan).wrapContentHeight(align = Alignment.CenterVertically),color = Color.Black,fontSize = 42.sp,textAlign = TextAlign.Right)
Neha Soni
  • 3,935
  • 2
  • 10
  • 32
  • my text doesn't have a fixed height value but has minLines and it doesn't help https://stackoverflow.com/questions/75738733/text-is-not-centered-vertically-with-textalign-textalign-center-only-horizont – user924 Mar 14 '23 at 22:05
  • empty `wrapContentSize` works like a charm – user128440 Aug 15 '23 at 15:03
5

Alignment modifiers depend on the parent, which is how it supposed to be, in a vertical list (column) vertical alignment is handled by the parent (which is great that compose can enforce this)

Soo in your case you probably need a different hierarchy. For ex: Box instead of Column. Alternatively you can play around with the size of the Text, make it 200dp in height for example and use textAlign, to center it's text.

For reference:

  • Box(contentAlignment = Alignment.Center) -> Specifies alignment of children
  • Text(text = "example", modifier = Modifier.wrapContentSize().align()) -> Specifies alignment of Text composable under parent
  • Text(text = "example", textAlign = TextAlign.Center) -> specifies alignment of text, inside composable, using this with wrapContentSize() won't have any effect, since the Composable has the same size as it's content text
Róbert Nagy
  • 6,720
  • 26
  • 45