Search code examples
androidtextandroid-jetpack-composevertical-alignment

How to make text centered vertically in Android compose?


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))*/
            )
        } 

Solution

  • 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
            )
        }