绘制动画(波浪动画/轨迹动画/路径动画/PathMeasure)

本文由 Luzhuo 编写,转发请保留该信息.
原文: https://blog.csdn.net/rozol/article/details/79730582

绘制动画, 由Android的绘画功能 + 属性动画 组成的一种动画

主要方法
valueAnimator.addUpdateListener(AnimatorUpdateListener) // 监听动画数值更新
估值器

ValueAnimator.ofObject(new TypeEvaluator<PointF>() {
    @Override
    public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
        // fraction(时间因子[0,1]), startValue(开始值), endValue(结束值)
        return null; // 返回计算结果值
    }
});

波浪动画
原理: 通过动画计算的数值, 不断将波浪右移.

代码 

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    mPath.reset();
    mPath.moveTo(-waveLength + offset, centerY);

    // 绘制2个贝塞尔曲线
    for (int i = 0; i < waveCount; i++){
        mPath.quadTo( - waveLength * 3 / 4 + i * waveLength + offset, centerY + 60, - waveLength / 2 + i * waveLength + offset, centerY);
        mPath.quadTo( - waveLength / 4 + i * waveLength + offset, centerY - 60, i * waveLength + offset, centerY);
    }

    // 封闭波浪
    mPath.lineTo(screenWidth, screenHeight);
    mPath.lineTo(0, screenHeight);
    mPath.close();

    canvas.drawPath(mPath, mPaintBezier);
}

public void onClick(View v) {
    // 设置属性动画
    valueAnimator = valueAnimator.ofInt(0, waveLength);
    valueAnimator.setDuration(1000);
    valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
    valueAnimator.setInterpolator(new LinearInterpolator());
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            offset = (int) animation.getAnimatedValue();
            invalidate();
        }
    });
    valueAnimator.start();
}

效果

轨迹动画

  • 原理: 使用估值器计算出动画执行时间点的坐标结果, 然后将黑色小球按该坐标结果绘制

  • 代码

 

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawCircle(movePointX, movePointY, 20, paintCircle);
}

public void onClick(View v) {
    // 运动轨迹
    BezierEvaluator evaluator = new BezierEvaluator(new PointF(flagPointX, flagPointY));
    ValueAnimator animator = ValueAnimator.ofObject(evaluator,
            new PointF(startPointX, startPointY), new PointF(endPointX, endPointY));
    animator.setDuration(600);
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            PointF pointF = (PointF) animation.getAnimatedValue();
            movePointX = (int) pointF.x;
            movePointY = (int) pointF.y;
            invalidate();
        }
    });
    animator.setInterpolator(new AccelerateDecelerateInterpolator());
    animator.start();
}

public class BezierEvaluator implements TypeEvaluator<PointF> {

    private PointF flagPoint;

    public BezierEvaluator(PointF flagPoint){
        this.flagPoint = flagPoint;
    }

    @Override
    public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
        // fraction(时间因子[0,1]), startValue(开始值), endValue(结束值)
        return BezierUtil.getCalculateBezierPointForQuadratic(fraction, startValue, flagPoint, endValue);
    }
}

/**
 * 获取二阶贝塞尔曲线点的坐标
 * B(t) = (1 - t)^2 * P0 + 2t * (1 - t) * P1 + t^2 * P2, t ∈ [0,1]
 *
 * @param t  曲线长度比例
 * @param p0 起始点
 * @param p1 控制点
 * @param p2 终止点
 * @return t对应的点
 */
public static PointF getCalculateBezierPointForQuadratic(float t, PointF p0, PointF p1, PointF p2) {
    PointF point = new PointF();
    float temp = 1 - t;
    point.x = temp * temp * p0.x + 2 * t * temp * p1.x + t * t * p2.x;
    point.y = temp * temp * p0.y + 2 * t * temp * p1.y + t * t * p2.y;
    return point;
}

效果

PathMeasure (Path测量类)

基本使用

