PathMeasure 轨迹动画神器

来源:互联网 发布:putty串口发送数据 编辑:程序博客网 时间:2024/05/16 06:31

PathMeasure 轨迹动画神器

轨迹动画一般利用SVG来实现,或者使用属性动画,自定义估计值,根据两点之间的线性关系式计算坐标(复杂)
但是使用PathMeasure来进行绘制轨迹动画,so easy。

先看效果:
这里写图片描述
效果分析:
1、圆圈变成圆弧
2、圆弧不断的变小

实现
方式1:通过不断改变绘制圆弧的开始角度。 这个方法肯定是最先想到的方法,
因为api
drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,
@NonNull Paint paint)用的最多

方式2:利用PathMeasure,不断截取路径。截取不断变小的圆弧,然后把截取到的path绘制出来就可以。

比较:两种方式相比PathMeasure并没有多大的优势,但是绘制圆弧可以使用 drawArc 来改变角度实现类似轨迹运动的效果,但是,如果path、不是一个圆形,问题就复杂了,PathMeasure的强大也就体现出来了。


学习PathMeasure的使用

查看源码,发现只有100多行,其实也就几个方法。但是确实非常流弊
1、setPath(Path path, boolean forceClosed)
要对path进行操作,首先需要设置一个path,也可以在构造函数中绑定,关键在第二个参数,是否关闭,如果是true,那么会给参数path,强制首尾闭合,因为path可能是一个非闭合的路径。

2、getLength
得到path路径的长度,很有用。

3、getPosTan(float distance, float pos[], float tan[])
得到distance,位置的点的坐标,和在个点与path进行切线的正切,分别放在pos[] 和tan[]中。
例如,distance==length/2,就是得到这条path路径的中间点,的坐标和正切

4、getMatrix(float distance, Matrix matrix, int flags)
这个就厉害了,直接返回得到矩阵,这个矩阵就包含了这个点移动的位置,和倾斜的角度,可以直接在这个点绘制
一个图片,利用这个matrix

5、getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)
从开始的距离,到结束的距离,截取一段path放在dst中,
startWithMoveTo 如果为true,这个dst会从截取点开始
如果为false,这个dst会从(0,0)作为开始点

使用PathMeasure来进行分析

这里写图片描述

接着圆弧动画完了,就是手柄的动画。把属性动画的0到1拆分,0到0.8,完成画的轨迹动画,0.8到1完成手柄的动画,手柄就是一条直线,再使用PathMeasure有点大炮打蚊子,直接不断改变手柄直线的结束点就可以了。

下面是具体的代码实现:

