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

API26及以下版本为SpannableStringBuilder设置TextAppearanceSpan时字体不生效

问题原因

API26及更低版本的系统原生TextAppearanceSpan不支持解析Style中定义的自定义字体资源属性,只有API27及以上版本才会自动加载fontFamily指定的自定义字体,所以会出现低版本样式不生效的问题。

解决方案

方案1:使用AndroidX兼容实现(最简)

直接替换系统原生TextAppearanceSpan为AndroidX Core库提供的兼容版即可,兼容实现已经处理了低版本自定义字体的加载逻辑,无需修改其他代码:

import androidx.core.text.style.TextAppearanceSpan

fun getMenuSpannable(context: Context, title: String): SpannableStringBuilder {
    return SpannableStringBuilder(title).apply {
        setSpan(TextAppearanceSpan(context, R.style.TextAppearance_Theme_Subtitle1), 0, title.length, 0)
    }
}

如果项目还没依赖Core KTX,在build.gradle中补充依赖即可:

implementation "androidx.core:core-ktx:1.12.0"

原有Style配置无需修改,保持原样即可:

<style name="TextAppearance.Theme.Subtitle1" parent="TextAppearance.MaterialComponents.Subtitle1">
    <item name="typeface">MacanRegular</item>
    <item name="fontFamily">@font/macan_regular</item>
    <item name="android:fontFamily">@font/macan_regular</item>
    <item name="android:textSize">16sp</item>
    <item name="lineSpacingMultiplier">1.4</item>
    <item name="android:letterSpacing">-0.04</item>
</style>

方案2:自定义Span实现(无需AndroidX)

如果项目未接入AndroidX,可以手动拆分属性设置,自定义兼容字体的Span即可:

import android.content.Context
import android.graphics.Paint
import android.graphics.Typeface
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.style.AbsoluteSizeSpan
import android.text.style.MetricAffectingSpan
import android.util.TypedValue
import androidx.core.content.res.ResourcesCompat

fun getMenuSpannable(context: Context, title: String): SpannableStringBuilder {
    return SpannableStringBuilder(title).apply {
        // 加载自定义字体,ResourcesCompat兼容到API16
        val customTypeface = ResourcesCompat.getFont(context, R.font.macan_regular)
        setSpan(TypefaceSpanCompat(customTypeface), 0, title.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
        // 手动设置字号
        setSpan(AbsoluteSizeSpan(16.spToPx(context), true), 0, title.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
        // Android 5.0及以上设置字间距
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
            setSpan(android.text.style.LetterSpacingSpan(-0.04f), 0, title.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
        }
    }
}

private fun Int.spToPx(context: Context): Int = TypedValue.applyDimension(
    TypedValue.COMPLEX_UNIT_SP, this.toFloat(), context.resources.displayMetrics
).toInt()

class TypefaceSpanCompat(private val targetTypeface: Typeface?) : MetricAffectingSpan() {
    override fun updateDrawState(ds: android.text.TextPaint) {
        applyTypeface(ds)
    }

    override fun updateMeasureState(paint: android.text.TextPaint) {
        applyTypeface(paint)
    }

    private fun applyTypeface(paint: Paint) {
        targetTypeface?.let { typeface ->
            val oldStyle = paint.typeface?.style ?: 0
            val fakeStyle = oldStyle and typeface.style.inv()
            if (fakeStyle and Typeface.BOLD != 0) paint.isFakeBoldText = true
            if (fakeStyle and Typeface.ITALIC != 0) paint.textSkewX = -0.25f
            paint.typeface = typeface
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 20:57:02