颜色渐变效果:
【1】聊到自定义View的文本绘制,首先看看如何自定义View
①继承View类,重写构造方法(通常是三个)
②重写onMeasure和onDraw方法
例如:
public class MyTextView extends View {
public MyTextView(Context context) {
super(context);
}
public MyTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setTextSize(100);
canvas.drawLine(0,getHeight()/2,getWidth(),getHeight()/2,paint);
canvas.drawLine(getWidth()/2,0,getWidth()/2,getHeight(),paint);
paint.setTextAlign(Paint.Align.CENTER);
Paint.FontMetrics fm = paint.getFontMetrics();
canvas.drawText("你好啊",getWidth()/2,getHeight()/2-(fm.descent+fm.ascent)/2,paint);
}
}
【2】说道自定义View就不得不说一下
FontMetrics
文字模型
- FontMetrics 文字绘制模型涉及到5个概念:
1.top
2.bottom
3.ascent
4.descent
5.baseline
咱们来一个个的说,说的明白了,之后就好做了,就不会再迷糊了
baseline baseline
也叫基准线,是文字绘制的基准,文字以文字中心内容为核心以baseline为起点进行居中对齐,典型的就是带圈的小写字母了,大家从上面的图中能看出端倪。文字剩下的部分,有的向上占据空间,比如
i,有的向下占据空间,比如 j 。canvas 绘制文字时就是以baseline 的值作为文字 Y坐标的基准
ascent 和 descent ascent 和 descent 是成对来说的,ascent 叫文字的上坡度,descent
叫文字的下坡度,绘制文字据对不会超出 ascent - descent 的范围,上标,下标,音标除外文字占据 ascent - descent 空间的情况分3种:
a,I 典型小写字母,只会占据 baseline - ascent 的部分空间 A 典型大写字母,会占据 baseline - ascent
的全部空间,也就是撑满从 baseline - ascent j,g 向下占据空间的典型字母,会像 a 一样占据部分 baseline -
ascent 的空间,但是向下绘制的部分会占据部分 baseline - descent 的空间,向下最多的如 j 是会占据全部
baseline - descent 的空间 我 中文基本会填满 ascent - descent 的空间,但是又不会 100%
填满,上下会多少流出一些空隙
top - bottom top-ascent 叫上标,bottom-descent
叫下标,一般绘制文字不会占这块的空间,但是上标,下标,音标会占用这块空间,好比上图中的罗马字符。top-ascent 和
bottom-descent 上下2块的空间除了绘制特殊部分,基本是作为文字上下分割空间存在的
文字是不会超过top和bottom的,通常的汉字和26个英文也通常都在accent-decent之间,但是其他的比如藏文,罗马文之类的就会超过这个范围,但也不会超过top-bottom
再来一张图:
【3】开始绘制文字 “你好啊”
先来说下水平方向上的:
可以使用paint.setTextAlign(Paint.Align.CENTER);
来让文字显示在设置的x的左边右边还是中间
①先来看看LEFTpaint.setTextAlign(Paint.Align.LEFT);
②再看看CENTERpaint.setTextAlign(Paint.Align.CENTER);
③再来看看RIGHT:
再来说下竖直方向上的:
如果直接将y设置为getHeight()/2
的话,效果是这样的:
效果并不是我们希望的,画到上面去了,因为这里我们设置的y就是baseLine,但是baseLine并不是文字的高度的一半,所以,我们需要计算baseLine是什么值才能使我们的文字在View的中心。文字需要下移,y肯定需要增加,增加多少呢?我们来算一算
效果:
自定义View代码:
public class MyTextView extends View {
public MyTextView(Context context) {
super(context);
}
public MyTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setTextSize(100);
canvas.drawLine(0,getHeight()/2,getWidth(),getHeight()/2,paint);
canvas.drawLine(getWidth()/2,0,getWidth()/2,getHeight(),paint);
paint.setTextAlign(Paint.Align.CENTER);
Paint.FontMetrics fm = paint.getFontMetrics();
canvas.drawText("你好啊",getWidth()/2,getHeight()/2-(fm.descent+fm.ascent)/2,paint);
}
}
【4】自定义文字渐变效果
①首先来看看canvas画布:
可以把canvas理解为多层的,我们是一层一层画上去的
可以简单的把canvas.save();
和canvas.restore();
之间的绘制看做一层,如:
canvas.save();
float drLeft = getWidth()/2 - paint.measureText(text)/2;
canvas.drawLine(0,getHeight()/2,getWidth(),getHeight()/2,paint);
canvas.drawLine(getWidth()/2,0,getWidth()/2,getHeight(),paint);
canvas.drawText(text,drLeft,getHeight()/2-(fm.descent+fm.ascent)/2,paint);
canvas.restore();
可以把上面的看做是一层。
②再来看看裁剪画布:
Rect rect = new Rect((int)drLeft,0,(int)TopRight,getHeight());
canvas.clipRect(rect);
③原理:
- 最下面一层我们绘制全黑体的字,在上面一层我们绘制红体字。
- 我们让在第二个图层上的剪裁矩形的右边不断增长,最终绘制出完整的红体字
1)如何使右边随时间不断增长?我们使用属性动画,让一个数从0f-1f,在这个数改变的时候,我们调用invalidate()刷新,然后让(这个数去乘上文字长度+文字开始的位置)作为矩形的有边界,这样就能改变矩形的有边界了。 - 我们不能绘制半个文字,所以只能是绘制好了文字,我们使用剪裁,可以呈现出文字渐变的效果。
④开始绘制:
我们先绘制第一层,黑体字:
String text = "你好啊";
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
super.onDraw(canvas);
Log.v("zj","ondraw");
paint.setTextSize(100);
paint.setAntiAlias(true);//抗锯齿
paint.setStyle(Paint.Style.FILL);
Paint.FontMetrics fm = paint.getFontMetrics();
canvas.save();
float drLeft = getWidth()/2 - paint.measureText(text)/2;
canvas.drawLine(0,getHeight()/2,getWidth(),getHeight()/2,paint);
canvas.drawLine(getWidth()/2,0,getWidth()/2,getHeight(),paint);
canvas.drawText(text,drLeft,getHeight()/2-(fm.descent+fm.ascent)/2,paint);
canvas.restore();
}
实现效果:
当然你可以不用绘制中心线,这里为了说明文字的位置,绘制了中心线。
我们先绘制第二层,红体字:
canvas.save();
drLeft = getWidth()/2 - paint.measureText(text)/2;
TopRight = (float) (drLeft + progress*paint.measureText(text));
paint.setColor(Color.RED);
Rect rect = new Rect((int)drLeft,0,(int)TopRight,getHeight());
canvas.clipRect(rect);
canvas.drawText(text,drLeft,getHeight()/2-(fm.descent+fm.ascent)/2,paint);
canvas.restore();
- 自定义View完整代码:
public class MyTextView extends View {
private float progress = 0.0f;
public float getProgress() {
return progress;
}
public void setProgress(float progress) {
//invalidate();
this.progress = progress;
//Log.v("dd","progress -》"+ progress);
invalidate();
}
public MyTextView(Context context) {
super(context);
}
public MyTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
String text = "你好啊,欢迎阅览我的博客";
private float TopRight;
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
super.onDraw(canvas);
Log.v("zj","ondraw");
paint.setTextSize(100);
paint.setAntiAlias(true);//抗锯齿
paint.setStyle(Paint.Style.FILL);
Paint.FontMetrics fm = paint.getFontMetrics();
canvas.save();
float drLeft = getWidth()/2 - paint.measureText(text)/2;
canvas.drawLine(0,getHeight()/2,getWidth(),getHeight()/2,paint);
canvas.drawLine(getWidth()/2,0,getWidth()/2,getHeight(),paint);
canvas.drawText(text,drLeft,getHeight()/2-(fm.descent+fm.ascent)/2,paint);
canvas.restore();
canvas.save();
drLeft = getWidth()/2 - paint.measureText(text)/2;
TopRight = (float) (drLeft + progress*paint.measureText(text));
paint.setColor(Color.RED);
Rect rect = new Rect((int)drLeft,0,(int)TopRight,getHeight());
canvas.clipRect(rect);
canvas.drawText(text,drLeft,getHeight()/2-(fm.descent+fm.ascent)/2,paint);
canvas.restore();
}
}
- MainActivity中的属性动画
public class MainActivity extends AppCompatActivity {
MyTextView mt;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mt = findViewById(R.id.myTextView);
ObjectAnimator.ofFloat(mt,"progress",0f,1f).setDuration(5000).start();
}
}
mt是对应的这个View在xml中的id
⑤运行效果:
⑥纠正:
这只是一个小demo,有以下几点,通常不要这样写:
【1】尽量不要再onDraw方法中new对象,这样会导致gc一直回收,从而导致内存抖动,降低性能
【2】在绘制的时候其实重复绘制了,这样导致许多问题,当然这里最多只叠加了两次,我们底层的黑体字其实也可以剪裁,右边界不动,让左边界随着时间往右移,直到有边界,这样的话,同一个像素点就只会绘制一次。