ITmob-Ly
发布于 2023-04-18 / 292 阅读
0

在 Jetpack Compose 中使用 DrawScope.drawText API 将文本绘制到 Canvas 上

1. 介绍:

Jetpack Compose 1.3.0 之前没有 DrawScope.drawText API 不能直接在 Jetpack Compose 画布/Canvas 上绘制文本,必须通过 Android 原生 Canvas 的 API nativeCanvas.drawText 来绘制文本。

从 Jetpack Compose 1.3.0 引入了新的 API DrawScope.drawText() 在 Canvas 上绘制文本。在本文中我们将探讨新的 DrawScope.drawText() API。这里是新 API 的官方介绍:https://android-developers.googleblog.com/2022/10/whats-new-in-jetpack-compose.html

现在(androidx.compose.ui:ui-text:1.4.1)此 API 仍处于实验状态,将来可能会发生变化。

如下是 Jetpack Compose 1.3.0 之前使用 nativeCanvas 怎样绘制文本:

@Composable
fun NativeDrawText() {
    val paint = Paint().asFrameworkPaint().apply {
        this.color = Color.White.toArgb()
        this.textSize = 58f
    }

    Canvas(
        modifier = Modifier
            .fillMaxWidth()
            .height(50.dp),
        onDraw = {
            drawRect(color = Color.Black)
            drawIntoCanvas {
                it.nativeCanvas.drawText("Hello nativeCanvas,\n Hello itmob.cn", 30f, 100f, paint)
            }
        },
    )
}

draw Text using Native Canvas

2. DrawScope.drawText()

drawText 函数有 4 个重载,两个重载的参数是 TextMeasure,其他两个重载函数的参数的是 TextLayoutResult ,使用这两种方式测量文本来绘制它。

2.1 使用 TextMeasurer 绘制文本

TextMeasure 也是一个新的实验性 API 负责测量整个文本,以便可以绘制它。使用 rememberTextMeasurer() 可以创建 TextMeasurer 实例,并且可以设置带有样式的注释字符串作为参数,详情可以查看官方文档:https://developer.android.com/reference/kotlin/androidx/compose/ui/text/TextMeasurer

@ExperimentalTextApi
fun DrawScope.drawText(
    textMeasurer: TextMeasurer,
    text: AnnotatedString,
    topLeft: Offset = Offset.Zero,
    style: TextStyle = TextStyle.Default,
    overflow: TextOverflow = TextOverflow.Clip,
    softWrap: Boolean = true,
    maxLines: Int = Int.MAX_VALUE,
    placeholders: List<AnnotatedString.Range<Placeholder>> = emptyList(),
    size: Size = Size.Unspecified,
    blendMode: BlendMode = DrawScope.DefaultBlendMode
) {
...
}
@ExperimentalTextApi
fun DrawScope.drawText(
    textMeasurer: TextMeasurer,
    text: String,
    topLeft: Offset = Offset.Zero,
    style: TextStyle = TextStyle.Default,
    overflow: TextOverflow = TextOverflow.Clip,
    softWrap: Boolean = true,
    maxLines: Int = Int.MAX_VALUE,
    size: Size = Size.Unspecified,
    blendMode: BlendMode = DrawScope.DefaultBlendMode
) {
...
}

上面是 DrawScope.drawText 源码中使用 TextMeasurer 参数的函数,主要区别是它们的 text 参数,一个是 AnnotatedString 一个是 String

