You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Canvas绘制不同属性多行文本,如何避免位置重叠?

解决Canvas多行文本下方绘制不同样式文本的重叠问题

现有Canvas扩展函数drawMultilineText用于绘制多行文本,手动指定y坐标绘制下方文本时,会因原文本长度变化导致重叠,可通过以下两种方案解决:

方案1:让drawMultilineText返回文本总高度

修改原绘制函数,返回生成的StaticLayout总高度,以此计算下一段文本的起始y坐标:

fun Canvas.drawMultilineText(
    text: CharSequence,
    textPaint: TextPaint,
    width: Int,
    x: Float,
    y: Float,
    start: Int = 0,
    end: Int = text.length,
    alignment: Layout.Alignment = Layout.Alignment.ALIGN_NORMAL,
    spacingMult: Float = 1f,
    spacingAdd: Float = 0f,
    includePad: Boolean = true,
    ellipsizedWidth: Int = width,
    ellipsize: TextUtils.TruncateAt? = null
): Int {
    val cacheKey = "$text-$start-$end-$textPaint-$width-$alignment-" +
            "$spacingMult-$spacingAdd-$includePad-$ellipsizedWidth-$ellipsize"

    val staticLayout = StaticLayoutCache[cacheKey] ?: StaticLayout.Builder.obtain(text, start, end, textPaint, width)
        .setAlignment(alignment)
        .setLineSpacing(spacingAdd, spacingMult)
        .setIncludePad(includePad)
        .setEllipsizedWidth(ellipsizedWidth)
        .setEllipsize(ellipsize)
        .build()

    staticLayout.draw(this, x, y)
    return staticLayout.height
}

private fun StaticLayout.draw(canvas: Canvas, x: Float, y: Float) {
    canvas.withTranslation(x, y) {
        draw(this)
    }
}

private object StaticLayoutCache {
    private const val MAX_SIZE = 50
    private val cache = lruCache<String, StaticLayout>(MAX_SIZE)

    operator fun set(key: String, staticLayout: StaticLayout) {
        cache.put(key, staticLayout)
    }

    operator fun get(key: String): StaticLayout? {
        return cache[key]
    }
}

使用示例

val textPaint = TextPaint().apply {
    textSize = 10f
}
// 绘制第一段文本并获取高度
val firstTextHeight = canvas.drawMultilineText(myString, textPaint, canvas.width, 100f, 100f)

val textPaint2 = TextPaint().apply {
    textSize = 5f
}
// 第二段文本起始y = 第一段起始y + 第一段高度 + 自定义间距
val secondTextY = 100f + firstTextHeight + 10f
canvas.drawMultilineText("This is text underneath myString", textPaint2, canvas.width, 100f, secondTextY)

方案2:单独实现文本高度计算函数

若不想修改原绘制函数,可新增高度计算函数,提前获取文本高度再确定绘制位置:

fun calculateMultilineTextHeight(
    text: CharSequence,
    textPaint: TextPaint,
    width: Int,
    start: Int = 0,
    end: Int = text.length,
    alignment: Layout.Alignment = Layout.Alignment.ALIGN_NORMAL,
    spacingMult: Float = 1f,
    spacingAdd: Float = 0f,
    includePad: Boolean = true,
    ellipsizedWidth: Int = width,
    ellipsize: TextUtils.TruncateAt? = null
): Int {
    val cacheKey = "$text-$start-$end-$textPaint-$width-$alignment-" +
            "$spacingMult-$spacingAdd-$includePad-$ellipsizedWidth-$ellipsize"

    val staticLayout = StaticLayoutCache[cacheKey] ?: StaticLayout.Builder.obtain(text, start, end, textPaint, width)
        .setAlignment(alignment)
        .setLineSpacing(spacingAdd, spacingMult)
        .setIncludePad(includePad)
        .setEllipsizedWidth(ellipsizedWidth)
        .setEllipsize(ellipsize)
        .build()
    
    if (StaticLayoutCache[cacheKey] == null) {
        StaticLayoutCache[cacheKey] = staticLayout
    }
    return staticLayout.height
}

使用示例

val textPaint = TextPaint().apply {
    textSize = 10f
}
// 提前计算第一段文本高度
val firstTextHeight = calculateMultilineTextHeight(myString, textPaint, canvas.width)
// 绘制第一段文本
canvas.drawMultilineText(myString, textPaint, canvas.width, 100f, 100f)

val textPaint2 = TextPaint().apply {
    textSize = 5f
}
val secondTextY = 100f + firstTextHeight + 10f
canvas.drawMultilineText("This is text underneath myString", textPaint2, canvas.width, 100f, secondTextY)

两种方案均利用StaticLayout的高度属性,确保下一段文本自动排在前一段下方,避免内容重叠。

内容的提问来源于stack exchange,提问作者user19729496

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 19:03:31