Is there a way to center a component relative to i...
# compose
d
Is there a way to center a component relative to its parent, but prevent it from colliding with its neighbors? For example, I have three components: Red, SomeText, Blue. And I want SomeText to go after Red and before Blue, so as not to draw over them, but at the same time SomeText has to be centered in its parent. The only way I can think of is to dinamically calculate start-end paddings for SomeText as maximum width of Red and Blue. Is there something already built-in I could use, perhaps?
s
What happens if Red or Blue evaluates to be big enough so that text isn’t allowed to be centered?
d
Great question, I guess I would have to set maximum width for Red and Blue. But if they are small, I would want my SomeText to have all the available space, so I can't use this maximum as its paddings
s
Yeah and what happens if SomeText is so big that it takes full width? Do Red and Blue hide, or should they also have a minimum?
d
No, SomeText can't move Red and Blue, the text just gets ellipsized
f
You will probably need a custom layout which places the text in the middle first and then measures red and blue box with the remaining space
s
Yeah if it gets ellipsized, it means you want a fixed max width percentage the centered text can take right?
d
I guess there's a guarantee that Red and Blue will be of reasonable width, i.e. leave enough space for Text, and Text has to occupy all the available space between Red and Blue
f
I think that
Text has to occupy all the available space between Red and Blue
and
SomeText has to be centered in its parent
are contradictory statements. If there really is a guarantee that red and blue will leave enough space for SomeText to be centered, then what is the problem? Just put everything in Box and align red to start, blue to end and text to center. If there is no such guarantee then create a custom layout with a behavior you want. It shouldn't be too difficult.
s
Got curious and I wrote smth real quick
Copy code
@Composable
fun CustomLayout(
    red: @Composable RowScope.() -> Unit,
    centeredText: @Composable (textAlignment: TextAlign) -> Unit,
    blue: @Composable RowScope.() -> Unit,
    maxWidthPercentageCenteredTextShouldTake: Float = 0.5f,
) {
    Layout(
        content = {
            Row(Modifier.layoutId("red").border(1.dp, Color.Red)) {
                red()
            }
            Row(Modifier.layoutId("blue").border(1.dp, Color.Blue), horizontalArrangement = Arrangement.End) {
                blue()
            }
            Box(Modifier.layoutId("text").border(1.dp, Color.Magenta)) {
                centeredText(TextAlign.Center)
            }
        },
    ) { measurables, constraints ->
        val maxWidthCenteredTextShouldTake =
            (constraints.maxWidth * maxWidthPercentageCenteredTextShouldTake).roundToInt()
        val centeredTextConstraints = constraints.copy(
            minWidth = 0,
            maxWidth = maxWidthCenteredTextShouldTake,
        )
        val centerAlignedTextPlaceable = measurables.first { it.layoutId == "text" }.measure(centeredTextConstraints)

        val redAndBlueWidth = (constraints.maxWidth - centerAlignedTextPlaceable.width) / 2
            .coerceAtLeast(0)
        val redAndBlueConstraints = constraints.copy(minWidth = 0, maxWidth = redAndBlueWidth)
        val redPlaceable = measurables.first { it.layoutId == "red" }.measure(redAndBlueConstraints)
        val bluePlaceable = measurables.first { it.layoutId == "blue" }.measure(redAndBlueConstraints)

        val layoutWidth = constraints.maxWidth
        val layoutHeight = listOf(centerAlignedTextPlaceable, redPlaceable, bluePlaceable)
            .maxOf { it.height }
            .coerceAtMost(constraints.maxHeight)

        layout(layoutWidth, layoutHeight) {
            redPlaceable.place(0, 0)
            centerAlignedTextPlaceable.place(
                (layoutWidth / 2) - (centerAlignedTextPlaceable.width / 2),
                (layoutHeight / 2) - (centerAlignedTextPlaceable.height / 2),
            )
            bluePlaceable.place(layoutWidth - bluePlaceable.width, 0)
        }
    }
}
Can probably take this and adjust according to your needs.
A call site like this
Copy code
@Composable
fun BlueRedTextCentered() {
    Column {
        CustomLayout(
            red = {
                IconButton({}) {
                    Icon(Icons.Filled.ArrowBack, contentDescription = "Localized description")
                }
            },
            centeredText = {
                Text("Centered text....")
            },
            blue = {
                IconButton({}) {
                    Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
                }
                IconButton({}) {
                    Icon(Icons.Filled.Email, contentDescription = "Localized description")
                }
            },
        )
        Divider(Modifier.height(2.dp))
        CustomLayout(
            red = {
                IconButton({}) {
                    Icon(Icons.Filled.ArrowBack, contentDescription = "Localized description")
                }
            },
            centeredText = {
                Text("Centered text....".repeat(5))
            },
            blue = {
                IconButton({}) {
                    Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
                }
                IconButton({}) {
                    Icon(Icons.Filled.Email, contentDescription = "Localized description")
                }
            },
        )
        Divider(Modifier.height(2.dp))
        CustomLayout(
            red = {
                IconButton({}) {
                    Icon(Icons.Filled.ArrowBack, contentDescription = "Localized description")
                }
            },
            centeredText = {
                Text("Centered text....".repeat(8))
            },
            blue = {
                IconButton({}) {
                    Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
                }
                IconButton({}) {
                    Icon(Icons.Filled.Email, contentDescription = "Localized description")
                }
                IconButton({}) {
                    Icon(Icons.Filled.Phone, contentDescription = "Localized description")
                }
                IconButton({}) {
                    Icon(Icons.Filled.Check, contentDescription = "Localized description")
                }
            },
        )
    }
}
Results in pics below. Not perfect by any means but yeah
d
Wow, that's so great! Thank you very much, it helped a lot!
l
We encountered this and ended up writing a custom layout too 👍