先看效果图:默认效果:
可以自定义修改一些属性:
一.实现思路:
思路其实很简单:相当于是内外层两个圆弧在不停地旋转,将内外两个圆弧绘制出来,通过改变绘制起点位置的角度,模拟动画效果。做一个定时器,不断改变圆弧的绘制起点,然后重新绘制整个View,就可以模拟出动画效果。在定时器结束的时候,调用自己重新开始,动画就可以一直执行下去。
二.实现代码:
1.首先需要继承View:
public class CustomLoadingView extends View
2.设置一些默认的属性
//定义画笔
private Paint paint;
//倒计时的时间
private int countDownTimeTotal = 1500;//总的倒计时默认是1.5秒
//view默认的长度和高度
private int defaultWidth = 200;
private int defaultHeight = 300;
//内外层圆弧的颜色,默认都为黑色
private int outsideArcColor = Color.BLACK;
private int insideArcColor = Color.BLACK;
//内外层圆弧的宽度
private float outsideArcWidth = 15f;
private float insideArcWidth = 15f;
//内外层圆弧转过的角度,
private float outsideArcAngle = 330f;
private float insideArcAngle = 60f;
//默认起始角度
private float defaultStartAngle = 105;
//计算改变之后的起始角度
private float startAngle = defaultStartAngle;
3.为了增强扩展性,可以通过xml文件定义一些自己需要的属性,内外层圆弧的颜色,角度,起始角度,动画执行一次的时间都
可以自定义,因此需要在构造方法中获取自己定义的属性:
public CustomLoadingView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//取出自定义的属性并赋值
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.CustomLoadingView);
countDownTimeTotal = array.getInteger(R.styleable.CustomLoadingView_round_once_time,1500);
outsideArcColor = array.getColor(R.styleable.CustomLoadingView_round_outside_color,Color.BLACK);
insideArcColor = array.getColor(R.styleable.CustomLoadingView_round_inside_color,Color.BLACK);
outsideArcAngle = array.getFloat(R.styleable.CustomLoadingView_round_outside_angle,300f);
insideArcAngle = array.getFloat(R.styleable.CustomLoadingView_round_inside_angle,60f);
defaultStartAngle = array.getFloat(R.styleable.CustomLoadingView_round_start_angle,105);
array.recycle();
init();
}
4.既然是自定义的属性,也就需要对这些属性进行声明,声明放在attr.xml文件中:
<!--自定义等待view中的属性-->
<declare-styleable name="CustomLoadingView">
<!--loading动画执行一次所需的时间-->
<attr name="round_once_time" format="integer"></attr>
<!--外层圆弧的颜色-->
<attr name="round_outside_color" format="color"></attr>
<!--内层圆弧的颜色-->
<attr name="round_inside_color" format="color"></attr>
<!--内层圆弧的角度-->
<attr name="round_inside_angle" format="float"></attr>
<!--外层圆弧的角度默认为330度-->
<attr name="round_outside_angle" format="float"></attr>
<!--圆弧旋转的起始角度-->
<attr name="round_start_angle" format="float"></attr>
</declare-styleable>
5.定义一个倒计时,通过倒计时来对整个View进行重绘:
private CountDownTimer countDownTimer;
6.在init()方法中执行实际的倒计时操作,init()方法可以在任意位置调用
private void init() {
if (countDownTimer != null) {
countDownTimer.cancel();
countDownTimer.onFinish();
countDownTimer = null;
}
countDownTimer = new CountDownTimer(countDownTimeTotal, 10) {
@Override
public void onTick(long millisUntilFinished) {
//计算当前剩余时间的比例
float radio = (float) millisUntilFinished / (float) countDownTimeTotal;
float addAngle = 360 - 360 * radio;
//根据比较改变开始位置的角度
startAngle = defaultStartAngle;
startAngle = defaultStartAngle + addAngle;
invalidate();
}
@Override
public void onFinish() {
if(countDownTimer != null){
countDownTimer.start();
}
}
};
countDownTimer.start();
}
7.最关键的是onDraw方法,在这里对两个圆环进行绘制,这里需要明白圆弧正反方向的绘制,否则绘制出来的是同一个方向旋转
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint = new Paint();
//首先绘制最外层的圆
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(outsideArcWidth);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setColor(outsideArcColor);
Path path = new Path();
path.addArc(10, 10, defaultWidth - 10, defaultHeight - 10, startAngle, outsideArcAngle);
canvas.drawPath(path, paint);
//绘制内层的圆
paint.reset();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(insideArcWidth);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setColor(insideArcColor);
canvas.drawArc(30 + outsideArcWidth, 30 + outsideArcWidth, defaultWidth - (30 + outsideArcWidth), defaultHeight - (30 + outsideArcWidth), (360 - startAngle), -insideArcAngle,false,paint);
}
8.到这里,整个view就绘制完成,可以直接在代码中使用,同时,也可以堆属性提供getter/setter方法,在外部(activity或者fragment中调用),同时刷新init()方法就可以了
9.全部代码如下:
package com.fanjuan.project.smartcarsrecignition;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Build;
import android.os.CountDownTimer;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
/*
* 自定义一个等待的动画
* */
public class CustomLoadingView extends View {
//定义画笔
private Paint paint;
//倒计时的时间
private int countDownTimeTotal = 1500;//总的倒计时默认是1.5秒
//view默认的长度和高度
private int defaultWidth = 200;
private int defaultHeight = 300;
//内外层圆弧的颜色,默认都为黑色
private int outsideArcColor = Color.BLACK;
private int insideArcColor = Color.BLACK;
//内外层圆弧的宽度
private float outsideArcWidth = 15f;
private float insideArcWidth = 15f;
//内外层圆弧转过的角度,
private float outsideArcAngle = 330f;
private float insideArcAngle = 60f;
//默认起始角度
private float defaultStartAngle = 105;
//计算改变之后的起始角度
private float startAngle = defaultStartAngle;
public CustomLoadingView(Context context) {
this(context,null);
}
public CustomLoadingView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//取出自定义的属性并赋值
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.CustomLoadingView);
countDownTimeTotal = array.getInteger(R.styleable.CustomLoadingView_round_once_time,1500);
outsideArcColor = array.getColor(R.styleable.CustomLoadingView_round_outside_color,Color.BLACK);
insideArcColor = array.getColor(R.styleable.CustomLoadingView_round_inside_color,Color.BLACK);
outsideArcAngle = array.getFloat(R.styleable.CustomLoadingView_round_outside_angle,300f);
insideArcAngle = array.getFloat(R.styleable.CustomLoadingView_round_inside_angle,60f);
defaultStartAngle = array.getFloat(R.styleable.CustomLoadingView_round_start_angle,105);
array.recycle();
init();
}
public CustomLoadingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs);
}
//定义一个倒计时,用于执行动画效果,每隔10毫秒执行一次
private CountDownTimer countDownTimer;
private void init() {
if (countDownTimer != null) {
countDownTimer.cancel();
countDownTimer.onFinish();
countDownTimer = null;
}
countDownTimer = new CountDownTimer(countDownTimeTotal, 10) {
@Override
public void onTick(long millisUntilFinished) {
//计算当前剩余时间的比例
float radio = (float) millisUntilFinished / (float) countDownTimeTotal;
float addAngle = 360 - 360 * radio;
//根据比较改变开始位置的角度
startAngle = defaultStartAngle;
startAngle = defaultStartAngle + addAngle;
invalidate();
}
@Override
public void onFinish() {
if(countDownTimer != null){
countDownTimer.start();
}
}
};
countDownTimer.start();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint = new Paint();
//首先绘制最外层的圆
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(outsideArcWidth);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setColor(outsideArcColor);
Path path = new Path();
path.addArc(10, 10, defaultWidth - 10, defaultHeight - 10, startAngle, outsideArcAngle);
canvas.drawPath(path, paint);
//绘制内层的圆
paint.reset();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(insideArcWidth);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setColor(insideArcColor);
canvas.drawArc(30 + outsideArcWidth, 30 + outsideArcWidth, defaultWidth - (30 + outsideArcWidth), defaultHeight - (30 + outsideArcWidth), (360 - startAngle), -insideArcAngle,false,paint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
defaultWidth = dealReadSize(widthMeasureSpec, defaultWidth);
defaultHeight = dealReadSize(heightMeasureSpec, defaultHeight);
//这两个哪个小就用哪个
if (defaultHeight > defaultWidth) {
defaultHeight = defaultWidth;
} else {
defaultWidth = defaultHeight;
}
setMeasuredDimension(defaultWidth, defaultHeight);
}
//根据不同的model处理不同的尺寸
private int dealReadSize(int measureSpec, int defaultSize) {
int result = 0;
int model = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
switch (model) {
case MeasureSpec.UNSPECIFIED:
//不限制,使用默认的尺寸
result = defaultSize;
break;
case MeasureSpec.AT_MOST:
//上限
result = Math.min(defaultSize, size);
break;
case MeasureSpec.EXACTLY:
result = size;
break;
}
return result;
}
//设置结束
public void setFinish(){
if(countDownTimer != null){
countDownTimer.onFinish();
countDownTimer.cancel();
countDownTimer = null;
}
}
}