TextView在斯洛伐克语文本显示不全解决方案

61 阅读2分钟

TextView 在测量文字宽度时,用的是字体里每个 glyph 的“advance”(水平步进)。
ľ(U+013E,小写 L + 抑扬符)在很多字体里为了美观,会把抑扬符的“尾巴”做成负 left‐bearing 的 overhang——也就是实际位图比 advance 再往左探出去几个像素。
Android 的 Layout/StaticLayout 在 measure 阶段 只累加 advance,不会为 overhang 追加额外宽度;到了 draw 阶段 却会把整个 bitmap 画出来,于是尾部 overhang 被父控件边缘或 TextView 自己的 padding 裁剪掉,看起来“点被吃掉”。
宽度“足够”只是 advance 足够,但 overhang 没算进去,所以仍然显示不全。

二、根治方案(4 选 1)

方案改动点优点缺点
1. 字体改造把 ľ 的 left‐bearing 调成 ≥ 0彻底,无运行时损耗需换字体/重签名
2. 字符串层面末尾加 U+00A0(NBSP)或 U+200A(thin space)0 行 Java,立即生效多一个空白字符,复制粘贴会带空格
3. View 层面给 TextView 加 2-3 dp 右 padding0 行 Java,设计师可控所有行尾部都多留白
4. 代码层面自定义 ReplacementSpan 把 ľ 的 advance 手动加宽精准,只影响 ľ需要写 30 行 Java,维护成本

线上最快落地:方案 2 或 3。
长期彻底:方案 1。

三、方案 2 代码示例(Kotlin)

val raw = "Reštaurácia Hiltonľ."
tv.text = raw.replace(Regex("ľ\b"), "ľ\u00A0")   // 只在词尾加 NBSP

四、方案 4 代码示例(Java)

public class SlovakLCaronSpan extends ReplacementSpan {
    private final float extra;   // 额外宽度,px
    public SlovakLCaronSpan(float extraPx) { this.extra = extraPx; }

    @Override
    public int getSize(@NonNull Paint paint, CharSequence text,
                       int start, int end, @Nullable FontMetricsInt fm) {
        return (int) (paint.measureText(text, start, end) + extra);
    }

    @Override
    public void draw(@NonNull Canvas canvas, CharSequence text,
                     int start, int end, float x, int top, int y,
                     int bottom, @NonNull Paint paint) {
        canvas.drawText(text, start, end, x, y, paint);
    }
}

// 使用
SpannableString sp = new SpannableString("Reštaurácia Hiltonľ.");
int i = sp.toString().indexOf('ľ');
if (i >= 0) {
    sp.setSpan(new SlovakLCaronSpan(dp2px(3)), i, i + 1,
               Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
tv.setText(sp);

五、完整调用时序图

显示不全原理.png

六、一句话总结

ľ 的尾巴是字体 overhang,Android measure 只算 advance;
要么让字体把 overhang 收回来,要么人为在尾部加空白/右 padding/ReplacementSpan 把 advance 撑大,就能把“点”完整露出来。