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

如何在Jetpack Compose中现有的图像视图上绘制这样的标签/丝带?

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

Thanks in advance! Example of the image view

推荐答案

解决这一问题需要简单的数学计算,带旋转的Drawcope,测量文本宽度和高度的TextMeasurer.和合成修饰语,如果你想把它们作为修饰语.构建了一个具有坚实的背景,另一个具有微光效果.在应用这些修饰符之前,可以判断painter.State是否为成功.

结果

enter image description here

修饰符实现

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 textLayout结果: TextLayout结果 = remember {
            textMeasurer.measure(text = AnnotatedString(text), style = style)
        }


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

                val textSize = textLayout结果.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 textLayout结果: TextLayout结果 = 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 = textLayout结果.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
                        )
                    )
                }

            }
    }
)

用法

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
    )
}

没有任何状态绘制到图像

enter image description here

@Composable
private fun RibbonSample() {

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

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

                val canvasWidth = size.width

                val textSize = textLayout结果.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
        )
    }
}

Android相关问答推荐

将Android Studio插件复制到离线网络

无法在Jetpack Compose中显示Admob原生广告

房间@嵌入式VS一对一关系

DocumentFile.canWrite()、DocumentFile.Existes()-使用本地内置手机存储(而不是云)时性能较差(占用太多CPU时间)

如果我的圆形图像的顶部居中于卡片内部,我如何在其下方画一条弧线?

如何在Jetpack Compose中向SearchBar添加边框

Jetpack Compose 使用 SavedStateHandle 发送返回结果不适用于 ViewModel 中注入的 SavedStateHandle

Jetpack Compose 中的用户在线指示器

如何在 Jetpack Compose 中处理水平滚动手势和变换手势

当父布局的背景为浅色时,Android ProgressBar 背景 colored颜色 变为灰色

Android AGP 8 + Gradle 8 + Kotlin 1.8 导致 Kapt 出错

Android Studio XML 文本在 ConstraintLayout 中不居中

如何在 Android Jetpack compose 中为列表初始填充设置动画

react 从输入中找到路径'lib/arm64-v8a/libfbjni.so'的本机2个文件

Material Design 网站的新设计已启动,但我找不到实现选项卡,该选项卡在哪里?

如何在行/列/卡片 compose 中添加左边框

为什么我不能直接记住 mutableStateOf 可组合函数?

在jetpack compose中将图像添加到脚手架顶部栏

优化 Room 数据库迁移

在jetpack compose中看不到圆角