package com.lmj.searchview;import android.animation.Animator;import android.animation.ValueAnimator;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Matrix;import android.graphics.Paint;import android.graphics.Path;import android.graphics.PathMeasure;import android.support.annotation.Nullable;import android.util.AttributeSet;import android.util.Log;import android.view.View;/** * Created by limengjie * on 2017/5/25.14:37 */public class SearchView extends View {    private Matrix mMatrix;    private Paint mPaint;    private Path mPath;    private int mCircleX;    private int mCircleY;    private int mRadius = 50;//圆的半径    private PathMeasure mPathMeasure;    private float circleLength;    private float startD;    private float endD;    private ValueAnimator mvalueAnimator_rotate;    private ValueAnimator mvalueAnimator_path;    private String Tag = "SearchView";    private Path dstCircle;    private int lineD = 0;    private static final int Status_Normal = 1;//正常状态    private static final int Status_PathAni = 2;//正在进行轨迹动画    private static final int Status_RotateAni = 3;//正在旋转动画    private int Status = Status_Normal;    private int rotateNum;    public SearchView(Context context) {        this(context, null);        init();    }    public SearchView(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        init();    }    private void init() {        mMatrix = new Matrix();        mPaint = new Paint();        mPath = new Path();        mPaint.setStyle(Paint.Style.STROKE);        mPaint.setColor(Color.GREEN);        mPaint.setStrokeWidth(2);        mPaint.setAntiAlias(true);        mPathMeasure = new PathMeasure();        dstCircle = new Path();    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        mPath.reset();        mCircleX = getWidth() / 2;        mCircleY = getHeight() / 2;        switch (Status) {            case Status_Normal:                drawNormal(canvas);                break;            case Status_PathAni:                drawPathAni(canvas);                break;            case Status_RotateAni:                drawRotateAni(canvas);                break;        }    }    private void drawNormal(Canvas canvas) {        mPaint.setColor(Color.GREEN);        mPath.addCircle(mCircleX, mCircleY, mRadius, Path.Direction.CW);        canvas.save();        canvas.drawPath(mPath, mPaint);        canvas.rotate(45, mCircleX, mCircleY);        canvas.drawLine(mCircleX + mRadius, mCircleY, mCircleX + mRadius * 2, mCircleY, mPaint);        canvas.restore();    }    private void drawPathAni(Canvas canvas) {        mPath.addCircle(mCircleX, mCircleY, mRadius, Path.Direction.CW);        canvas.save();        canvas.rotate(45, mCircleX, mCircleY);        mPathMeasure.setPath(mPath, false);        circleLength = mPathMeasure.getLength();        endD = circleLength;        dstCircle.reset();        Log.i(Tag, "sd:" + startD + ",ed:" + endD);        mPathMeasure.getSegment(startD, endD, dstCircle, true);//        mPaint.setColor(Color.RED);        canvas.drawPath(dstCircle, mPaint);//圆的动画        canvas.drawLine(mCircleX + mRadius, mCircleY, mCircleX + mRadius * 2 - lineD, mCircleY, mPaint);        canvas.restore();    }    private void drawRotateAni(Canvas canvas) {        mPath.addCircle(mCircleX, mCircleY, mRadius, Path.Direction.CW);        canvas.save();        canvas.rotate(45+rotateNum, mCircleX, mCircleY);        mPathMeasure.setPath(mPath, false);        circleLength = mPathMeasure.getLength();        dstCircle.reset();        mPathMeasure.getSegment(0, 20, dstCircle, true);//        mPaint.setColor(Color.RED);        canvas.drawPath(dstCircle, mPaint);//圆的动画    }    private void startPathAni() {//        if(null==mvalueAnimator){        if (null != mvalueAnimator_path) {            mvalueAnimator_path.cancel();        }        lineD = 0;        mvalueAnimator_path = ValueAnimator.ofFloat(0, 1);        mvalueAnimator_path.setDuration(1500);        mvalueAnimator_path.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                //0-0.8,分割圆,0.8到1 分割手柄                float fraction = animation.getAnimatedFraction();                if (fraction <= 0.8f) {                    startD = circleLength * fraction / 0.7f;                } else {                    startD = circleLength;                    lineD = (int) ((fraction - 0.8) * mRadius / 0.2f);                }                if(fraction==1f){                    lineD = mRadius;                    Status = Status_RotateAni;                    postInvalidate();                    startRotateAni();                }                postInvalidate();            }        });        mvalueAnimator_path.start();//        }    }    public void startRotateAni(){        if (null != mvalueAnimator_rotate) {            mvalueAnimator_rotate.cancel();        }        mvalueAnimator_rotate = ValueAnimator.ofInt(0, 360,0);        mvalueAnimator_rotate.setDuration(3000);        mvalueAnimator_rotate.setRepeatMode(ValueAnimator.RESTART);        mvalueAnimator_rotate.setRepeatCount(-1);        mvalueAnimator_rotate.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                rotateNum = (int) animation.getAnimatedValue();                postInvalidate();            }        });        mvalueAnimator_rotate.start();    }    public void startSearch() {        Status = Status_PathAni;        startPathAni();        postInvalidate();    }    public void stopSearch() {        Status = Status_Normal;        if (null != mvalueAnimator_rotate) {            mvalueAnimator_rotate.cancel();        }        if (null != mvalueAnimator_path) {            mvalueAnimator_path.cancel();        }        postInvalidate();    }}       

总结,
只要我们有一个已知的path,就可以通过PathMeasure,来截取轨迹,根据距离来获取某个点的坐标和正切,所以有了path,轨迹动画就so easy了。

源码下载