How to make Text wrap like swiftUI. I have a bunc...
# compose
d
How to make Text wrap like swiftUI. I have a bunch of test cases in the thread. Basically it seems like the rules if you have 2 Text next to each other. If one of the Text is short the other one can fill the available space. Then if both are long then both take 50%. đŸ§”
HStack(spacing: 0){
Text("Left Text").background(Color.red)
Text("Right Text").background(Color.blue)
}
HStack(spacing: 0){
Text("Left Text is longer that to fit screen width for a single line so right text will not be visible").background(Color.red)
Text("Right Text").background(Color.blue)
}
HStack(spacing: 0){
Text("Left Text").background(Color.red)
Text("Right Text is longer that to fit screen width so left text will not be visible").background(Color.blue)
}
HStack(spacing: 0){
Text("Left Text is as long as Right Text so right one will not be visible lorem ipsum lorem ipsum").background(Color.red)
Text("Right Text is as long as Left Text so right one will not be visible lorem ipsum lorem ipsum").background(Color.blue)
}
message has been deleted
k
For those who don’t know anything about swiftUI or its handling of texts, it would be useful to say: 1. If the screenshot is of Swift or Compose 2. If of Compose, what is it that you’re looking to achieve - no matter if it’s the “Swift” look or not
d
Screenshot is swiftUI. Here is the compose version with the same content.
message has been deleted
Copy code
Row {
    Text("Left Text", modifier = Modifier.background(color = Color.Red))
    Text("Right Text", modifier = Modifier.background(color = Color.Blue))
}
//Broken
Row {
    Text(
        "Left Text is longer that to fit screen width for a single line so right Text will not be visible",
        modifier = Modifier.background(color = Color.Red)
    )
    Text("Right Text", modifier = Modifier.background(color = Color.Blue))
}
Row {
    Text("Left Text", modifier = Modifier.background(color = Color.Red))
    Text(
        "Right Text is longer that to fit screen width so left text will not be visible",
        modifier = Modifier.background(color = Color.Blue)
    )
}
//Broken
Row {
    Text(
        "Left Text is as long as Right Text so right one will not be visible lorem ipsum lorem ipsum",
        modifier = Modifier.background(color = Color.Red)
    )
    Text(
        "Right Text is as long as Left Text so right one will not be visible lorem ipsum lorem ipsum",
        modifier = Modifier.background(color = Color.Blue)
    )
}
Main thing what I want is the wrapping and width rules being the same as iOS
Seems like if the left text is long it cuts off the right. So I need some means to measure and then adjust the widths to basically do it the same way as iOS. I described how iOS works in the original post
d
Could you do a custom layout that measures both left and right and checks if width of any of them is smaller than 50% of maxWidth? Then place accordingly. Thinking about it you could probably do it with the Paragraph API too given the only case that you want one side smaller is if the text is on 1 line.
r
Copy code
@Composable
fun MyCoolRowText(leftText: String, rightText: String) {
    SubcomposeLayout { constraints ->
        // Measure left with text content
        val leftTextWidth = subcompose(TextSlot.Left) {
            Text(text = leftText)
        }.first().measure(constraints).width

        // Measure right with text content
        val rightTextWidth = subcompose(TextSlot.Right) {
            Text(text = rightText)
        }.first().measure(constraints).width

        // get screen width
        val maxWidth = constraints.maxWidth
        val maxHalfWidth = maxWidth / 2

        // Prepare boolean to check if any of them is higher or lower than width
        val bothExceedHalfWidth = leftTextWidth > maxHalfWidth && rightTextWidth > maxHalfWidth
        val bothAreLowerThanHalfWidth = leftTextWidth < maxHalfWidth && rightTextWidth < maxHalfWidth
        val anyIsHigherThanHalfWidth = leftTextWidth > maxHalfWidth || rightTextWidth > maxHalfWidth

        val (updatedLeftWidth, updatedRightWidth) = when {
            // Return half of the width for both texts
            bothExceedHalfWidth -> maxHalfWidth to maxHalfWidth
            
            // Return as it is
            bothAreLowerThanHalfWidth -> leftTextWidth to rightTextWidth
            
            // Get the width for the higher one and return the lower one as it is
            anyIsHigherThanHalfWidth -> {
                if(leftTextWidth > maxHalfWidth) {
                    val newLeftWidth = maxWidth - rightTextWidth
                    newLeftWidth to rightTextWidth
                } else {
                    val newRightWidth = maxWidth - leftTextWidth
                    leftTextWidth to newRightWidth
                }
            }
            // as it is
            else -> leftTextWidth to rightTextWidth
        }

        // Set left and right text widths and get the whole placeable
        val placeable = subcompose(TextSlot.Content) {
            Row(verticalAlignment = Alignment.CenterVertically) {
                Text(
                    text = leftText,
                    Modifier
                        .width(updatedLeftWidth.toDp())
                        .background(Color.Red)
                )
                Text(
                    text = rightText,
                    Modifier
                        .width(updatedRightWidth.toDp())
                        .background(Color.Blue)
                )
            }
        }.first().measure(constraints)

        // Set width and height for the whole component
        layout(placeable.width, placeable.height) {
            // Place the content
            placeable.place(0, 0)
        }
    }
}

enum class TextSlot {
    Left, Right, Content
}
I'm pretty sure it can be improved, at least the logic part on how it's verified* but it's 12am and I'm a bit sleepy, left some comments there so you can see how it's done and feel free to play with it!
z
Another time where subcompose layout is not only completely unnecessary but very wasteful. This is what intrinsics are for. Here’s an implementation that supports an arbitrary number of children of any type, not just text.
r
Thank you for sharing! I'll take a look I haven't seen any implementation like that before 😉
d
@Zach Klippenstein (he/him) [MOD] thanks that is awesome. Do you think its worth a feature request to support something like the above in compose directly. Its pretty nice having the feature of knowing that you can have random texts or views and know they wont overlap. I can submit one.
z
It can’t hurt to file, but it’s too late to change the default behavior of
Row
.
d
Yeah sure but its pretty nice. Appreciate the work you put into that.
If anyone finds this solution and wants it to be supported directly in compose + 1 here https://issuetracker.google.com/issues/258764025
I have a question. Is there a way to make the custom Layout you have @Zach Klippenstein (he/him) [MOD] to basically be an actual Row. For example I would like to use MarxistRow but I would like it to work closer like row so the context I pass in would be RowScope.() -> Unit. I need that as sometimes when using this I want to support weight and other things that might be row specific. When I start digging into that I run into a bunch of internal things I cant use
For example Row uses RowScopeInstance.content()
z
Sure, you can just fork Row and then add in this behavior
d
Created a GIST of what this looks like in Row. Kind of painful because you have to copy everything but this seems to work. Ill update my example if I find any issues before going to prod https://gist.github.com/DavidCorrado/be8788eb05d3b4243e4dff9ee39617b7
306 Views