关于 AnnotatedString 可以参见其他文章:Jetpack Compose 中拥有样式的文本Jetpack Compose 中的文字怎样处理 Jetpack Compose 中文本的长按选择和点击事件(如何在 Text 中添加超链接?

  1. 如下是使用 TextMeasurerdrawText 绘制文本的例子(使用 AnnotatedString 参数):

    @OptIn(ExperimentalTextApi::class)
    @Composable
    fun DrawTextWithTextMeasure() {
        val textMeasure = rememberTextMeasurer()
        val text = buildAnnotatedString {
            withStyle(
                style = SpanStyle(
                    color = Color.White,
                    fontSize = 22.sp,
                    fontWeight = FontWeight.Bold,
                ),
            ) {
                append("Hello, ")
            }
            withStyle(
                style = SpanStyle(
                    color = Color.Cyan,
                    fontSize = 22.sp,
                    fontStyle = FontStyle.Italic,
                    fontWeight = FontWeight.Bold,
                ),
            ) {
                append("itmob.cn")
            }
        }
        Canvas(
            modifier = Modifier
                .fillMaxWidth()
                .height(60.dp),
            onDraw = {
                drawRect(color = Color.Black)
                drawText(
                    textMeasurer = textMeasure,
                    text = text,
                    topLeft = Offset(16.dp.toPx(), 16.dp.toPx()),
                )
            },
        )
    }
    

    draw Text using Text Measurer with AnnotatedString parameter

  2. 如下是使用 TextMeasurerdrawText 绘制文本的例子(使用 String 参数):

    这个重载函数只支持一种文本样式,除了 text 参数是字符串类型而不是带注释的字符串,其他参数与上一个 drawText 重载函数相同。

    @OptIn(ExperimentalTextApi::class)
    @Composable
    fun DrawTextWithTextMeasure() {
        val textMeasure = rememberTextMeasurer()
        Canvas(
            modifier = Modifier
                .fillMaxWidth()
                .height(60.dp),
            onDraw = {
                drawRect(color = Color.Black)
                drawText(
                    textMeasurer = textMeasure,
                    text = "Hello, itmob.cn",
                    style = TextStyle(
                        color = Color.White,
                        fontSize = 22.sp,
                    ),
                    topLeft = Offset(20.dp.toPx(), 20.dp.toPx()),
                )
            },
        )
    }
    

    draw Text using Text Measurer with String parameter

2.2 使用 TextLayoutResult 绘制文本

@ExperimentalTextApi
fun DrawScope.drawText(
    textLayoutResult: TextLayoutResult,
    color: Color = Color.Unspecified,
    topLeft: Offset = Offset.Zero,
    alpha: Float = Float.NaN,
    shadow: Shadow? = null,
    textDecoration: TextDecoration? = null,
    drawStyle: DrawStyle? = null,
    blendMode: BlendMode = DrawScope.DefaultBlendMode
) {
...
}
@ExperimentalTextApi
fun DrawScope.drawText(
    textLayoutResult: TextLayoutResult,
    brush: Brush,
    topLeft: Offset = Offset.Zero,
    alpha: Float = Float.NaN,
    shadow: Shadow? = null,
    textDecoration: TextDecoration? = null,
    drawStyle: DrawStyle? = null,
    blendMode: BlendMode = DrawScope.DefaultBlendMode
) {
...
}

上面是 DrawScope.drawText 源码中使用 TextLayoutResult 参数的函数,在画布上绘制现有的文本布局。主要区别是一个是使用颜色/Color 给文本上色,一个是使用笔刷/Brush 给文本上色。

下面的例子是使用创建的 TextLayoutResult 来绘制文本:

@OptIn(ExperimentalTextApi::class)
@Composable
fun DrawTextWithTextLayoutResult() {
    val textMeasure = rememberTextMeasurer()
    var textLayoutResult by remember { mutableStateOf<TextLayoutResult?>(null) }
    Canvas(
        modifier = Modifier
            .fillMaxWidth()
            .height(60.dp)
            .layout { measurable, constraints ->
                val placeable = measurable.measure(constraints)
                textLayoutResult = textMeasure.measure(
                    text = buildAnnotatedString {
                        withStyle(
                            style = SpanStyle(
                                color = Color.White,
                                fontSize = 22.sp,
                                fontWeight = FontWeight.Bold,
                            ),
                        ) {
                            append("Hello, ")
                        }
                        withStyle(
                            style = SpanStyle(
                                color = Color.Cyan,
                                fontSize = 22.sp,
                                fontStyle = FontStyle.Italic,
                                fontWeight = FontWeight.Bold,
                            ),
                        ) {
                            append("itmob.cn")
                        }
                    },
                )
                layout(placeable.width, placeable.height) {
                    placeable.place(0, 0)
                }
            },
    ) {
        drawRect(color = Color.Black)
        textLayoutResult?.let {
            drawText(
                textLayoutResult = it,
                color = Color.White,
                textDecoration = TextDecoration.Underline,
                topLeft = Offset(16.dp.toPx(), 16.dp.toPx()),
            )
        }
    }
}

draw Text using TextLayoutResult

通过 TextMeasure.measure() 创建 TextLayoutResult。这里我们使用 LayoutModifier 中创建TextLayoutResult

另一个使用 Brush 作为参数为文字上色的重载函数这里不做过多介绍,跟上例类似。

drawText 的 color 参数只在 AnnotatedString 中未指定文字颜色时生效。

创建 TextLayoutResult 时只能使用 AnnotatedString 不能像使用 TextMeasurer 的重载函数那样简单的使用 String 类型字符串(至少写本文时 Jetpack Compose 1.4.1 仍然不可以)。

其他

更多关于 Jetpack Compose 文本的介绍:

在 Jetpack Compose 中怎样实现印章样式(弧形)的文字效果?

Jetpack Compose 中 DrawStyle 详解 - (线段/笔画/轮廓的绘制样式)

Jetpack Compose 中如何自定义文本笔画的描边效果

Jetpack Compose 中怎样隐藏或禁用 TextField(文本字段)的光标、文本选择手柄和文本工具栏?

怎样处理 Jetpack Compose 中文本的长按选择和点击事件(如何在 Text 中添加超链接?)

Jetpack Compose TextField VisualTransformation 视觉转换详解-高亮显示URL-格式化显示银行卡号码

怎样处理 Jetpack Compose 中文本的长按选择和点击事件(如何在 Text 中添加超链接?)

Jetpack Compose 怎样实现跑马灯效果(Marquee)

在 Jetpack Compose 中使用 DrawScope.drawText API 将文本绘制到 Canvas 上