I’ve got a composable that uses a `LinearGradient`...
# compose
b
I’ve got a composable that uses a
LinearGradient
for the background on a
BoxWithConstraints
where the Box size is
fillMaxSize()
so it may be different sizes depending on where it’s used. So far I have it rendering as seen in the attached designer screen shot, which is not quite what I want. I’m trying to get it so that the “line” between red and blue always goes from the bottom left corner to the top right corner, no matter the shape of the Box. Any idea how I can achieve that? My current code is posted as a reply.
Copy code
@Composable
fun SaddleCloth(programNumber: String, trackType: TrackType, bettingInterest: Int) {
    val saddleCloth = saddleCloths.forRunner(trackType, bettingInterest)

    val color =Brush.linearGradient(
        0.0f to Color(0xEE,0x34,0x25),
        0.5f to Color(0xF0,0x4F,0x32),
        0.5f to Color(0x00,0x59,0xA4),
        1.0f to Color(0x30,0x63,0xAA)
    )


    BoxWithConstraints(
        modifier = Modifier.fillMaxSize().background(color),
        contentAlignment = Alignment.Center
    ) {
        val fontSize = maxWidth * 0.4f

        Text(
            text = programNumber,
            textAlign = TextAlign.Center,
            fontSize = LocalDensity.current.run { fontSize.toSp() },
            fontWeight = FontWeight.Bold,
            color = saddleCloth.contentColor)
    }
}

@Preview("Square")
@Composable
fun SaddleClothPreview() {
    ThemedPreview {
        Box(modifier = Modifier.size(48.dp)) {
            SaddleCloth(programNumber = "10F", TrackType.THOROUGHBRED, 10 )
        }
    }
}

@Preview("Rectangle")
@Composable
fun SaddleClothRectPreview() {
    ThemedPreview {
        Box(modifier = Modifier.width(24.dp).height(35.dp)) {
            SaddleCloth(programNumber = "10F", TrackType.HARNESS, 10 )
        }
    }
}
z
The
linearGradient
function takes start and end offsets, those don’t work?
b
I’ve played around with those a little bit, but I don’t think I understand how they work. Also, it seems like the defaults (start = Offset.Zero, end = Offset.Infinite) would work, but clearly not.
c
The design looks like it might be easier to just use a
Canvas
instead of a gradient
This article is a good intro to the Compose
Canvas
, and has examples of drawing a line between the two corners. Should be pretty similar to draw that with a fill https://dev.to/tkuenneth/drawing-and-painting-in-jetpack-compose-1-2okl
z
Zero to infinity is always going to be a 45 degree angle because x and y axes are the same scale. I would think you’d want to pass the bottom right offset of your container to end to get the right angle
b
Thanks @Casey Brooks I’ll take a look at that.
@Zach Klippenstein (he/him) [MOD] but it’s not always a 45 degree angle. For example:
z
Ah I see. Yea canvas makes more sense then
b
But to your point, yeah, I kind of figured that I might have to get the size of the box use that to determine proper start/end offset values if I were going to try to use a gradient.
The Canvas approach might be the correct one. I was trying to use a Gradient cause it’s a Brush. In my use case, there are about 50 different numbers (the “10F” in my example) and each number has a different background. Nearly all of them are solid colors, but there are a few odd one’s like 10F which are two toned like this, and 2 that are even “striped”. By using a Brush, I can define all of them the same way (using the
SolidColor()
Brush for the solid colors, and the LinearGradient for these few outliers) … But perhaps that’s not going to work out.
c
There are Modifiers which apply draw functions to another Composable, which might be more appropriate than a full
Canvas
https://dev.to/tkuenneth/more-on-canvas-70g
b
@Casey Brooks @Zach Klippenstein (he/him) [MOD] I got it! Shapes, Paths, and
drawBehind
FTW!!
🎉 4
Need to to a bit of clean up, but here’s the basic code:
Copy code
fun trianglePath(corner1: Offset, corner2: Offset, corner3: Offset): Path {
    return Path().apply {
        moveTo(corner1.x, corner1.y)
        lineTo(corner2.x, corner2.y)
        lineTo(corner3.x, corner3.y)
        close()
    }
}

@Composable
fun SaddleCloth(programNumber: String, trackType: TrackType, bettingInterest: Int) {
    val saddleCloth = saddleCloths.forRunner(trackType, bettingInterest)

    BoxWithConstraints(
        modifier = Modifier
            .fillMaxSize()
            .drawBehind {


                val topTriangle = trianglePath(
                    Offset(0f,0f),
                    Offset(size.width, 0f),
                    Offset(0f, size.height)
                )

                val bottomTriangle = trianglePath(
                    Offset(size.width, 0f),
                    Offset(size.width, size.height),
                    Offset(0f, size.height)
                )

                drawPath(topTriangle, Color(0xEE,0x34,0x25))
                drawPath(bottomTriangle, Color(0x30,0x63,0xAA))
            },
        contentAlignment = Alignment.Center
    ) {
        val fontSize = maxWidth * 0.4f

        Text(
            text = programNumber,
            textAlign = TextAlign.Center,
            fontSize = LocalDensity.current.run { fontSize.toSp() },
            fontWeight = FontWeight.Bold,
            color = saddleCloth.contentColor)
    }
}
I had to make some of these with striped backgrounds (both horizontal and vertical versions) as well. Took me about 10 minutes, and I was able to implement them all as extensions on DrawScope. So my final composable ended up looking like this:
Copy code
@Composable
fun SaddleCloth(programNumber: String, trackType: TrackType, bettingInterest: Int) {
    val saddleCloth = saddleCloths.forRunner(trackType, bettingInterest)

    BoxWithConstraints(
        modifier = Modifier
            .fillMaxSize()
            .drawWithCache {
                onDrawBehind {
                    when (saddleCloth.type) {
                        is SaddleClothType.Solid -> drawRect(saddleCloth.type.primaryColor)
                        is SaddleClothType.Triangles -> drawTriangles(size, saddleCloth.type.primaryColor, saddleCloth.type.bottomColor)
                        is SaddleClothType.VerticalStripes -> drawVerticalStripes(size, saddleCloth.type.primaryColor, saddleCloth.type.stripeColor)
                        is SaddleClothType.HorizontalStripes -> drawHorizontalStripes(size, saddleCloth.type.primaryColor, saddleCloth.type.stripeColor)
                    }
                }
            },
        contentAlignment = Alignment.Center
    ) {
        val fontSize = maxWidth * 0.4f

        Text(
            text = programNumber,
            textAlign = TextAlign.Center,
            fontSize = LocalDensity.current.run { fontSize.toSp() },
            fontWeight = FontWeight.Bold,
            color = saddleCloth.contentColor)
    }
}
Compose never ceases to amaze me!
K 3
n
One thing to keep in mind is that the line drawn with the initial linear gradient approach will always be perpendicular to the given points in the linear gradient API. That is the colors dispersed along those points from the top left to the bottom right will have the separation of colors on the line perpendicular to that one. So if you wanted to always have the separation between the red and blue regions, you can work backwards to determine what the actual start/end points to provide to the linear gradient