You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Jetpack Compose中使用Canvas绘制文本时字体粗细设置异常的问题

Jetpack Compose中使用Canvas绘制文本时字体粗细设置异常的问题

我完全理解你遇到的困扰——Compose的Text组件能正常显示指定粗细的字体,但用原生Canvas的drawText把文字合并到图片上时,字体粗细就变得混乱了。不管是自定义可变字体还是默认字体都踩了这个坑,我之前也遇到过类似问题,咱们一步步来解决它。

问题根源分析

你的问题主要出在原生Typeface构建与可变字体/静态字体的适配逻辑不匹配,以及单位转换和字体参数设置的细节遗漏

  1. 对于可变字体(比如你用的sn_pro_variablefont_wght.ttf),它通过字体变化轴(如wght权重轴)来控制粗细,原生Typeface.BuildersetWeight方法在部分设备或场景下无法正确触发可变轴参数,导致粗细失效。
  2. 对于静态字体,它们的粗体/常规是独立的字体文件,Typeface.Builder.setWeight无法直接映射到静态字体的粗体样式,需要用原生Typeface的样式枚举来指定。
  3. 你在原生Paint中直接写textSize = 24f,但Compose的24.sp是缩放无关像素(sp),需要转换为原生像素值,虽然这不是粗细问题的直接原因,但会导致文字大小和Compose预览不一致。

针对性解决方案

方案1:正确处理可变字体的粗细设置

可变字体的粗细由wght轴控制,直接在Paint中设置字体变化参数比通过Typeface.Builder更可靠:

步骤1:加载原始可变字体Typeface

跳过setWeight的设置,直接加载完整的可变字体:

// 加载可变字体,不提前设置权重
val variableTypeface = Typeface.createFromAsset(context.assets, "font/sn_pro_variablefont_wght.ttf")

步骤2:配置Paint时显式设置可变轴参数

Paintapply块中,通过fontVariationSettings指定权重轴,同时修正textSize的单位转换:

val scaledDensity = context.resources.displayMetrics.scaledDensity
val paint = android.graphics.Paint().apply {
    isAntiAlias = true
    color = Color.Black.toArgb()
    // 把Compose的24.sp转换为原生像素值
    textSize = 24 * scaledDensity
    textAlign = android.graphics.Paint.Align.LEFT
    typeface = variableTypeface
    
    // 直接设置可变字体的权重轴(比如ExtraBold对应的权重是800)
    fontVariationSettings = "wght=${textEffect.fontWeight.weight}"
    
    // 如果是斜体,可变字体通常有ital轴,直接添加参数;若字体不支持ital轴,再启用原生斜体
    if (textEffect.fontStyle == FontStyle.Italic) {
        fontVariationSettings += ", ital=1"
        isItalic = true
    }
}

方案2:静态字体的粗细适配

如果使用的是静态字体(比如默认的FontFamily.Default),需要根据FontWeight选择对应的原生Typeface样式:

// 根据Compose的FontWeight和FontStyle生成对应的原生Typeface
val staticTypeface = when (textEffect.fontWeight) {
    FontWeight.Thin -> Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)
    FontWeight.Light -> Typeface.create(Typeface.DEFAULT_LIGHT, Typeface.NORMAL)
    FontWeight.Bold, FontWeight.ExtraBold -> Typeface.create(Typeface.DEFAULT_BOLD, Typeface.NORMAL)
    // 其他权重可以按需扩展
    else -> Typeface.DEFAULT
}.let { baseTypeface ->
    if (textEffect.fontStyle == FontStyle.Italic) {
        Typeface.create(baseTypeface, Typeface.ITALIC)
    } else {
        baseTypeface
    }
}

然后在Paint中直接使用这个staticTypeface即可。

方案3:统一Typeface构建逻辑(兼容可变/静态字体)

如果你的App同时支持可变和静态字体,可以写一个通用的Typeface构建方法:

fun createTypeface(
    context: Context,
    fontPath: String,
    fontWeight: FontWeight,
    fontStyle: FontStyle
): Typeface {
    return try {
        // 先尝试按可变字体处理
        val variableTypeface = Typeface.createFromAsset(context.assets, fontPath)
        // 检查是否是可变字体(API 26+支持)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && variableTypeface.isVariable) {
            variableTypeface
        } else {
            // 静态字体用Typeface.Builder处理
            Typeface.Builder(context.assets, fontPath)
                .setWeight(fontWeight.weight)
                .setItalic(fontStyle == FontStyle.Italic)
                .build() ?: Typeface.DEFAULT
        }
    } catch (e: Exception) {
        // 加载失败 fallback 到默认字体
        Typeface.DEFAULT
    }
}

然后在Paint中,对于可变字体补充设置fontVariationSettings

val typeface = createTypeface(context, "font/sn_pro_variablefont_wght.ttf", textEffect.fontWeight, textEffect.fontStyle)
val paint = android.graphics.Paint().apply {
    // ...其他配置
    this.typeface = typeface
    // 如果是可变字体,设置权重轴
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && typeface.isVariable) {
        fontVariationSettings = "wght=${textEffect.fontWeight.weight}"
        if (textEffect.fontStyle == FontStyle.Italic) {
            fontVariationSettings += ", ital=1"
        }
    }
}

验证与测试

完成上述修改后,重新测试两个场景:

  1. 拖动Compose的Text组件时,预览的文字粗细和合并到图片后的文字粗细是否一致。
  2. 切换不同的FontWeight(如Regular、Bold、ExtraBold),确认每种粗细都能正确渲染。

这样应该就能解决你遇到的字体粗细混乱问题了,如果还有疑问,可以再检查一下字体文件是否完整支持对应的变化轴,或者设备的Android版本是否支持可变字体的相关API(API 26+支持完整的可变字体特性)。

火山引擎 最新活动