Search code examples
androidkotlinandroid-jetpack-composeandroid-constraintlayout

Constraint Layout items overlapping: Smart wrap text does not work when two opposing items intertwine


I want to make a list item which contains two texts , one anchored to the start of parent and one to the end of parent, I want the space to be dynamically handled each time and if too long it should wrap text and break line, my problem is that I'm using this item everywhere, sometimes Text1 takes up more space than Text2 and vice versa, this makes using Row with weight not useful, I tried using constraint layout, with top, bottom and start/end constraints, but this makes the texts overlap when too long.

item using Row and weight

This is my constraint layout approach:

val offset: Dp = 8.dp
val modifier = if (onClick != null) {
    Modifier
        .fillMaxWidth()
        .background(backgroundColor)
        .height(rowHeight)
        .clickable(
            onClick = onClick
        )
} else {
    Modifier
        .fillMaxWidth(widthPercentage)
        .background(backgroundColor)
        .defaultMinSize(minHeight = rowHeight)
}

ConstraintLayout(
    modifier = modifier
) {
    val (titleRef, infoRef, disclosureRef) = createRefs()

    Text(
        text = title,
        color = titleColor,
        style = titleStyle,
        modifier = Modifier
            .constrainAs(titleRef) {
                start.linkTo(parent.start, margin = horizontalPadding)
                top.linkTo(parent.top)
                bottom.linkTo(parent.bottom)
            }
    )

    Text(
        text = infoText,
        color = infoTextColor,
        style = infoStyle,
        textAlign = TextAlign.End,
        modifier = Modifier
            .constrainAs(infoRef) {
                if (showDisclosure) {
                    end.linkTo(disclosureRef.start, margin = offset)
                } else {
                    end.linkTo(parent.end, margin = horizontalPadding)
                }
                top.linkTo(parent.top)
                bottom.linkTo(parent.bottom)
            }
    )

    if (showDisclosure) {
        Image(
            painter = painterResource(id = R.drawable.ic_disclosure_gray),
            modifier = Modifier
                .constrainAs(disclosureRef) {
                    end.linkTo(parent.end, margin = horizontalPadding - offset)
                    top.linkTo(parent.top)
                    bottom.linkTo(parent.bottom)
                }
        )
    }
}

And the row approach which does not give me flexibility, when wanting different text having the bigger weight each time:

val offset: Dp = 8.dp
val modifier = if (onClick != null) {
    Modifier
        .defaultMinSize(minHeight = 35.dp)
        .fillMaxWidth()
        .background(backgroundColor)
        .padding(vertical = verticalPadding)
        .clickable(
            onClick = onClick
        )
} else {
    Modifier
        .fillMaxWidth(widthPercentage)
        .background(backgroundColor)
        .padding(vertical = verticalPadding)
        .defaultMinSize(minHeight = rowHeight)
}

Row(
    verticalAlignment = Alignment.CenterVertically,
    modifier = modifier
) {
    Text(
        text = title,
        color = titleColor,
        style = titleStyle,
        maxLines = 2,
        modifier = Modifier
            .weight(1f)
            .padding(start = horizontalPadding)
    )

    val trailingPadding = if (showDisclosure) {
        offset
    } else {
        horizontalPadding
    }

    Text(
        text = infoText,
        color = infoTextColor,
        style = infoStyle,
        textAlign = TextAlign.End,
        modifier = Modifier
            .weight(1.5f)
            .padding(start = horizontalPadding, end = trailingPadding)
    )

    if (showDisclosure) {
        Image(
            painter = painterResource(id = R.drawable.ic_disclosure_gray),
            modifier = Modifier
                .padding(end = offset)
                // .align(Alignment.CenterVertically)
        )
    }
}

What I am expecting :

enter image description here


Solution

  • Please try to apply the weight Modifier to the first Text Composable as follows:

    @Composable
    fun MyComposable() {
        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceBetween
        ) {
            Text(
                modifier = Modifier.weight(1f),
                text = "ABC DEF"
            )
            Text(text = "1000 1000 1000 1000 1000 1000 1000 1000 1000")
        }
    }
    

    Jetpack Compose measures the unweighted children first, then it distributes the remaining space between the children that have the weight Modifier set.

    The parent will divide the horizontal space remaining after measuring unweighted child elements and distribute it according to this weight.

    Output:

    screenshot


    Note that if the second Text might fill the whole screen width, you need to specify a max width of the second Text to prevent it from filling the whole screen width and pushing the first Text off. You can try this as follows:

    val configuration = LocalConfiguration.current
    val screenWidth = configuration.screenWidthDp.dp
    
    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.SpaceBetween
    ) {
    
        Text(
            modifier = Modifier.weight(1f),
            text = "ABC DEF"
        )
    
        Text(
            modifier = Modifier.widthIn(max = screenWidth - 150.dp),  // first Text will always have 150dp space
            Text(text = "1000 1000 1000 1000 1000 1000 1000 1000 1000")
        )
    
    }