先上效果图:
一、PathMeasure
在绘制画布时,我们通常会使用Path对象来勾勒出绘制的路径,而PathMeasure可以让我们得到路径上的一些信息,比如获取路径上点的坐标,截取路径上的某一段小路径等。。
它主要有既个方法:
setPath(Path path, boolean forceClosed);
与一个Path对象绑定,forceClose为true时,不管关联的Path是否是闭合的,都会被闭合。
getLength()
获取路径的长度
getPosTan(float distance, float pos[],float tan[])
distance 为一个 0 - getLength() 之间的值,根据这个距离值计算出当前点的xy坐标和正切值封装到 pos和tan当中;
boolean getSegment (float startD, float stopD, Path dst, boolean startWithMoveTo)
判断截取路径成功时返回true;
startD为开始截取位置距离 Path 起点的长度
stopD 结束截取位置距离 Path 起点的长度
取值范围: 0 <= startD < stopD <= Path总长度getLength(),超过范围截取不成功截取的 Path 将会添加到 dst 中
startWithMoveTo 起始点是否使用 moveTo,用于保证截取的 Path 第一个点位置不变(一般为true)
在默认开启硬件加速的情况下,更改 dst 的内容后可能绘制会出现问题,可以给 dst 添加一个操作:dst.lineTo(0, 0)
二、自定义动画控件
大概思路就是:
创建一个勾勒空心圆的Path对象
再根据上面的Path对象创建PathMeasure对象
最后再通过属性动画不断动态获取startD和stopD的值,并更新View
public class LoadingCircleView extends View {
private Path path;
private PathMeasure pathMeasure;
private Path dst; // 被截取的路径
private Paint paint; // 画笔
// View的宽高
private int height;
private int width;
private float radius; // 空心圆的半径
private float mLength; // path路径的长度
ValueAnimator valueAnimator; // 属性动画
private float mAnimatorValue; // 属性动画返回的百分比
private float stop; // 截取路径时的stopD值
private float start; // 截取路径时的startD值
public LoadingCircleView(Context context) {
super(context);
init(); // 初始化
}
public LoadingCircleView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public LoadingCircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public LoadingCircleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
radius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,21,getContext().getResources().getDisplayMetrics()); // 初始化半径
path = new Path();
dst = new Path();
paint = new Paint();
pathMeasure = new PathMeasure();
// 设置画笔属性
paint.setAntiAlias(true);
paint.setColor(0xbfe46d32);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND); paint.setStrokeWidth(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,3.5f,getContext().getResources().getDisplayMetrics()));
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
// 勾勒空心圆
path.reset();
path.addCircle(width/2,height/2,radius, Path.Direction.CW);
// 生成pathMeasure对象
pathMeasure.setPath(path,true);
// 获取path的长度
mLength = pathMeasure.getLength();
// 通过属性动画取得百分比值,并更新View
valueAnimator = ValueAnimator.ofFloat(0,1);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 获取动画进行的百分比
mAnimatorValue = (float) animation.getAnimatedValue();
postInvalidate(); // 更新界面
}
});
// 设置动画的属性
valueAnimator.setDuration(2100);
valueAnimator.setRepeatCount(1000);
valueAnimator.setRepeatMode(ValueAnimator.RESTART);
valueAnimator.start();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// 只对EXACTLY的条件的宽高处理
setMeasuredDimension(Math.min(widthSize,heightSize),Math.min(widthSize,heightSize));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 为加强动画效果,每次对画布旋转不同角度
canvas.rotate(360.0f*mAnimatorValue-45.0f,width/2,height/2);
// 初始化截取的路径
dst.reset();
dst.lineTo(0,0); // 消除硬件加速的影响
// 更新截取的开始值和结束值:当mAnimatorValue为0或1时,两个值相等
stop = mAnimatorValue*mLength;
start = (float) (stop - ((0.5 - Math.abs(mAnimatorValue - 0.5)) * mLength));
// 截取路径后,并绘制路径
pathMeasure.getSegment(start, stop, dst, true);
canvas.drawPath(dst,paint);
}
}