1

If I have items that are wider than the containing Column, how can I achieve that all items are left aligned and clipped only at their right side? Here is an example:

@Composable
fun RandomTriangle(modifier: Modifier = Modifier) {
    val width = rand.nextInt(20, 500).dp
    Box(modifier
        .height(30.dp)
        .requiredWidth(width)
        .drawBehind {
            val path = Path().apply {
                moveTo(0f, 0f)
                lineTo(size.width, size.height / 2)
                lineTo(0f, size.height)
                close()
            }
            drawPath(path, Brush.horizontalGradient(listOf(Color.Green, Color.Blue)))
        })
}

@Preview(widthDp = 100)
@Composable
fun ListTestPreview() {
    Column(Modifier.fillMaxWidth()) {
        List(5) { RandomTriangle(Modifier.padding(4.dp)) }
    }
}

This yields the following result:

Preview

As you can see, the second, fourth and fifth triangles (from top to bottom) are oversize. They are centered in the parent Column and clipped left and right, but I would like them to be left-aligned and clipped on the right side only.

I already tried clipToBounds() on the Column, and .align(Alignment.Start) on the contained item; both do not help.

FrankM
  • 1,007
  • 6
  • 15

2 Answers2

1

The issue you have is about how layouts work in Jetpack Compose when you force Constraints that are bigger than parent your Composable is placed at its (parentWidth-childWidth)/2. You can check this answer for more details. Modifier.requiredWidth/Height() section explains this issue.

For instance

Column(
    Modifier
        .fillMaxSize()
        .padding(20.dp)) {

    Column(
        modifier = Modifier
            .size(100.dp)
            .border(2.dp, Color.Green)
    ) {
        Box(
            modifier = Modifier
                .requiredWidth(140.dp)
                .height(20.dp)
                .background(Color.Red)
        )
    }
}

Result

enter image description here

Child with Red background with 140.dp width is placed at -20.dp inside parent. What you with that size is exactly this as you can bright green section of triangles are also not visible.

You should set start point as top of triangle with

moveTo(size.width, size.height / 2)

then draw line to bottom right of triangle with width either at 0 or less than 0 if you create a random with that is greater than Canvas width

Result

enter image description here

You can clip Composable to remove drawing outside Canvas only to draw what's inside canvas bounds.

@Composable
fun RandomTriangle(modifier: Modifier = Modifier) {
    val width = Random.nextInt(50, 220).dp

    Box(
        modifier
            .padding(start = 100.dp, top = 100.dp)
            .border(2.dp, Color.Red)
            .height(30.dp)
            .width(200.dp)
            .drawWithCache {

                val widthPx = width.toPx()

                val path = Path().apply {
                    if (widthPx > size.width) {
                        moveTo(size.width, size.height / 2)
                        lineTo(size.width - widthPx, size.height.coerceAtMost(widthPx))
                        lineTo(size.width - widthPx, 0f)
                    } else {
                        moveTo(0f, 0f)
                        lineTo(widthPx, size.height / 2)
                        lineTo(0f, size.height)
                    }

                    close()
                }
                onDrawBehind {
                    drawPath(path, Brush.horizontalGradient(listOf(Color.Green, Color.Blue)))

                }
            }
    )
}
Thracian
  • 43,021
  • 16
  • 133
  • 222
1

In addition to Thracian's answer - the simpliest way to 'extend' bounds in one direction is to use graphicsLayer modifier with scale.

@Composable
fun RandomTriangle(modifier: Modifier = Modifier) {        
    val scale = (10..300).random().toFloat()/100f // or whatever         
    Canvas(modifier = modifier
        .height(30.dp)
        .fillMaxWidth()
        .graphicsLayer(scaleX = scale,
            transformOrigin = TransformOrigin(pivotFractionX = 0f,
                pivotFractionY = 0f)))
    {
        val path = Path().apply {
            moveTo(0f, 0f)
            lineTo(size.width, size.height / 2)
            lineTo(0f, size.height)
            close()
        }
        drawPath(path, Brush.horizontalGradient(listOf(Color.Green, Color.Blue)))
    }
}

enter image description here

bylazy
  • 1,055
  • 1
  • 5
  • 16