Android 自定义字体,字替换为图片

不久前开发的版本中有这样一个需求,根据服务器返回的分数,显示分数,各位可能觉得这有什么呢?那么请看下图。





没错,这就是我们的需求。


思路一,

添加字体库,通过设置 TextView,在初始化的时候设置我们的字体。例如:

TextView textView = (TextView) findViewById(R.id.custom);
// 将字体文件保存在assets/fonts/目录下
Typeface typeFace =  Typeface.createFromAsset(getAssets(),"fonts/your_font_name.ttf");
// 应用字体
textView.setTypeface(typeFace);

思路二,

通过 自定义 View,根据分数算出每个字的位置,通过canvas 画出。通过如下方法:

canvas.drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint) 

解决方案:

按照上面的思路,我直接去找设计妹子,说能给我做成字体库吗?然后我就被抓成土豆丝了。回来我自己了解了一下做字体库,通过了解,为这几个字(0,1,2,3,4,5,6,7,8,9,分,@#%作为一个字),做字体库显然成本比较大。

思路一不行,就思路二吧。思路麻烦在于,要分别处理,两个字(0分),三个字(60分),四个字(100分)和@#%,四种情况。

分清了几种状态,就可以开始着手开发了,就在这时,这时,我想到,这个,这个,不就和年初看的 emoji 表情是一个意思吗,只不过换成我自己的表情而已。

年初看的 emoji 表情,是 github 上的一个项目,其主要原理是,通过判断字符来替换相应的字符码为 emoji 图片。

这是自定义的 TextView

public class FaceRateTextView extends TextView {

    public FaceRateTextView(Context context) {
        super(context);
        init();
    }


    public FaceRateTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public FaceRateTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        if (!TextUtils.isEmpty(getText())) {
            setText(getText());
        }
    }

    @Override
    public void setText(CharSequence text, BufferType type) {
        if (!TextUtils.isEmpty(text)) {
            SpannableStringBuilder builder = new SpannableStringBuilder(text);
            FontsHandler.addFonts(getContext(), builder, (int) getTextSize(), (int) getTextSize());
            text = builder;
        }
        super.setText(text, type);
    }
}

FontsHandler,是通过正则判断字符串当中有没有要替换的字符,并且替换为 DynamicDrawableSpan。

public class FontsHandler {

    private static final Map<String, Integer> mFonts = new HashMap<>(12);

    static {
        mFonts.put("分", R.drawable.ic_face_rate);
        mFonts.put("0", R.drawable.ic_face_rate_0);
        mFonts.put("1", R.drawable.ic_face_rate_1);
        mFonts.put("2", R.drawable.ic_face_rate_2);
        mFonts.put("3", R.drawable.ic_face_rate_3);
        mFonts.put("4", R.drawable.ic_face_rate_4);
        mFonts.put("5", R.drawable.ic_face_rate_5);
        mFonts.put("6", R.drawable.ic_face_rate_6);
        mFonts.put("7", R.drawable.ic_face_rate_7);
        mFonts.put("8", R.drawable.ic_face_rate_8);
        mFonts.put("9", R.drawable.ic_face_rate_9);
        mFonts.put("balala", R.drawable.ic_face_rate_balala);
    }

    public static boolean addFonts(Context context, Spannable spannable, int size, int textSize) {
        boolean hasChanges = false;
        for (Map.Entry<String, Integer> entry : mFonts.entrySet()) {
            String key = entry.getKey();
            Matcher matcher = Pattern.compile(Pattern.quote(key)).matcher(spannable);
            while (matcher.find()) {
                boolean set = true;
                for (FontsSpan span : spannable.getSpans(matcher.start(),
                        matcher.end(), FontsSpan.class))
                    if (spannable.getSpanStart(span) >= matcher.start()
                            && spannable.getSpanEnd(span) <= matcher.end())
                        spannable.removeSpan(span);
                    else {
                        set = false;
                        break;
                    }
                if (set) {
                    hasChanges = true;
                    spannable.setSpan(new FontsSpan(context, entry.getValue(), size, textSize),
                            matcher.start(), matcher.end(),
                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                }
            }
        }
        return hasChanges;
    }
}

FontsSpan 继承于DynamicDrawableSpan,主要实现替换字符为 drawable。

public class FontsSpan extends DynamicDrawableSpan {
    private final Context mContext;

    private final int mResourceId;

    private final int mSize;

    private final int mTextSize;

    private int mHeight;

    private int mWidth;

    private int mTop;

    private Drawable mDrawable;

    private WeakReference<Drawable> mDrawableRef;

    public FontsSpan(Context context, int resourceId, int size, int textSize) {
        super(DynamicDrawableSpan.ALIGN_BASELINE);
        mContext = context;
        mResourceId = resourceId;
        mWidth = mHeight = mSize = size;
        mTextSize = textSize;
    }

    public Drawable getDrawable() {
        if (mDrawable == null) {
            try {
                mDrawable = mContext.getResources().getDrawable(mResourceId);
                mHeight = mSize;
                mWidth = mHeight * mDrawable.getIntrinsicWidth() / mDrawable.getIntrinsicHeight();
                mTop = (mTextSize - mHeight) / 2;
                mDrawable.setBounds(0, mTop, mWidth, mTop + mHeight);
            } catch (Exception e) {
                // swallow
            }
        }
        return mDrawable;
    }

    @Override
    public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
        //super.draw(canvas, text, start, end, x, top, y, bottom, paint);
        Drawable b = getCachedDrawable();
        canvas.save();

        int transY = bottom - b.getBounds().bottom;
        if (mVerticalAlignment == ALIGN_BASELINE) {
            transY = top + ((bottom - top) / 2) - ((b.getBounds().bottom - b.getBounds().top) / 2) - mTop;
        }

        canvas.translate(x, transY);
        b.draw(canvas);
        canvas.restore();
    }

    private Drawable getCachedDrawable() {
        if (mDrawableRef == null || mDrawableRef.get() == null) {
            mDrawableRef = new WeakReference<Drawable>(getDrawable());
        }
        return mDrawableRef.get();
    }
}

其实原理很简单,并不复杂。
demo代码地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,783评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,396评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,834评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,036评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,035评论 5 362
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,242评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,727评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,376评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,508评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,415评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,463评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,140评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,734评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,809评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,028评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,521评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,119评论 2 341

推荐阅读更多精彩内容