一、开源
源码及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中查找,发现:
百分比值,原版的是前面那种,而我的是系统默认的后面那种;原版的单位和按钮文本还是保留了系统默认的。
系统字体库都在这个目录下(别问我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的缩小版?一万只草泥马灰过。。。
最后,进一步完善:
优化百分比字体显示,使其更瘦小,默认使用外部字体,毕竟人家是大公司,UI肯定厉害添加自定义属性isPercentFontSystem,让开发者可以自己选择使用外部的(默认)还是系统的添加自定义属性percentThinPadding,如果觉得字体大(背景是纯颜色),还可以使用此瘦身针优化绘制按钮时的冗余代码,进行抽取
感谢”产品经理“,让我又学了一招。
三、瘦身法1:更换字体类型
即导入外部字体库(如果有的话):
把外部ttf字体库abc.ttf放在src/main/assets/fonts目录下获取字体库:Typeface tf = Typeface.createFromAsset(context.getAssets(), "fonts/abc.ttf")
,
若不放在assets中,可通过这两个方法获取:
Typeface.createFromFile(File file)
Typeface.createFromFile(String path)
设置字体类型:
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点多了,偶的身体啊!!!):
直接擦除,文本边框成黑色。因为canvas是包括了背景色的,在canvas上擦除,相当于直接把整个控件的颜色擦除,这样会成为黑色。
解决办法:创建一个与控件一样大小的Bitmap,同时用此bitmap创建新画布mCanvas,然后在上面绘制,最后把bitmap绘制到canvas中。onDraw()会被频繁调用,强烈避免在此方法中创建Bitmap和Canvas。
解决办法:在onSizeChanged()中创建,刚好,只要尺寸变化,Bitmap的尺寸也必须变化。每次绘制都是使用同一个bitmap和mCanvas,会导致之前的痕迹都在上面
解决办法:每次绘制前先擦除画布的所有内容。
mClearCanvasXfermode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);// 先清除上次绘制的mPaint.setXfermode(mClearCanvasXfermode);mCanvas.drawPaint(mPaint);mPaint.setXfermode(null);