安卓文字绘制和歌词器的简单实现

安卓绘制文本的细节和歌词动画实战

绘制文本有许多细节,这篇文章从绘制最简单的文字开始,到绘制的具体细节,再到歌词动画实战进行讲解

绘制简单文本

先绘制一个简单的文本,定义一个继承自AppCompatTextView的类MyTextView,在activity_main中进行使用

绘制API

文字是通过CanvasdrawText方法进行绘制的,在MyTextView中需要重写onDraw调用drawText进行绘制,解析一下drawText的参数

CanvasdrawText在父类里实现

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,还有基准线,他们这几个单词都是代表距离基准线的距离,往上为负数,往下为正数。

设计多条线的原因

说一下我自己的理解,我们当初为啥有四线三格,就是为了规整字母的大小

文字是多种多样的,四线三格也只是对于英文字母来说的,对于中文来讲就是田字格,每种文字的规范都不同,所以安卓设计出很多线来定义你要绘制的这段文字的规范。

先看图:

img

线的含义:

  • top

    所有文字的最顶端,所有类型的文字都不可能超过top

  • ascent

    大部分文字的顶端,低于top,可能有一些文字会超过ascent

  • base

    基准线,0线

  • descent

    大部分分子的底端,高于bottom,可能会有一些文字超过descent

  • bottom

    所以文字的最低端,所有类型的文字都不可能比bottom还低

  • leading

    图中没有画出来,含义是上一行的descent与下一行的ascent之间的距离

中心绘制

在屏幕中心绘制文本,注意我在activity_main定义的MyTextViewwidthheight都是match

x轴居中

canvas.drawText("hbsd", getWidth()/2, 100, mPaint);

上边代码是不能生效的,它绘制的效果是

因为他的x是宽度的一半,默认在最左边绘制

下面演示正确方式

align居中

给画笔设置TextAlign属性,他默认是left的,也就是上边错误的原因

mPaint.setTextAlign(Paint.Align.CENTER);
宽度居中

根据文字的宽度来设置drawTextx参数

//测量文字的宽度
float fontWidth = mPaint.measureText(mStr);
//往左平移一半宽度
float left = width/2 - fontWidth/2;
//绘制文字
canvas.drawText(mStr, left, 100, mPaint);

正确效果

正中心绘制

中心居中,也就是xy轴全部居中,上边解决了x轴居中,现在解决y轴居中

mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setTextSize(70);
float baseline = getHeight()/2;
canvas.drawText("河北师大", width/2, baseline, mPaint);

上方代码效果

发现文字的文职在中心线偏上,因为这是根据基准线来的
上面讲述了很多距离,根据距离可以尝试推导居中公式

这里我按照大部分的文字的规范来说,也就是accentdecent分别代表文字的顶端和底端

先找到最底部,也就是 getHeight()/2 - decent

然后往上平移一半高度 getHeight()/2 - decent + (decent - accent)/2

最后推导得出 getHeight()/2 - (decent + accent)/2

修改上面代码:

这里想要拿到descentascent需要通过FontMetrics类,FontMetrics直接调用Paintapi就可以获取

 mFontMetrics = mPaint.getFontMetrics();
 float baseline = getHeight()/2 - (mFontMetrics.descent + mFontMetrics.ascent)/2;

正确效果图

动画绘制

现在实现歌词器效果,效果如下:

原理

Canvas是可以分层的Canvas提供两个API支持分层saverestore,两个方法中间绘制的就是独立的一层,而且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);
        //裁剪canvas
        canvas.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();
}

变成蓝色说明没有过度绘制

Demo点赞评论找我要哦

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值