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
相关产品推荐
相关产品推荐

