效果图:
PathMeasure中的方法:
返回值 | 方法名 | 释义 |
---|---|---|
void | setPath(Path path, boolean forceClosed) | 关联一个Path |
boolean | isClosed() | 是否闭合 |
float | getLength() | 获取Path的长度 |
boolean | nextContour() | 跳转到下一个轮廓 |
boolean | getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) | 截取片段 |
boolean | getPosTan(float distance, float[] pos, float[] tan) | 获取指定长度的位置坐标及该点切线值 |
boolean | getMatrix(float distance, Matrix matrix, int flags) | 获取指定长度的位置坐标及该点Matrix |
一、实现上述效果主要通过getSegment方法不断截取片段然后重绘界面,让我们先学习getSegment的用法,getSegment方法的参数说明如下:
public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo);
参数说明:
startD:开始的距离在(0-getLength())之间,startD必须小于stopD
stopD:结束距离在(0-getLength())之间
startWithMoveTo,是否移动的开始位置
代码如下:
package com.cool.pathanim;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
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.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
/**
* Created by cool on 2017/5/18.
*/
public class MagnifyingGlassView extends View {
private Paint mPaint;
private Path mCirclePath;
private Path mHandPath;
private Path mBigCirclePath;
private Path mCirclePathDst;
private Path mHandPathDst;
private Path mBigCirclePathDst;
private PathMeasure mPathMeasure;
//view的宽高
private int mWhidth;
private int mHight;
private int mRadius;
//当前绘制进度占总长度的百分百
private float mCirclePercent = 0;
private float mHandPercent = 0;
private float mBigCirclePercent = 0;
private boolean isCircleAnimStart = false;
private boolean isHandAnimStart = false;
private boolean isBigCircleAnimStart = false;
public MagnifyingGlassView(Context context) {
this(context, null);
}
public MagnifyingGlassView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MagnifyingGlassView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.WHITE);
mPaint.setStrokeWidth(5);
mPaint.setStyle(Paint.Style.STROKE);
mCirclePath = new Path();
mCirclePathDst = new Path();
mBigCirclePath = new Path();
mHandPath = new Path();
mHandPathDst = new Path();
mBigCirclePathDst = new Path();
mPathMeasure = new PathMeasure();
startCircleValueAnim();
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(!isHandAnimStart && !isCircleAnimStart && !isBigCircleAnimStart)
startCircleValueAnim();
}
});
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.parseColor("#87CEFA"));
canvas.save();
canvas.translate(mWhidth / 2, mHight / 2);
canvas.rotate(45);
mCirclePath.addCircle(0, 0, mRadius, Path.Direction.CW);
mHandPath.moveTo(mRadius, 0);
mHandPath.lineTo(mRadius + dp2px(getContext(),60), 0);
mBigCirclePath.addCircle(0,0,mRadius + dp2px(getContext(),60), Path.Direction.CW);
if(!isCircleAnimStart) {
canvas.drawPath(mCirclePath, mPaint);
}
if(!isHandAnimStart){
canvas.drawPath(mHandPath, mPaint);
}
mPathMeasure.setPath(mCirclePath, false);
mCirclePathDst.reset();
mPathMeasure.getSegment(mCirclePercent * mPathMeasure.getLength(), mPathMeasure.getLength(), mCirclePathDst, true);
canvas.drawPath(mCirclePathDst, mPaint);
if (mCirclePercent == 1.0) {
mPathMeasure.nextContour();
mPathMeasure.setPath(mHandPath, false);
mHandPathDst.reset();
mPathMeasure.getSegment(mHandPercent * mPathMeasure.getLength(), mPathMeasure.getLength(), mHandPathDst, true);
canvas.drawPath(mHandPathDst, mPaint);
}
if(mHandPercent == 1){
mPathMeasure.nextContour();
mBigCirclePathDst.reset();
mPathMeasure.setPath(mBigCirclePath,false);
mPathMeasure.getSegment(mBigCirclePercent * mPathMeasure.getLength(),mBigCirclePercent * mPathMeasure.getLength() + 50,mBigCirclePathDst,true);
canvas.drawPath(mBigCirclePathDst,mPaint);
}
canvas.restore();
}
private void startHandValueAnim() {
ValueAnimator handPathValueAnimator = ValueAnimator.ofFloat(0, 1);
handPathValueAnimator.setDuration(500);
handPathValueAnimator.start();
handPathValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mHandPercent = (float) animation.getAnimatedValue();
invalidate();
}
});
handPathValueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
startBigCircleValueAnim();
}
});
}
private void startBigCircleValueAnim(){
ValueAnimator bigCircleValueAnim = ValueAnimator.ofFloat(0,1);
bigCircleValueAnim.setDuration(1500);
bigCircleValueAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mBigCirclePercent = (float) animation.getAnimatedValue();
invalidate();
}
});
bigCircleValueAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
isBigCircleAnimStart = false;
isHandAnimStart = false;
isCircleAnimStart = false;
}
@Override
public void onAnimationStart(Animator animation) {
isBigCircleAnimStart = true;
}
});
bigCircleValueAnim.start();
}
private void startCircleValueAnim() {
ValueAnimator circleValueAnimator = ValueAnimator.ofFloat(0, 1);
circleValueAnimator.setDuration(1000);
circleValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCirclePercent = (float) animation.getAnimatedValue();
invalidate();
}
});
circleValueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
startHandValueAnim();
isHandAnimStart = true;
}
@Override
public void onAnimationStart(Animator animation) {
isCircleAnimStart = true;
}
});
circleValueAnimator.start();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureSpec(widthMeasureSpec), measureSpec(heightMeasureSpec));
}
public int measureSpec(int measureSpec) {
int expectSize;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
if (mode == MeasureSpec.EXACTLY) {
expectSize = size;
} else {
expectSize = dp2px(getContext(), 300f);
if (mode == MeasureSpec.AT_MOST) {
expectSize = Math.min(expectSize, size);
}
}
return expectSize;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
this.mWhidth = w;
this.mHight = h;
this.mRadius = mWhidth / 6;
}
public static int dp2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
二、再上一张效果图
此动画是通过PathMeasure的getPosTan,下面说说getPosTan的使用
getPosTan(float distance, float[] pos, float[] tan)
参数说明
distance:这是距离path起点的长度,取值区间为(0-getLength())
pos:对应distance在path的坐标数组,x=pos[0],y=pos[1]
tan:对应distance在path的正切值,x=tan[0],y=[tan[1],计算该点的弧度(注意,这里计算的是弧度哦,角度需要我们再转一次),可以这样计算角度Math.atan2(mTan[1], mTan[0])*180/Math.PI
下面是上面动画的实现源码:
package com.cool.pathanim;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
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 cool on 2017/5/26.
*/
public class PlanFlyView extends View {
//view的宽高
private int mWhidth;
private int mHight;
private int mRadius;
private Paint mPaint;
private Path mCirclePath;
private PathMeasure mPathMeasure;
private Bitmap mPlanBitmap;
private float mPercent;
private float[] mPos;
private float[] mTan;
private Matrix matrix;
public PlanFlyView(Context context) {
this(context,null);
}
public PlanFlyView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public PlanFlyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.WHITE);
mPaint.setStrokeWidth(5);
mPaint.setStyle(Paint.Style.STROKE);
mPathMeasure = new PathMeasure();
mCirclePath = new Path();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
mPlanBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.plan, options);
mPos = new float[2];
mTan = new float[2];
matrix = new Matrix();
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startAnim();
}
});
startAnim();
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.parseColor("#87CEFA"));
mCirclePath.addCircle(mWhidth/2,mHight/2,mRadius, Path.Direction.CCW);
canvas.drawPath(mCirclePath,mPaint);
mPathMeasure.setPath(mCirclePath,false);
mPathMeasure.getPosTan(mPathMeasure.getLength()*mPercent,mPos,mTan);
double atan = Math.atan2(mTan[1], mTan[0]);//弧度制
double angle = atan*180/Math.PI;
matrix.reset();
matrix.postRotate((float) angle + 90,mPlanBitmap.getWidth()/2,mPlanBitmap.getHeight()/2);
matrix.postTranslate(mPos[0]-mPlanBitmap.getWidth()/2,mPos[1]-mPlanBitmap.getHeight()/2);
canvas.drawBitmap(mPlanBitmap,matrix,mPaint);
}
private void startAnim() {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0,1);
valueAnimator.setDuration(5000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mPercent = (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.start();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureSpec(widthMeasureSpec), measureSpec(heightMeasureSpec));
}
public int measureSpec(int measureSpec) {
int expectSize;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
if (mode == MeasureSpec.EXACTLY) {
expectSize = size;
} else {
expectSize = dp2px(getContext(), 300f);
if (mode == MeasureSpec.AT_MOST) {
expectSize = Math.min(expectSize, size);
}
}
return expectSize;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
this.mWhidth = w;
this.mHight = h;
this.mRadius = mWhidth / 4;
}
public static int dp2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
三、最后还剩下getMatrix方法
public boolean getMatrix(float distance, Matrix matrix, int flags)
参数说明:
distance:这是距离path起点的长度,取值区间为(0-getLength())
matrix: 直接获取对应distance某点的矩阵,里面封装了位置的旋转角度和位置信息,我们可以直接使用
flags:有2个可以选择,分别是POSITION_MATRIX_FLAG、TANGENT_MATRIX_FLAG,看名字就知道什么意思了
到这里,第二个动画可以使用getMatrix方法来更加简单方便的来实现的