这个其实是改编了 一个腾讯开源 qmui 里面的效果,但是我们UI 要圆形的就改了下,项目里面是直接复制源码,然后将里面的drawline 改成drawcircle 就好了。
public class LoadingCircleView extends View {
// 大圆的半径
private int mWidth;
private int mHeight;
private boolean init = false;
//圆球的个数
private int mCircleCount;
private int mCircleWidth;
private Paint mPaint;
private int mPaintColor;
//这里可以多加一个attr 属性值 用来设置大圆的半径(小于getwidth)
private int mSize;
//动画
private ValueAnimator mValueAnimator;
private int mAnimateValue;//用来计算 旋转角度
private ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mAnimateValue = (int) animation.getAnimatedValue();
invalidate();
}
};
public LoadingCircleView(Context context) {
this(context, null);
}
public LoadingCircleView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public LoadingCircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context, attrs, defStyleAttr);
}
private void initView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
TypedArray typedArray = context.obtainStyledAttributes(R.styleable.LoadingView);
mCircleCount = typedArray.getInteger(R.styleable.LoadingView_loading_circle_count, 10);
mPaintColor = typedArray.getColor(R.styleable.LoadingView_loading_color, Color.BLUE);
//回收
typedArray.recycle();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (!init) {
init = true;
init();
}
}
private void init() {
mWidth = getMeasuredWidth() / 4;
mHeight = getMeasuredHeight();
mPaint = new Paint();
mPaint.setColor(mPaintColor);
mPaint.setStrokeWidth(1);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
//view 大圆圆心 切小圆球的两条切线的圆心角 小于 360/circlecount 极端就是小圆形相切
// 这里要注意 Math.sin() 参数要传弧度值 看源码注释
double sin = Math.sin(360 / mCircleCount / 2 * Math.PI / 180);
//相切的时候 公式 (R-r)*sinA = r; 也可以排松一点 比这个小就好了
mCircleWidth = (int) (sin * mWidth / 2 / (1 + sin));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
drawCircles(canvas);
canvas.restoreToCount(saveCount);
}
private void drawCircles(Canvas canvas) {
//1.动画的时候需要旋转
canvas.rotate(360 / mCircleCount * mAnimateValue,getWidth()/2,getHeight()/2);
//2.将坐标平移到 外圆中心
canvas.translate(getMeasuredWidth() / 2, mHeight / 2);
//3.画圆球 每次旋转 360/count
for (int i = 0; i < mCircleCount; i++) {
mPaint.setAlpha(255 * (i + 1) / mCircleCount);
//圆心坐标 从最下面的开始画
canvas.drawCircle(0, mWidth / 2 - mCircleWidth, mCircleWidth, mPaint);
canvas.rotate(360 / mCircleCount);
}
}
private void initAnimator() {
mValueAnimator = ValueAnimator.ofInt(0, mCircleCount - 1);
mValueAnimator.addUpdateListener(mAnimatorUpdateListener);
mValueAnimator.setDuration(500);
mValueAnimator.setRepeatMode(ValueAnimator.RESTART);
mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
mValueAnimator.setInterpolator(new LinearInterpolator());
}
//加上旋转动画
private void startAnimate() {
if (mValueAnimator == null) {
initAnimator();
}
if (!mValueAnimator.isStarted()) {
mValueAnimator.start();
}
}
private void stopAnimate() {
if (mValueAnimator != null) {
mValueAnimator.removeUpdateListener(mAnimatorUpdateListener);
mValueAnimator.removeAllUpdateListeners();
mValueAnimator.cancel();
mValueAnimator = null;
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
startAnimate();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
stopAnimate();
}
@Override
protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
if (visibility == VISIBLE) {
startAnimate();
} else {
stopAnimate();
}
}
}
里面的自定义属性就不贴了,没什么难度。
总结:canvas.translate 和 rotate 是相当好用,不然还要递归去计算坐标,想想头也是大了,现在只用找到一个最好找的 直接旋转画布就搞定了。