Android字体ttf或otf类型呈现随机符号/字母

4

我有一个自定义的View,在其中绘制一些文本。我使用各种免费的otf/ttf字体文件,这些文件都在assets文件夹中。

public class ProjectView extends View {
    private final Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    private void init(Context context) {
        textPaint.setStyle(Paint.Style.FILL);
        textPaint.setAntiAlias(true);

        typeface = Typeface.createFromAsset(context.getAssets(), "fonts" + File.separator + fontFileName);
        textPaint.setTypeface(typeface);
    }
}

除了一个问题,一切似乎都正常:我绘制的字会随机混乱,也就是说字母会被随机替换成其他字母或符号。
以下是示例:
正确的单词在左图中,但有时会像右图那样绘制。再次调用invalidate()使所有内容正确呈现,可以解决这个问题。
这种效果在ListView中更为明显,因为我在每次项点击时调用notifyDatasetChanged()导致重绘更加频繁。在适配器中,我使用如下方式:
@Override
    public View getView(int position, View convertView, ViewGroup parent) {

        View view = convertView;
        ViewHolder holder;
        if (convertView == null) {
            view = inflater.inflate(R.layout.list_item_fonts, null);

            holder = new ViewHolder();
            holder.txtFont = (TextView) view.findViewById(R.id.txtFont);
            view.setTag(holder);
        } else {
            holder = (ViewHolder) view.getTag();
        }

        //tried this two but no success
        holder.txtFont.setPaintFlags(holder.txtFont.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG | Paint.DEV_KERN_TEXT_FLAG);
        holder.txtFont.getPaint().setSubpixelText(true);

        holder.txtFont.setTextSize(TypedValue.COMPLEX_UNIT_SP, fonts.get(position).getSize());

        holder.txtFont.setTypeface(Typeface.createFromAsset(context.getAssets(), "fonts" + File.separator + font.getFileName()));
}

说实话,我不知道是什么原因导致了这个问题,也不知道该如何防止它。如果有帮助,请提供!


这在我使用的所有字体中随机发生。TTF文件来自Google Fonts。 - Alin
我用这个字体进行了测试:链接 - Xaver Kapeller
你确定这是一个字体问题?也许问题在于你想要显示的“字符串”。 - Xaver Kapeller
尝试使用我上面链接的字体,看看是否解决了问题。 - Xaver Kapeller
只是为了确认一下,这个问题只出现在您的自定义视图中,还是也出现在标准小部件中? - matiash
显示剩余7条评论
2个回答

3
每次调用getView时都会创建字体。这很低效,可能会在加载和解析字体文件时出现竞争。
相反,在活动中拥有所有已加载字体的映射,并仅加载每个字体一次。
如果许多活动和视图使用相同的字体,甚至可以在Application类中管理字体。

虽然这是一个好建议,但我真的不明白它如何与问题相关。Typeface.createFromAsset() 不是同步的吗?而且这些调用都来自 UI 线程。为什么会创建竞争条件呢? - matiash
为每种字体创建字体类型似乎可以使这种情况发生得更少,几乎看不到它再次出现,因此肯定是一种改进。问题是,拥有所有这些100个字体类型对象会使用太多内存吗?用户可能永远不会滚动整个列表。事实上,我正在getView上创建字体。 - Alin
@Alin 在一个类似的应用程序中(大约有50种字体),当加载所有字体时,我没有看到显著的内存使用。如果字体文件很小,它所占用的内存也应该很小。您还可以在第一次使用时仅缓存实际使用的字体。 - yoah
@matiash 加载字体文件需要读取文件、解析文件并为其创建内存数据结构。我猜测其中一些工作是在另一个线程中完成的,可能是本地线程。不确定是否完全正确,因为我没有查看 Android 代码。 - yoah
@yoah 这样怎么样:我在 getView 中创建 Typeface,但同时也将其存储在缓存中,这样下次加载该项时,我只需从缓存中获取 Typeface? - Alin
运行得很好,我现在就这样留着。感谢你的回答。 - Alin

0
也许这可以帮助到某些人。我遇到了同样的问题,但是我使用了 lru 缓存来解决它。
import android.content.Context;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.text.TextPaint;
import android.text.style.MetricAffectingSpan;
import android.util.LruCache;

public class TypefaceSpan extends MetricAffectingSpan {
    private static LruCache<String, Typeface> sTypefaceCache = new LruCache<>(12);
    private Typeface mTypeface;

    public TypefaceSpan(Context context, String typefaceName) {
        mTypeface = sTypefaceCache.get(typefaceName);

        if (mTypeface == null) {
            mTypeface = Typeface.createFromAsset(context.getApplicationContext().getAssets(),
                    String.format("fonts/%s", typefaceName));
            sTypefaceCache.put(typefaceName, mTypeface);
        }
    }

    @Override
    public void updateMeasureState(TextPaint p) {
        p.setTypeface(mTypeface);
        p.setFlags(p.getFlags() | Paint.SUBPIXEL_TEXT_FLAG);
    }

    @Override
    public void updateDrawState(TextPaint tp) {
        tp.setTypeface(mTypeface);
        tp.setFlags(tp.getFlags() | Paint.SUBPIXEL_TEXT_FLAG);
    }
}

例子:

private TypefaceSpan typefaceSpan;

构造函数:

typefaceSpan = new TypefaceSpan(context, "name_of_font.otf");

getView:

typefaceSpan.updateDrawState(holder.title.getPaint());

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接