文章目录
- 安卓绘制文本的细节和歌词动画实战
- 绘制简单文本
- 绘制API
- 绘制线
- 设计多条线的原因
- 中心绘制
- x轴居中
- align居中
- 宽度居中
- 正中心绘制
- 动画绘制
- 原理
- 过度绘制
- 解决过度绘制
- Demo点赞评论找我要哦
安卓绘制文本的细节和歌词动画实战
绘制文本有许多细节,这篇文章从绘制最简单的文字开始,到绘制的具体细节,再到歌词动画实战进行讲解
绘制简单文本
先绘制一个简单的文本,定义一个继承自AppCompatTextView
的类MyTextView
,在activity_main
中进行使用
绘制API
文字是通过Canvas
的drawText
方法进行绘制的,在MyTextView
中需要重写onDraw
调用drawText
进行绘制,解析一下drawText
的参数
Canvas
的drawText
在父类里实现
BaseCanvas#drawText
//text 会问的文本
//x,y 在哪开始绘制
//paint 画笔
public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {super.drawText(text, x, y, paint);
}
绘制文字是需要画笔的,我们在MyTextView
定义一个画笔,在构造中对画笔进行初始化,可以通过调用setColor
, setTextSize
之类的方法修改画笔的颜色,绘制文字的大小等等,然后在onDraw
中调用
canvas.drawText("hbsd", 0, 0, mPaint);
发现一个问题,文字并没有绘制,这是为什么呢?
其实绘制文本的时候有他自己的规则,drawText
的参数有y
,是文字基准线的意思,我理解为四线三格的第三条线,传入0
的话,意思就是这个第三条线的纵坐标是0
,也就意味着文字都写到屏幕上边去了,如果我们把y
值设的大一点就发现文字出现了。
绘制线
既然安卓在设计绘制文字的时候是按照四线三格来做的,那么也就意味着不可能只有一条线,他还有很多线(线其实也不是线,就是距离吧,这里按线理解),ascent,bottom,descent,leading,top
,还有基准线,他们这几个单词都是代表距离基准线的距离,往上为负数,往下为正数。
设计多条线的原因
说一下我自己的理解,我们当初为啥有四线三格,就是为了规整字母的大小。
文字是多种多样的,四线三格也只是对于英文字母来说的,对于中文来讲就是田字格,每种文字的规范都不同,所以安卓设计出很多线来定义你要绘制的这段文字的规范。
先看图:
线的含义:
-
top
所有文字的最顶端,所有类型的文字都不可能超过
top
-
ascent
大部分文字的顶端,低于
top
,可能有一些文字会超过ascent
-
base
基准线,0线
-
descent
大部分分子的底端,高于bottom,可能会有一些文字超过
descent
-
bottom
所以文字的最低端,所有类型的文字都不可能比
bottom
还低 -
leading
图中没有画出来,含义是上一行的
descent
与下一行的ascent
之间的距离
中心绘制
在屏幕中心绘制文本,注意我在activity_main
定义的MyTextView
的width
和height
都是match
的
x轴居中
canvas.drawText("hbsd", getWidth()/2, 100, mPaint);
上边代码是不能生效的,它绘制的效果是
因为他的x
是宽度的一半,默认在最左边绘制
下面演示正确方式
align居中
给画笔设置TextAlign
属性,他默认是left
的,也就是上边错误的原因
mPaint.setTextAlign(Paint.Align.CENTER);
宽度居中
根据文字的宽度来设置drawText
的x
参数
//测量文字的宽度
float fontWidth = mPaint.measureText(mStr);
//往左平移一半宽度
float left = width/2 - fontWidth/2;
//绘制文字
canvas.drawText(mStr, left, 100, mPaint);
正确效果
正中心绘制
中心居中,也就是x
轴y
轴全部居中,上边解决了x
轴居中,现在解决y
轴居中
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setTextSize(70);
float baseline = getHeight()/2;
canvas.drawText("河北师大", width/2, baseline, mPaint);
上方代码效果
发现文字的文职在中心线偏上,因为这是根据基准线来的
上面讲述了很多距离,根据距离可以尝试推导居中公式
这里我按照大部分的文字的规范来说,也就是accent
和decent
分别代表文字的顶端和底端
先找到最底部,也就是 getHeight()/2 - decent
然后往上平移一半高度 getHeight()/2 - decent + (decent - accent)/2
最后推导得出 getHeight()/2 - (decent + accent)/2
修改上面代码:
这里想要拿到descent
,ascent
需要通过FontMetrics
类,FontMetrics
直接调用Paint
的api
就可以获取
mFontMetrics = mPaint.getFontMetrics();float baseline = getHeight()/2 - (mFontMetrics.descent + mFontMetrics.ascent)/2;
正确效果图
动画绘制
现在实现歌词器效果,效果如下:
原理
Canvas
是可以分层的,Canvas
提供两个API
支持分层save
,restore
,两个方法中间绘制的就是独立的一层,而且Canvas
是支持裁剪的,这样的话,我们可以给下面的图层绘制黑色,上面的图层绘制红色,然后根据属性裁剪红色的Canvas
,让它从左至右,裁剪的区域越来越大,也就最终实现了上面的效果,下面是代码实现
//width和height是整个view的宽高,在onDraw中线调用drawCenterText再调用drawCenterTopText
private void drawCenterText(Canvas canvas) {canvas.save();mPaint.setTextAlign(Paint.Align.CENTER);mPaint.setTextSize(150);mFontMetrics = mPaint.getFontMetrics();float baseline = getHeight()/2 - (mFontMetrics.descent + mFontMetrics.ascent)/2;canvas.drawText(mStr, width/2, baseline, mPaint);canvas.restore();
}
//mPercent是属性动画的改变属性,百分比
private void drawCenterTopText(Canvas canvas) {canvas.save();mRedPaint.setTextSize(150);mFontMetrics = mRedPaint.getFontMetrics();//测量字体宽度float fontWidth = mRedPaint.measureText(mStr);float left = width/2 - fontWidth/2;float right = left + fontWidth*mPercent;//居中公式float baseline = height/2 - (mFontMetrics.descent + mFontMetrics.ascent)/2;//裁剪区域Rect rect = new Rect((int)left,0, (int)right, (int)height);//裁剪canvascanvas.clipRect(rect);//绘制文字canvas.drawText(mStr, left, baseline, mRedPaint);canvas.restore();
}
这样写存在问题,过度绘制
过度绘制
过度绘制就是一个像素点被多次绘制,也就是说画布叠加绘制了,层数不多对手机影响不大,如果说很多层就会导致掉帧等问题
在开发者选项中可以开启过度绘制调试对ui
进行优化
开启调试后我们的app就变得花花绿绿了,这里说明一下颜色的显示规则:
- 蓝色:绘制 1 次
- 绿色:绘制 2 次
- 粉色:绘制 3 次
- 红色:绘制 4 次或更多次
查看demo
的颜色显示
显示为绿色,代表着绘制了两次,我们需要进行优化
解决过度绘制
人眼看到的永远只有最上边一层的不透明的Canvas
,下层看不见的话就需要裁剪掉,根据上面代码可知,上层红色是根据属性动画往右逐渐显示的。那么下层的黑色是否也可以借助属性动画慢慢消失呢?
代码如下,只需要替换上面的drawCenterText
方法即可
private void drawCenterTopText(Canvas canvas) {canvas.save();mRedPaint.setTextSize(150);mFontMetrics = mRedPaint.getFontMetrics();float fontWidth = mRedPaint.measureText(mStr);float left = width/2 - fontWidth/2;float right = left + fontWidth*mPercent;float baseline = height/2 - (mFontMetrics.descent + mFontMetrics.ascent)/2;Rect rect = new Rect((int)left,0, (int)right, (int)height);canvas.clipRect(rect);canvas.drawText(mStr, left, baseline, mRedPaint);canvas.restore();
}
变成蓝色说明没有过度绘制