动画:加载--成功打钩或者失败打叉

最近做了一个仿支付宝支付成功的动画,用到了很多关于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);
}
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值