本次练习效果图如下:左边是支付宝的支付成功打对勾的path动画,右边的是一个箭头沿着圆圈做圆周运动的path动画。
首先了解下PathMeasure的相关基础函数的用法,下图是将一个正方形,利用PathMeasure的getSegment()方法截取从0开始,到150像素距离长度的路径画出来如下:
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 GetSegmentViewLearn extends View {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
Path originPath = new Path();
Path dstPath = new Path();
PathMeasure measure;
public GetSegmentViewLearn(Context context) {
super(context);
}
public GetSegmentViewLearn(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5);
paint.setColor(Color.BLACK);
originPath.addRect(-50, -50, 50, 50, Path.Direction.CCW);
measure = new PathMeasure(originPath, false);
}
/**
*
* getSegment函数使用:用于截取整个Path中的某个片段,并将截取后的Path保存到参数dstPath中
*
*
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(100, 100);
measure.getSegment(0, 150, dstPath, true);
canvas.drawPath(dstPath, paint);
}
}
能够利用path以及PathMeasure画出静态路径的图后,动态路径的图其实跟简单,就是不断改变getSegment函数中的起点和终点坐标即可,下面画一个动态的圆,就是上面动图中所示的外面那个圆圈:
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
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.util.AttributeSet;
import android.view.View;
import com.test.cp5_pathmeasure.util.Utils;
import androidx.annotation.Nullable;
public class GetSegmentView extends View {
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private PathMeasure mPathMeasure;
private Path mDstPath = new Path();
private Float mCurAnimValue;
//箭头动画
private Bitmap mArrawBmp;
private float[] pos = new float[2];
private float[] tan = new float[2];
public GetSegmentView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mArrawBmp = Utils.changeBitmapSize(getResources(), R.drawable.arraw);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5);
paint.setColor(Color.BLACK);
Path mCirclePath = new Path();
mCirclePath.addCircle(100, 100, 50, Path.Direction.CW);
mPathMeasure = new PathMeasure(mCirclePath, false);
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurAnimValue = (float) animation.getAnimatedValue();
invalidate();
}
});
animator.setDuration(2000);
animator.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float length = mPathMeasure.getLength();
float stop = mCurAnimValue * length;
float start = (float) (stop - ((0.5 - Math.abs(mCurAnimValue - 0.5)) * length));
/**
* 画出一个圆圈,终点动态变化
* 想要动态画出截取到的path路线,只要不断改变终点的取值即可
*/
mDstPath.reset();
mPathMeasure.getSegment(0, stop, mDstPath, true);
canvas.drawPath(mDstPath, paint);
/**
* 画出一个圆圈,起点、终点动态变化
* 想要动态画出截取到的path路线,只要不断改变起点和终点的取值即可
*/
// mDstPath.reset();
// mPathMeasure.getSegment(start, stop, mDstPath, true);
// canvas.drawPath(mDstPath, paint);
/**
* 箭头旋转、位移实现方式一:
*
*/
// mPathMeasure.getPosTan(stop, pos, tan);
// float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180 / Math.PI);
// Matrix matrix = new Matrix();
// matrix.postRotate(degrees, mArrawBmp.getWidth()/2, mArrawBmp.getHeight()/2);
// matrix.postTranslate(pos[0] - (float) mArrawBmp.getWidth() / 2, pos[1] - (float) mArrawBmp.getHeight() / 2);
// canvas.drawBitmap(mArrawBmp, matrix, paint);
/**
* 箭头旋转、位移实现方式二:
*/
Matrix matrix2 = new Matrix();
mPathMeasure.getMatrix(stop, matrix2, PathMeasure.POSITION_MATRIX_FLAG | PathMeasure.TANGENT_MATRIX_FLAG);
matrix2.preTranslate(-mArrawBmp.getWidth() / 2, -mArrawBmp.getHeight() / 2);
canvas.drawBitmap(mArrawBmp, matrix2, paint);
}
}
其中的箭头图形有个工具类:
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.util.Log;
public class Utils {
public static Bitmap changeBitmapSize(Resources res, int id) {
Bitmap bitmap = BitmapFactory.decodeResource(res, id);
int width = bitmap.getWidth();
int height = bitmap.getHeight();
Log.e("width", "width:" + width);
Log.e("height", "height:" + height);
//设置想要的大小
int newWidth = 30;
int newHeight = 30;
//计算压缩的比率
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
//获取想要缩放的matrix
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
//获取新的bitmap
bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
bitmap.getWidth();
bitmap.getHeight();
Log.e("newWidth", "newWidth" + bitmap.getWidth());
Log.e("newHeight", "newHeight" + bitmap.getHeight());
return bitmap;
}
}
支付宝的动图,其实就是利用path的两个路径,利用pathMeasure的nextContour函数就可以跳转到path路径的下一个路径上去:
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 AliPayView extends View {
Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
Path mCirclePath = new Path();
Path mCircleDstPath = new Path();
PathMeasure mMeasure;
float mFraction = 0;
public AliPayView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint.setStrokeWidth(5);
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
int mCentX = 100;
int mCentY = 100;
int mRadius = 50;
//先画一个圆形O
mCirclePath.addCircle(mCentX, mCentY, mRadius, Path.Direction.CW);
//再将path移动到画对勾的地方画√
mCirclePath.moveTo(mCentX - mRadius / 2, mCentY);
mCirclePath.lineTo(mCentX, mCentY + mRadius / 2);
mCirclePath.lineTo(mCentX + mRadius / 2, mCentY - mRadius / 3);
mMeasure = new PathMeasure(mCirclePath, false);
ValueAnimator animator = ValueAnimator.ofFloat(0, 2);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mFraction = (float) animation.getAnimatedValue();
invalidate();
}
});
animator.setDuration(4000);
animator.start();
}
boolean mNext = false;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float length = mMeasure.getLength();
if (mFraction < 1) {
float stop = length * mFraction;
mMeasure.getSegment(0, stop, mCircleDstPath, true);
} else {
if (!mNext) {
mNext = true;
mMeasure.getSegment(0, length, mCircleDstPath, true);
mMeasure.nextContour();
}
float stop = length * (mFraction - 1);
mMeasure.getSegment(0, stop, mCircleDstPath, true);
}
canvas.drawPath(mCircleDstPath, mPaint);
}
}