public void onclick(View view){
    Bitmap copybitmap = Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888); // 白纸
    Canvas canvas = new Canvas(copybitmap); // 画布
    Paint paint = new Paint(); // 画笔
    paint.setStyle(Paint.Style.STROKE);


    Path path = new Path();
    Path dst = new Path();
    path.addCircle(300, 300, 200, Path.Direction.CW);

    PathMeasure pm;
    // 测量Path的类
    pm= new PathMeasure();
    // pm = new PathMeasure(path, true);

    pm.setPath(path, true); // 设置path
    float length = pm.getLength(); // 获取Path长度
    // 截取片段 (参数:起始截取位置, 结束截取位置, 截取的path添加到dst, 是否从起点开始截) 返回:true存入dst中
    boolean segment = pm.getSegment(0 + 100, length - 100, dst, true);
    // 选择下个path
    boolean next = pm.nextContour();
    // 获取指定位置的 坐标 和 该坐标的正切值 (参数:位置, 坐标, 正切值) 返回:true将数据存入 pos 和 tan 中,
    float[] pos = new float[2], tan = new float[2];
    boolean postan = pm.getPosTan(300, pos, tan);
    // 获取指定位置的 坐标 和 该坐标的矩阵 (参数:位置, 矩阵, 将什么存入矩阵[POSITION_MATRIX_FLAG(坐标) / TANGENT_MATRIX_FLAG(正切)]) 返回:true将数据存入 matrix 中
    Matrix matrix = new Matrix();
    boolean m = pm.getMatrix(300, matrix, PathMeasure.TANGENT_MATRIX_FLAG);

    // 如果清除dst里的内容, 修改后加上该代码, 用以解决 Android 4.4- 开启硬件加速的bug
    // dst.reset();
    // dst.lineTo(0, 0);

    // 记算路径上某点的切线的角度(Math.atan2(纵坐标, 横坐标))
    float degree = (float) (Math.atan2(tan[1], tan[0]) * 180 / Math.PI);

    canvas.drawPath(dst, paint);
    iv.setImageBitmap(copybitmap);
}

 案例: 缓冲圆圈

  • 实现

public PathView(Context context, AttributeSet attrs) {
    super(context, attrs);

    paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeWidth(5);

    path = new Path();
    dst = new Path();

    // 画一个圆, 关联pathMeasure
    path.addCircle(400, 400, 100, Path.Direction.CW);
    pathMeasure = new PathMeasure(); // 测量Path的类
    pathMeasure.setPath(path, true);

    length = pathMeasure.getLength(); // 路径长度

    // 创建属性动画
    ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
    animator.setDuration(1000);
    animator.setInterpolator(new LinearInterpolator());
    animator.setRepeatCount(ValueAnimator.INFINITE);
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            animValue = (float) valueAnimator.getAnimatedValue();
            invalidate();
        }
    });
    animator.start();
}

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    dst.reset();
    dst.lineTo(0, 0); // 解决硬件加速的bug

    float stop = length * animValue;
    float start = (float) (stop - ((0.5 - Math.abs(animValue - 0.5)) * length));
    // 从起点开始截取, 路径将越来越长
    pathMeasure.getSegment(start, stop, dst, true); // 截取整个path的任何片段(开始长度 / 结束长度 / 保存截取的路径 / 是否从起点开始截取)
    canvas.drawPath(dst, paint);
}

上述主要使用pathMeasure.getSegment(start, stop, dst, true);对path进行截取, 然后进行重绘; 除此之外还可以使用Path虚线类, 改变起始偏移量实现类似截取的效果, 由于是通过起始偏移量实现的, 所以只能从头部开始.

 public void onAnimationUpdate(ValueAnimator valueAnimator) {
     animValue = (float) valueAnimator.getAnimatedValue();
     // 初始化路径风格(float intervals[]:实现的长度, float phase:起始偏移量)
     effect = new DashPathEffect(new float[]{length, length}, length * animValue);
     paint.setPathEffect(effect);
     invalidate();
 }

效果
 

————————————————
版权声明:本文为CSDN博主「LZ_Luzhuo」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Rozol/article/details/79730582

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值