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了。