Android 自定义View之文本绘制及文字颜色渐变效果

颜色渐变效果:
在这里插入图片描述

【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. 最下面一层我们绘制全黑体的字,在上面一层我们绘制红体字。
  2. 我们让在第二个图层上的剪裁矩形的右边不断增长,最终绘制出完整的红体字
    1)如何使右边随时间不断增长?我们使用属性动画,让一个数从0f-1f,在这个数改变的时候,我们调用invalidate()刷新,然后让(这个数去乘上文字长度+文字开始的位置)作为矩形的有边界,这样就能改变矩形的有边界了。
  3. 我们不能绘制半个文字,所以只能是绘制好了文字,我们使用剪裁,可以呈现出文字渐变的效果。

④开始绘制:

我们先绘制第一层,黑体字:
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】在绘制的时候其实重复绘制了,这样导致许多问题,当然这里最多只叠加了两次,我们底层的黑体字其实也可以剪裁,右边界不动,让左边界随着时间往右移,直到有边界,这样的话,同一个像素点就只会绘制一次。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值