如何在 Jetpack Compose 中向图像视图添加对角丝带?

Bar*_*t P 4 android android-jetpack-compose

根据设计,我们的图像视图应该在视图顶部添加一个标签。此标签应包含文本,例如“50%”,表示图像中的商品已折扣 50%。

如何在 Jetpack Compose 中的现有图像视图之上绘制这样的标签/功能区?

编辑:使用的图像是来自 URL 的图像,当前正在使用 Coil 中的“AsyncImage”加载,如果这很重要的话

提前致谢! 图像视图示例

Thr*_*ian 13

解决这个问题需要简单的数学、带有旋转功能的 Drawscope、用于测量文本宽度和高度的 TextMeasurer。如果您希望将这些作为修饰符,请组合修饰符。构建一个具有纯色背景的另一个具有微光效果的。您可以在应用这些修饰符之前检查 Painter.state 是否成功。

结果

在此输入图像描述

修改器实现

fun Modifier.drawDiagonalLabel(
    text: String,
    color: Color,
    style: TextStyle = TextStyle(
        fontSize = 18.sp,
        fontWeight = FontWeight.SemiBold,
        color = Color.White
    ),
    labelTextRatio: Float = 7f
) = composed(
    factory = {

        val textMeasurer = rememberTextMeasurer()
        val textLayoutResult: TextLayoutResult = remember {
            textMeasurer.measure(text = AnnotatedString(text), style = style)
        }


        Modifier
            .clipToBounds()
            .drawWithContent {
                val canvasWidth = size.width
                val canvasHeight = size.height

                val textSize = textLayoutResult.size
                val textWidth = textSize.width
                val textHeight = textSize.height

                val rectWidth = textWidth * labelTextRatio
                val rectHeight = textHeight * 1.1f

                val rect = Rect(
                    offset = Offset(canvasWidth - rectWidth, 0f),
                    size = Size(rectWidth, rectHeight)
                )

                val sqrt = sqrt(rectWidth / 2f)
                val translatePos = sqrt * sqrt

                drawContent()
                withTransform(
                    {
                        rotate(
                            degrees = 45f,
                            pivot = Offset(
                                canvasWidth - rectWidth / 2,
                                translatePos
                            )
                        )
                    }
                ) {
                    drawRect(
                        color = color,
                        topLeft = rect.topLeft,
                        size = rect.size
                    )
                    drawText(
                        textMeasurer = textMeasurer,
                        text = text,
                        style = style,
                        topLeft = Offset(
                            rect.left + (rectWidth - textWidth) / 2f,
                            rect.top + (rect.bottom - textHeight) / 2f
                        )
                    )
                }

            }
    }
)


fun Modifier.drawDiagonalShimmerLabel(
    text: String,
    color: Color,
    style: TextStyle = TextStyle(
        fontSize = 18.sp,
        fontWeight = FontWeight.SemiBold,
        color = Color.White
    ),
    labelTextRatio: Float = 7f,
) = composed(
    factory = {

        val textMeasurer = rememberTextMeasurer()
        val textLayoutResult: TextLayoutResult = remember {
            textMeasurer.measure(text = AnnotatedString(text), style = style)
        }

        val transition = rememberInfiniteTransition()

        val progress by transition.animateFloat(
            initialValue = 0f,
            targetValue = 1f,
            animationSpec = infiniteRepeatable(
                animation = tween(3000, easing = LinearEasing),
                repeatMode = RepeatMode.Reverse
            )
        )

        Modifier
            .clipToBounds()
            .drawWithContent {
                val canvasWidth = size.width
                val canvasHeight = size.height

                val textSize = textLayoutResult.size
                val textWidth = textSize.width
                val textHeight = textSize.height

                val rectWidth = textWidth * labelTextRatio
                val rectHeight = textHeight * 1.1f

                val rect = Rect(
                    offset = Offset(canvasWidth - rectWidth, 0f),
                    size = Size(rectWidth, rectHeight)
                )

                val sqrt = sqrt(rectWidth / 2f)
                val translatePos = sqrt * sqrt

                val brush = Brush.linearGradient(
                    colors = listOf(
                        color,
                        style.color,
                        color,
                    ),
                    start = Offset(progress * canvasWidth, progress * canvasHeight),
                    end = Offset(
                        x = progress * canvasWidth + rectHeight,
                        y = progress * canvasHeight + rectHeight
                    ),
                )

                drawContent()
                withTransform(
                    {
                        rotate(
                            degrees = 45f,
                            pivot = Offset(
                                canvasWidth - rectWidth / 2,
                                translatePos
                            )
                        )
                    }
                ) {
                    drawRect(
                        brush = brush,
                        topLeft = rect.topLeft,
                        size = rect.size
                    )
                    drawText(
                        textMeasurer = textMeasurer,
                        text = text,
                        style = style,
                        topLeft = Offset(
                            rect.left + (rectWidth - textWidth) / 2f,
                            rect.top + (rect.bottom - textHeight) / 2f
                        )
                    )
                }

            }
    }
)
Run Code Online (Sandbox Code Playgroud)

