最近做了一个仿支付宝支付成功的动画,用到了很多关于canvas和paint的知识,于是重新复习了下,下面是实现的源码。
下面是简单介绍这个动画用到的几个点
1、渐变色画笔(Shader)
2、属性动画(ObjectAnimator)
3、圆的缩放动画
4、渐变色圆环的绘制
5、勾和叉的路径动画
/**
* @author mgod
* @Description
* @date 2018/1/6
*/
public class CircleLoadingView extends View {
public static final String TAG = "CircleLoadingView";
public static final int CIRCLE_DURATION = 300; // 圆环缩放动画时间
public static final int TICK_DURATION = 400; // 打钩动画时间
public static final int ARC_DURATION = 1000; // 转圈动画时间
public static final float ANGLE_START = 0f;
public static final float ANGLE_END = 360f;
public static final int ARC = 1;
public static final int CIRCLE_SUCCESS = 2;
public static final int TICK = 3;
public static final int FORK = 4;
public static final int CIRCLE_FAILED = 5;
public static final float RADIANS = 141; // 页面颜色渐变角度
// 圆圈的大小,半径
private int mArcRadius; // 圆环半径
private int mArcStrokeWidth; // 加载圆环画笔宽度
private int mCircleStrokeWidth; // 缩放圆画笔宽度
private int mTickStrokeWidth; // 打钩或者打叉画笔宽度
private int mCanvasWidth; // 画布宽度
private int mCanvasHeight; // 画布高度
private int mOffsetWidth; // 圆距离画布边缘的距离
private int mOffsetHeight; // 圆距离画布边缘的距离
private int mTickOffsetWidth; // 钩距离画布边缘的距离
private int mTickOffsetHeight; // 钩距离画布边缘的距离
private int mForkOffsetWidth; // 叉距离画布边缘的距离
private int mForkOffsetHeight; // 叉距离画布边缘的距离
private int mTickWidth; // 钩所在矩形宽度
private int mTickHeight; // 打钩所在矩形宽度
private int mForkWidth; // 叉所在矩形宽度
private int mForkHeight; // 叉所在矩形宽度
/**
* 以下打钩的三个坐标点
*/
private int mOnePointX;
private int mOnePointY;
private int mTwoPointX;
private int mTwoPointY;
private int mThreePointX;
private int mThreePointY;
/**
* 以下打X的四个坐标点
*/
private int mForkOneStartPointX;
private int mForkOneStartPointY;
private int mForkOneEndPointX;
private int mForkOneEndPointY;
private int mForkTwoStartPointX;
private int mForkTwoStartPointY;
private int mForkTwoEndPointX;
private int mForkTwoEndPointY;
/**
* 圆所处的方形区域
*/
private RectF mRec;
/**
* 加载圆环画笔
*/
private Paint mArcPaint;
/**
* 缩放圆画笔
*/
private Paint mCirclePaint;
/**
* 成功圆画笔
*/
private Paint mCircleSuccessFillPaint;
/**
* 失败圆画笔
*/
private Paint mCircleFailedFillPaint;
/**
* 打钩画笔
*/
private Paint mTickPaint;
/**
* 打X画笔
*/
private Paint mForkPaint;
/**
* 圆环旋转动画
*/
private ObjectAnimator mArcAnimator;
/**
* 圆环缩放动画
*/
private ValueAnimator mCircleAnimator;
/**
* 打钩动画
*/
private ValueAnimator mTickAnimator;
/**
* 每次缩小百分比
*/
private float mCirclePercent;
/**
* 打钩路径百分比
*/
private float mTickPercent;
/**
* 圆环画笔shader属性
*/
private SweepGradient mSweepGradient;
/**
* 成功页面的线性渐变色
*/
private LinearGradient mSuccessGradient;
/**
* 失败页面的线性渐变色
*/
private LinearGradient mFailedGradient;
private Context mContext;
private Path mTickDynamicPath;// 动态打钩路径
private Path mForkOneDynamicPath;// 动态打叉路径
private Path mForkTwoDynamicPath;// 动态打叉路径
// 初始化打钩路径
private Path mTickPath;
// 初始化打X路径
private Path mForkOnePath;
// 初始化打X路径
private Path mForkTwoPath;
private PathMeasure mTickPathMeasure;
private PathMeasure mForkOnePathMeasure;
private PathMeasure mForkTwoPathMeasure;
private OnLoadingCompleteListener mOnLoadingCompleteListener;
/**
* 画的类型 1-圆环,2-缩放的圆环,3-勾
*/
private int mDrawType;
public CircleLoadingView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mContext = context;
initData(attrs);
init();
}
public CircleLoadingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
initData(attrs);
init();
}
/**
* 初始化一些常量值
*/
private void initData(AttributeSet attrs) {
if (attrs == null) {
return;
}
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.CircleLoadingView);
mCanvasWidth = typedArray.getDimensionPixelSize(R.styleable.CircleLoadingView_canvas_width, 0);
mCanvasHeight = typedArray.getDimensionPixelSize(R.styleable.CircleLoadingView_canvas_height, 0);
mTickWidth = typedArray.getDimensionPixelSize(R.styleable.CircleLoadingView_tick_width, 0);
mTickHeight = typedArray.getDimensionPixelSize(R.styleable.CircleLoadingView_tick_height, 0);
mForkWidth = typedArray.getDimensionPixelSize(R.styleable.CircleLoadingView_fork_width, 0);
mForkHeight = typedArray.getDimensionPixelSize(R.styleable.CircleLoadingView_fork_height, 0);
mArcRadius = typedArray.getDimensionPixelSize(R.styleable.CircleLoadingView_radius, 0);
typedArray.recycle();
mArcStrokeWidth = (int) mContext.getResources().getDimension(R.dimen.common_circle_loading_stroke_width); // 圆环画笔宽度
mCircleStrokeWidth = (int) mContext.getResources()
.getDimension(R.dimen.common_circle_loading_circle_stroke_width);// 缩放圆画笔宽度
mTickStrokeWidth = (int) mContext.getResources().getDimension(R.dimen.common_circle_loading_tick_stroke_width); // 打钩画笔宽度
mOffsetWidth = (mCanvasWidth - mArcRadius * 2) / 2; // 圆距离画布左部边缘的距离
mOffsetHeight = (mCanvasHeight - mArcRadius * 2) / 2; // 圆距离画布上部边缘的距离
mTickOffsetWidth = (mCanvasWidth - mTickWidth) / 2;// 勾所在矩形距离上下的距离
mTickOffsetHeight = (mCanvasHeight - mTickHeight) / 2;// 勾所在矩形距离上下的距离
mForkOffsetWidth = (mCanvasWidth - mForkWidth) / 2;// 叉所在矩形距离上下的距离
mForkOffsetHeight = (mCanvasHeight - mForkHeight) / 2;// 叉所在矩形距离上下的距离
mOnePointX = mTickOffsetWidth;
mOnePointY = mTickOffsetHeight + mTickHeight / 2;// 勾的第一个顶点相对于勾所在的矩形垂直居中
mTwoPointX = mTickOffsetWidth + mTickWidth / 3;// 勾的第二个顶点横坐标相对于勾所在的矩形长的1/3处
mTwoPointY = mTickOffsetHeight + mTickHeight;
mThreePointX = mTickOffsetWidth + mTickWidth;
mThreePointY = mTickOffsetHeight;
// 以下是叉四个顶点的距离
mForkOneStartPointX = mForkOffsetWidth;
mForkOneStartPointY = mForkOffsetHeight;
mForkOneEndPointX = mForkOffsetWidth + mForkWidth;
mForkOneEndPointY = mForkOffsetHeight + mForkHeight;
mForkTwoStartPointX = mForkOffsetWidth + mForkWidth;
mForkTwoStartPointY = mForkOffsetHeight;
mForkTwoEndPointX = mForkOffsetWidth;
mForkTwoEndPointY = mForkOffsetHeight + mForkHeight;
}
private void init() {
mTickDynamicPath = new Path();
mForkOneDynamicPath = new Path();
mForkTwoDynamicPath = new Path();
mTickPath = new Path();
mForkOnePath = new Path();
mForkTwoPath = new Path();
mTickPathMeasure = new PathMeasure();
mForkOnePathMeasure = new PathMeasure();
mForkTwoPathMeasure = new PathMeasure();
mArcPaint = new Paint();
mCirclePaint = new Paint();
mCircleSuccessFillPaint = new Paint();
mCircleFailedFillPaint = new Paint();
mTickPaint = new Paint();
mForkPaint = new Paint();
mDrawType = ARC;
mRec = new RectF();
mRec.set(mCanvasWidth / 2 - mArcRadius, mOffsetHeight, mCanvasWidth / 2 + mArcRadius,
mOffsetHeight + mArcRadius * 2);// 设置圆环的位置
int[] mGradientColors = { mContext.getResources().getColor(R.color.common_white),
mContext.getResources().getColor(R.color.common_circle_loading_blue) };
// 圆环的圆心和渐变色设置
mSweepGradient = new SweepGradient(mCanvasWidth / 2, mCanvasHeight / 2, mGradientColors, null);
// 页面线性渐变起始点和结束点
int startX = (int) (mCanvasWidth / 2 - mArcRadius * Math.sin(Math.toRadians(RADIANS - 90)));
int startY = (int) (mCanvasHeight / 2 - mArcRadius * Math.cos(Math.toRadians(RADIANS - 90)));
int endX = (int) (mCanvasWidth / 2 + mArcRadius * Math.sin(Math.toRadians(RADIANS - 90)));
int endY = (int) (mCanvasHeight / 2 + mArcRadius * Math.cos(Math.toRadians(RADIANS - 90)));
int[] mSuccessGradientColors = {
mContext.getResources().getColor(R.color.common_circle_loading_success_blue_start),
mContext.getResources().getColor(R.color.common_circle_loading_suucess_blue_end) };
mSuccessGradient = new LinearGradient(startX, startY, endX, endY, mSuccessGradientColors, null,
Shader.TileMode.CLAMP);
int[] mFailedGradientColors = {
mContext.getResources().getColor(R.color.common_circle_loading_failed_red_start),
mContext.getResources().getColor(R.color.common_circle_loading_failed_red_end) };
mFailedGradient = new LinearGradient(startX, startY, endX, endY, mFailedGradientColors, null,
Shader.TileMode.CLAMP);
// 圆环画笔
mArcPaint.setShader(mSweepGradient);
mArcPaint.setStyle(Paint.Style.STROKE);
mArcPaint.setAntiAlias(true);
mArcPaint.setColor(mContext.getResources().getColor(R.color.common_white));
mArcPaint.setStrokeWidth(mArcStrokeWidth);
// 渐变圆画笔
mCirclePaint.setStyle(Paint.Style.FILL);
mCirclePaint.setAntiAlias(true);
mCirclePaint.setColor(mContext.getResources().getColor(R.color.common_white));
mCirclePaint.setStrokeWidth(mCircleStrokeWidth);
// 圆画笔
mCircleSuccessFillPaint.setStyle(Paint.Style.FILL);
mCircleSuccessFillPaint.setAntiAlias(true);
mCircleSuccessFillPaint.setStrokeWidth(mCircleStrokeWidth);
mCircleSuccessFillPaint.setShader(mSuccessGradient);
mCircleFailedFillPaint.setStyle(Paint.Style.FILL);
mCircleFailedFillPaint.setAntiAlias(true);
mCircleFailedFillPaint.setStrokeWidth(mCircleStrokeWidth);
mCircleFailedFillPaint.setShader(mFailedGradient);
// 打钩画笔
mTickPaint.setStyle(Paint.Style.STROKE);
mTickPaint.setAntiAlias(true);
mTickPaint.setStrokeCap(Paint.Cap.ROUND);// 线条末端是圆
mTickPaint.setStrokeJoin(Paint.Join.ROUND);// 线条拐弯处是圆
mTickPaint.setColor(mContext.getResources().getColor(R.color.common_white));
mTickPaint.setStrokeWidth(mTickStrokeWidth);
// 打X画笔
mForkPaint.setStyle(Paint.Style.STROKE);
mForkPaint.setAntiAlias(true);
mForkPaint.setColor(mContext.getResources().getColor(R.color.common_white));
mForkPaint.setStrokeWidth(mTickStrokeWidth);
// 设置勾的路径
mTickPath.moveTo(mOnePointX, mOnePointY);
mTickPath.lineTo(mTwoPointX, mTwoPointY);
mTickPath.lineTo(mThreePointX, mThreePointY);
mTickPathMeasure.setPath(mTickPath, false);
// 设置X的第一条线路径
mForkOnePath.moveTo(mForkOneStartPointX, mForkOneStartPointY);
mForkOnePath.lineTo(mForkOneEndPointX, mForkOneEndPointY);
mForkOnePathMeasure.setPath(mForkOnePath, false);
// 设置X的第二条线路径
mForkTwoPath.moveTo(mForkTwoStartPointX, mForkTwoStartPointY);
mForkTwoPath.lineTo(mForkTwoEndPointX, mForkTwoEndPointY);
mForkTwoPathMeasure.setPath(mForkTwoPath, false);
// 圆环缩小动画
mCircleAnimator = ValueAnimator.ofFloat(0f, 1f);
mCircleAnimator.setDuration(CIRCLE_DURATION);
mCircleAnimator.setInterpolator(new LinearInterpolator());
mCircleAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
Log.d(TAG, "start");
}
@Override
public void onAnimationEnd(Animator animation) {
Log.d(TAG, "end");
if (mDrawType == CIRCLE_SUCCESS) { // 成功画勾
mDrawType = TICK;
} else if (mDrawType == CIRCLE_FAILED) { // 失败画叉
mDrawType = FORK;
}
mTickAnimator.start();// 圆环渐变动画结束之后开始打钩动画
}
@Override
public void onAnimationCancel(Animator animation) {
Log.d(TAG, "cancel");
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
mCircleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCirclePercent = (float) animation.getAnimatedValue();
invalidate();
}
});
// 打钩动画
mTickAnimator = ValueAnimator.ofFloat(0f, 1f);
mTickAnimator.setDuration(TICK_DURATION);
mTickAnimator.setInterpolator(new LinearInterpolator());
mTickAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
if (mOnLoadingCompleteListener != null) {
mOnLoadingCompleteListener.onLoadingComplete();
}
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
mTickAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mTickPercent = (float) animation.getAnimatedValue();
invalidate();
}
});
}
/**
* 圆环旋转动画
*/
private void rotate() {
mArcAnimator = ObjectAnimator.ofFloat(this, "rotation", ANGLE_START, ANGLE_END);
mArcAnimator.setInterpolator(new LinearInterpolator());
mArcAnimator.setDuration(ARC_DURATION);// 设置动画持续周期
mArcAnimator.setRepeatCount(-1);// 设置重复次数
mArcAnimator.start();
}
/**
* 圆环停止动画
*/
public void stop() {
if (mArcAnimator != null) {
mArcAnimator.cancel();
this.setRotation(ANGLE_START);
}
}
/**
* 加载中
*/
public void loading() {
rotate();
}
public void completeSuccess() {
completeSuccess(null);
}
/**
* 加载完成成功
*/
public void completeSuccess(OnLoadingCompleteListener listener) {
mDrawType = CIRCLE_SUCCESS;
// mCirclePaint.setColor();
setOnLoadingCompleteListener(listener);
stop();
// 圆圈由大到小绘制动画
mCircleAnimator.start();
}
public void completeFailed() {
mDrawType = CIRCLE_FAILED;
completeFailed(null);
}
/**
* 加载完成失败
*/
public void completeFailed(OnLoadingCompleteListener listener) {
mDrawType = CIRCLE_FAILED;
// mCirclePaint.setColor();
setOnLoadingCompleteListener(listener);
stop();
// 圆圈由大到小绘制动画
mCircleAnimator.start();
}
public void setOnLoadingCompleteListener(OnLoadingCompleteListener listener) {
if (listener != null) {
mOnLoadingCompleteListener = listener;
}
}
public interface OnLoadingCompleteListener {
void onLoadingComplete();
}
@Override
protected void onDraw(Canvas canvas) {
if (mDrawType == ARC) { // 画圆环
canvas.drawArc(mRec, ANGLE_START, ANGLE_END, false, mArcPaint);
} else if (mDrawType == CIRCLE_SUCCESS) { // 成功缩放圆
canvas.drawCircle(mCanvasWidth / 2, mCanvasHeight / 2, mArcRadius, mCircleSuccessFillPaint);
float radius = mArcRadius * (1 - mCirclePercent);// 动态半径
canvas.drawCircle(mCanvasWidth / 2, mCanvasHeight / 2, radius, mCirclePaint);
} else if (mDrawType == CIRCLE_FAILED) { // 失败缩放圆
canvas.drawCircle(mCanvasWidth / 2, mCanvasHeight / 2, mArcRadius, mCircleFailedFillPaint);
float radius = mArcRadius * (1 - mCirclePercent);// 动态半径
// 根据设置该view的高度,进行对所画图进行居中处理
canvas.drawCircle(mCanvasWidth / 2, mCanvasHeight / 2, radius, mCirclePaint);
} else if (mDrawType == TICK) { // 打钩动画
mTickPathMeasure.getSegment(0, mTickPercent * mTickPathMeasure.getLength(), mTickDynamicPath, true);
mTickDynamicPath.rLineTo(0, 0);
canvas.drawCircle(mCanvasWidth / 2, mCanvasHeight / 2, mArcRadius, mCircleSuccessFillPaint);
canvas.drawPath(mTickDynamicPath, mTickPaint);
} else if (mDrawType == FORK) { // 打叉动画
canvas.drawCircle(mCanvasWidth / 2, mCanvasHeight / 2, mArcRadius, mCircleFailedFillPaint);
mForkOnePathMeasure.getSegment(0, mTickPercent * mForkOnePathMeasure.getLength(), mForkOneDynamicPath,
true);
mForkOneDynamicPath.rLineTo(0, 0);
mForkTwoPathMeasure.getSegment(0, mTickPercent * mForkTwoPathMeasure.getLength(), mForkTwoDynamicPath,
true);
mForkTwoDynamicPath.rLineTo(0, 0);
canvas.drawPath(mForkOneDynamicPath, mForkPaint);
canvas.drawPath(mForkTwoDynamicPath, mForkPaint);
}
}
}