绘制文本路径

最近复习了一下自定义View相关知识。看到Paint有个api,public void getTextPath(String text, int start, int end,float x, float y, Path path) ,可以获取到文字的 Path。

文字的绘制,虽然是使用 Canvas.drawText() 方法,但其实在下层,文字信息全是被转化成图形,对图形进行绘制的。
getTextPath() 方法,获取的就是目标文字所对应的 Path 。这个就是所谓「文字的 Path」。

那么我通过ValueAnimator控制这个路径的绘制进度,不是可以显示文本的绘制过程了吗。看下效果图

如果对PathMeasure不够熟悉的朋友,可以先去看下PathMeasure的使用。
整个流程分下面几步:
一、init():
获取待绘制文本的Path,初始化对应的PathMeasure, 迭代Path的每个轮廓,计算Path的总长度

二、onDraw

  1. 根据动画的进度,计算待绘制的路径长度needDrawLength
  2. 判断当前轮廓的总长度是否大于要绘制的长度,如果大于,则当前轮廓只用绘制部分,结束循环;如果小于,则当前轮廓完整绘制,并和已绘制轮廓总长度累加,循环当前步骤

下面贴完整代码

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.Nullable;

public class PathMeasureView extends View {
    private static final String TAG = "PathMeasureView";
    public PathMeasureView(Context context) {
        this(context,null);
    }

    public PathMeasureView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public PathMeasureView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private Paint mPaint;
    private Path mPath; //整个文本所对应的Path
    private PathMeasure mPathMeasure; //mPath对应的PathMeasure,用来控制mPath需要绘制的比例
    private Path dstPath; //每次重绘时,需要绘制的path
    private String drawText = "睁眼看世界"; //待绘制文本

    private ValueAnimator valueAnimator;
    private float mProgress; //当前进度
    private float mTotalLength; //要绘制的文本所对应Path的总长度

    private void init(){
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setTextSize(200);
        mPath = new Path();
        dstPath = new Path();
        //获取待绘制文本的Path
        mPaint.getTextPath(drawText,0,drawText.length(),50,200,mPath);

        mPathMeasure = new PathMeasure();
        mPathMeasure.setPath(mPath,false);
        //计算总长度
        mTotalLength = mPathMeasure.getLength();
        while (mPathMeasure.nextContour()){
            mTotalLength += mPathMeasure.getLength();
        }
        mPathMeasure.setPath(mPath,false);

        //执行动画
        valueAnimator = ValueAnimator.ofFloat(0,1);
        valueAnimator.setDuration(20000);
        valueAnimator.setRepeatMode(ValueAnimator.RESTART);
        valueAnimator.setRepeatCount(-1);
        valueAnimator.addUpdateListener(animation -> {
            mProgress = (float) animation.getAnimatedValue();
            invalidate();
        });

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //因为上次绘制时,将PathMeasure循环到了中间的某个轮廓。所以每次开始绘制,一定要对mPathMeasure重新设置Path,回到第1个轮廓
        mPathMeasure.setPath(mPath,false);
        float needDrawLength = mTotalLength * mProgress;
        float consumedLength = mPathMeasure.getLength(); //当前path的总长度
        while (consumedLength<needDrawLength){
            //当前轮廓的总长度小于要绘制的总长度,所以当前轮廓需要完整绘制
            mPathMeasure.getSegment(0,mPathMeasure.getLength(),dstPath,true);
            canvas.drawPath(dstPath,mPaint);
            mPathMeasure.nextContour();
            consumedLength += mPathMeasure.getLength();
        }
        //加上最后一段轮廓的长度后,超过了要绘制的总长度,所以先减去最后一个轮廓的长度,然后对最后一段轮廓部分绘制
        consumedLength -= mPathMeasure.getLength();
        mPathMeasure.getSegment(0,needDrawLength - consumedLength,dstPath,true);
        canvas.drawPath(dstPath,mPaint);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        valueAnimator.start();
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值