如何让自定义控件的字体瘦身(优化圆形圆点进度条)

一、开源

源码及demo下载

二、产品经理来了

本以为写好的圆形圆点进度条没问题了,因为写前篇博客时优化了一些细节。那位提需求的童鞋下班后呼叫我,说UI有点问题:我的百分比字体偏壮,没原版的苗条(告诉我是百度手机助手,现在才知道⊙﹏⊙b)。
这里写图片描述
一对比,确实粗壮了不少。

怀疑是不是系统的原因,特意去下载手机助手在同一手机上进行比对,,,确实如此,接下来,找办法解决:

首先去查是不是有字体类型,一个一个试,都不是想要的。

// 系统给的5种字体类型:
Typeface.DEFAULT; // 系统默认
Typeface.DEFAULT_BOLD;
Typeface.SANS_SERIF;
Typeface.SERIF;
Typeface.MONOSPACE;

// 设置字体类型
paint.setTypeface(typeface);

问度娘也无果。。。

看代码,突然想Stroke是否可以。但以前测试过,Stroke下绘制文本,setStrokeWidth(w)中的w为0,与Fill一样,非0的话就出现中空文本,类似于“华文彩云”的效果,并不能瘦身。

奇葩灵感突然冒出来:中空文本的颜色变成背景色,然后画到原文本上,这不就瘦身了吗?别说,还真可以,详见瘦身法2,后来也加入到自定义属性中了。

这方法总有点怪怪的,仔细对比图,还发现字体都不一样,拿到Excel中查找,发现:

  1. 百分比值,原版的是前面那种,而我的是系统默认的后面那种;
  2. 原版的单位和按钮文本还是保留了系统默认的。
    这里写图片描述

系统字体库都在这个目录下(别问我Mac):系统盘/Windows/Fonts。就这么轻而易举地找到了,把它拷贝到系统中,AS放到module的src/main/assets/fonts目录下,文件名是ARIALUNI.TTF。

// 获取外部字体库
Typeface tf = Typeface.createFromAsset(context.getAssets(), "fonts/ARIALUNI.TTF");
paint.setTypeface(tf);

运行结果:
这里写图片描述

字体OK了,但怎么还这么粗壮?
再看看这个字体库,22M多,百度不可能塞这样大的文件到apk中。本以为在字体库中只留数字就好了,可以用记事本编辑字体库,发现我错了。。。(要不是错了,还真以为是这种字体+抽脂法实现的。看下面的图,抽的还好吧?后面那个)

这里写图片描述

难道是图片?也不可能,得101张图片,严重浪费资源。

既然字体库太大,不行,就去找找原版的有没有小的字体库。把apk改成后缀zip,里面还真找到两个字体库文件(把文件显示图标放大,在图标上就能看到字体样式),就是你了:HelveticaNeueLTPro.ttf,大小18K,放到module中测试,结果就是上图中的第一个。

竟然还有Arial Unicode MS的缩小版?一万只草泥马灰过。。。

最后,进一步完善:

  1. 优化百分比字体显示,使其更瘦小,默认使用外部字体,毕竟人家是大公司,UI肯定厉害
  2. 添加自定义属性isPercentFontSystem,让开发者可以自己选择使用外部的(默认)还是系统的
  3. 添加自定义属性percentThinPadding,如果觉得字体大(背景是纯颜色),还可以使用此瘦身针
  4. 优化绘制按钮时的冗余代码,进行抽取

感谢”产品经理“,让我又学了一招。

三、瘦身法1:更换字体类型

即导入外部字体库(如果有的话):

  1. 把外部ttf字体库abc.ttf放在src/main/assets/fonts目录下
  2. 获取字体库:Typeface tf = Typeface.createFromAsset(context.getAssets(), "fonts/abc.ttf")
    若不放在assets中,可通过这两个方法获取:
    Typeface.createFromFile(File file)
    Typeface.createFromFile(String path)
  3. 设置字体类型:
    paint.setTypeface(tf);

四、瘦身法2:抽脂法

所谓抽脂法,就是把字体周围抽掉一些。方法就是:在使用Fill绘制好文本后,设置画笔颜色为背景色,然后使用Stroke绘制一次文本。

但此方法有使用局限:背景必须是纯颜色,不能是空、透明、图片。

// 2.1.1 对百分比瘦身
if (percentThinPadding != 0) {
    int bgColor = Color.TRANSPARENT;
    Drawable bgDrawable = getBackground();
    // 确保有背景,且是纯颜色背景(图片则是BitmapDrawable)
    if (bgDrawable != null && bgDrawable instanceof ColorDrawable) {
        bgColor = ((ColorDrawable)bgDrawable).getColor();
    }
    if (bgColor != Color.TRANSPARENT) {
        // 使用Stroke进行瘦身
        mPaint.setColor(bgColor);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(percentThinPadding);
        canvas.drawText(percent + "", mCenterX - textWidth / 2, baseline, mPaint);
        mPaint.setStyle(Paint.Style.FILL);
    }
}

也把它加入到功能中了。效果如下:
这里写图片描述


1个小时后补充:
一直觉得不对劲,洗澡时想起来paint画笔是有橡皮擦的。又重新修改代码,使它在适用于任意背景:

// 2.1.1 对百分比瘦身
if (percentThinPadding != 0) {
    // 使用橡皮擦擦除
    mPaint.setXfermode(mPercentThinXfermode);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeWidth(percentThinPadding);
    canvas.drawText(percent + "", mCenterX - textWidth / 2, baseline, mPaint);
    mPaint.setXfermode(null);
    mPaint.setStyle(Paint.Style.FILL);
}

这样才OK:
这里写图片描述

这次修改,掉入不少坑(不然也不会到现在的3点多了,偶的身体啊!!!):

  1. 直接擦除,文本边框成黑色。因为canvas是包括了背景色的,在canvas上擦除,相当于直接把整个控件的颜色擦除,这样会成为黑色。
    解决办法:创建一个与控件一样大小的Bitmap,同时用此bitmap创建新画布mCanvas,然后在上面绘制,最后把bitmap绘制到canvas中。
  2. onDraw()会被频繁调用,强烈避免在此方法中创建Bitmap和Canvas。
    解决办法:在onSizeChanged()中创建,刚好,只要尺寸变化,Bitmap的尺寸也必须变化。
  3. 每次绘制都是使用同一个bitmap和mCanvas,会导致之前的痕迹都在上面
    解决办法:每次绘制前先擦除画布的所有内容。
mClearCanvasXfermode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);

// 先清除上次绘制的
mPaint.setXfermode(mClearCanvasXfermode);
mCanvas.drawPaint(mPaint);
mPaint.setXfermode(null);

猜你喜欢

转载自blog.csdn.net/a10615/article/details/52686204