用法

Column(
    modifier = Modifier
        .background(backgroundColor)
        .fillMaxSize()
        .padding(20.dp)
) {
    val painter1 = rememberAsyncImagePainter(
        model = ImageRequest.Builder(LocalContext.current)
            .data("https://www.techtoyreviews.com/wp-content/uploads/2020/09/5152094_Cover_PS5.jpg")
            .size(coil.size.Size.ORIGINAL) // Set the target size to load the image at.
            .build()
    )

    Image(
        modifier = Modifier
            .fillMaxWidth()
            .aspectRatio(4 / 3f)
            .then(
                if (painter1.state is AsyncImagePainter.State.Success) {
                    Modifier.drawDiagonalLabel(
                        text = "50%",
                        color = Color.Red
                    )
                } else Modifier
            ),
        painter = painter1,
        contentScale = ContentScale.FillBounds,
        contentDescription = null
    )

    Spacer(modifier = Modifier.height(10.dp))

    val painter2 = rememberAsyncImagePainter(
        model = ImageRequest.Builder(LocalContext.current)
            .data("https://i02.appmifile.com/images/2019/06/03/03ab1861-42fe-4137-b7df-2840d9d3a7f5.png")
            .size(coil.size.Size.ORIGINAL) // Set the target size to load the image at.
            .build()
    )

    Image(
        modifier = Modifier
            .fillMaxWidth()
            .aspectRatio(4 / 3f)
            .then(
                if (painter2.state is AsyncImagePainter.State.Success) {
                    Modifier.drawDiagonalShimmerLabel(
                        text = "40% OFF",
                        color = Color(0xff4CAF50),
                        labelTextRatio = 5f
                    )
                } else Modifier
            ),
        painter = painter2,
        contentScale = ContentScale.FillBounds,
        contentDescription = null
    )
}
Run Code Online (Sandbox Code Playgroud)

没有任何状态绘制到图像

在此输入图像描述

@Composable
private fun RibbonSample() {

    val text = "50%"
    val textMeasurer = rememberTextMeasurer()
    val style = TextStyle(
        fontSize = 18.sp,
        fontWeight = FontWeight.SemiBold,
        color = Color.White
    )
    val textLayoutResult: TextLayoutResult = remember {
        textMeasurer.measure(text = AnnotatedString(text), style = style)
    }

    Box(
        modifier = Modifier
            .clipToBounds()
            .drawWithContent {

                val canvasWidth = size.width

                val textSize = textLayoutResult.size
                val textWidth = textSize.width
                val textHeight = textSize.height

                val rectWidth = textWidth * 7f
                val rectHeight = textHeight * 1.1f

                val rect = Rect(
                    offset = Offset(canvasWidth - rectWidth, 0f),
                    size = Size(rectWidth, rectHeight)
                )

                val translatePos = sqrt(rectWidth / 2f) * sqrt(rectWidth / 2f)

                drawContent()
                withTransform(
                    {
                        rotate(
                            degrees = 45f,
                            pivot = Offset(
                                canvasWidth - rectWidth / 2,
                                translatePos
                            )
                        )
                    }
                ) {
                    drawRect(
                        Color.Red,
                        topLeft = rect.topLeft,
                        size = rect.size
                    )
                    drawText(
                        textMeasurer = textMeasurer,
                        text = text,
                        style = style,
                        topLeft = Offset(
                            rect.left + (rectWidth - textWidth) / 2f,
                            rect.top + (rect.bottom - textHeight) / 2f
                        )
                    )
                }

            }
    ) {
        Image(
            modifier = Modifier
                .fillMaxWidth()
                .aspectRatio(4 / 3f),
            painter = painterResource(id = R.drawable.landscape1),
            contentScale = ContentScale.FillBounds,
            contentDescription = null
        )
    }
}
Run Code Online (Sandbox Code Playgroud)