最近想把项目中用到的东西都写出来,刚好做上传时手写了一个加载控件,在这就写下来。效果如图:
看到这个,会的人就不说了,很简单,不会的人就想着网上找,但是网上找的有不如意,而且改起来还要读懂别人代码,其他的就要UI切图,做成帧动画,但是这样的东西还是没必要做成帧动画,而且从这个入手还可以回顾下自定义view的步骤,所以还是动手写一个呗。
创建类,集成View实现其构造方法。
public CircularLinesProgress(Context context) {
this(context, null);
}
public CircularLinesProgress(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CircularLinesProgress(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
View的绘制流程是onMeasure、onDraw、onLayout,onMeasure是计算我们子View要多大的设置,这里我们的加载控件肯定是一个正方形,不管布局怎么设置宽高,我们都取最小的,木桶效应将其搞成个正方形,如代码:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeigth = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(mWidth >= mHeigth ? mHeigth : mWidth, mWidth >= mHeigth ? mHeigth : mWidth);
}
然后就是画,就需要我们实现onDraw方法,里面自带一个画布,那我们的画笔呢?而且画笔还有样式要设置,找一个实例化画笔最好的地方,就莫过于构造方法了。
public CircularLinesProgress(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint();
mPaint.setDither(true);
mPaint.setAntiAlias(true);
mPaint.setColor(Color.GRAY);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mLineWidth);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mChangePaint = new Paint();
mChangePaint.setDither(true);
mChangePaint.setAntiAlias(true);
mChangePaint.setColor(Color.RED);
mChangePaint.setStyle(Paint.Style.STROKE);
mChangePaint.setStrokeWidth(mLineWidth);
mChangePaint.setStrokeCap(Paint.Cap.ROUND);
mChangePaint.setStrokeJoin(Paint.Join.ROUND);
}
然后就是画,onDraw里面我们先画灰色的,我们先上个图分析下:
我们实际看到的刻度线,是不是都夹杂在大圆与小圆的中间?只看12点方向的刻度,线的两端X值都是一样的是圆形也是控件的宽的一半。然后看Y值,起点的Y是不是圆心的Y往上减去一定数值?终点是不是要减去更多?所以顺着这个思路我们就能控制好画刻度了,这个一定数值我取的是控件高度一半的百分比。那么第二根第三根怎么办?唉?是不是只要将画布转一个角度在划线是不是就可以了?至于转多少度,那就是360度除以我们刻度线的总数了。如代码:
@Override
protected void onDraw(Canvas canvas) {
int x = mWidth / 2;
int y = mHeigth / 2;
int r = (int) (mWidth * 0.45);
canvas.save();
for (int i = 0; i < mLinesNumber; i++) {
//绘制下层菊花
canvas.drawLine(x, y - r, x, (float) (y - r + x * 0.4), mPaint);
canvas.rotate(360 / mLinesNumber, x, y);
}
canvas.restore();
}
这就是画灰色刻度。
那么就轮到画红色进度刻度了,仔细观察其实就是在灰色上覆盖一个红色的,进度的话用属性动画控制,如代码:
public void start() {
if(valueAnimator==null){
valueAnimator = ValueAnimator.ofFloat(0, 1.0f);
valueAnimator.setDuration(1000);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float value = (float) valueAnimator.getAnimatedValue();
setCurrentProgress(value);
}
});
valueAnimator.start();
}
}
设置当前进度,并调用刷新
private float mCurrentProgress = 0f;
public void setCurrentProgress(float mCurrentProgress) {
this.mCurrentProgress = mCurrentProgress;
postInvalidate();
}
然后再上面刚画灰色的地方画红色
@Override
protected void onDraw(Canvas canvas) {
int x = mWidth / 2;
int y = mHeigth / 2;
int r = (int) (mWidth * 0.45);
canvas.save();
for (int i = 0; i < mLinesNumber; i++) {
//绘制下层菊花
canvas.drawLine(x, y - r, x, (float) (y - r + x * 0.4), mPaint);
canvas.rotate(360 / mLinesNumber, x, y);
}
//应该画多少
int currentContent = (int) (mLinesNumber * mCurrentProgress);
for (int i = currentContent; i > 0; i--) {
//绘制下层菊花
canvas.drawLine(x, y - r, x, (float) (y - r + x * 0.4), mChangePaint);
canvas.rotate(360 / mLinesNumber, x, y);
}
canvas.restore();
}
这样我们就能完成一次循环了,但是UI的建议是循环并且红色和灰色交替作为进度刻度,这样看着顺眼。那么动画就要加个循环了
public void start() {
if(valueAnimator==null){
valueAnimator = ValueAnimator.ofFloat(0, 1.0f);
valueAnimator.setDuration(1000);
valueAnimator.setRepeatCount(-1);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationRepeat(Animator animation) {
mRunContent += 1;
}
});
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float value = (float) valueAnimator.getAnimatedValue();
setCurrentProgress(value);
}
});
valueAnimator.start();
}
}
mRunContent 变量就是控制红灰交替进度了,修改下onDraw
@Override
protected void onDraw(Canvas canvas) {
mPaint.setStrokeWidth(mLineWidth);
mChangePaint.setStrokeWidth(mLineWidth);
int x = mWidth / 2;
int y = mHeigth / 2;
int r = (int) (mWidth * 0.45);
canvas.save();
if (mRunContent % 2 == 0) {
for (int i = 0; i < mLinesNumber; i++) {
//绘制下层菊花
canvas.drawLine(x, y - r, x, (float) (y - r + x * 0.4), mPaint);
canvas.rotate(360 / mLinesNumber, x, y);
}
//应该画多少
int currentContent = (int) (mLinesNumber * mCurrentProgress);
for (int i = currentContent; i > 0; i--) {
//绘制下层菊花
canvas.drawLine(x, y - r, x, (float) (y - r + x * 0.4), mChangePaint);
canvas.rotate(360 / mLinesNumber, x, y);
}
} else {
for (int i = 0; i < mLinesNumber; i++) {
//绘制下层菊花
canvas.drawLine(x, y - r, x, (float) (y - r + x * 0.4), mChangePaint);
canvas.rotate(360 / mLinesNumber, x, y);
}
//应该画多少
int currentContent = (int) (mLinesNumber * mCurrentProgress);
for (int i = currentContent; i > 0; i--) {
//绘制下层菊花
canvas.drawLine(x, y - r, x, (float) (y - r + x * 0.4), mPaint);
canvas.rotate(360 / mLinesNumber, x, y);
}
}
canvas.restore();
}
好,到此为止,效果达到,但是我们用到了动画,就要记得销毁动画,所以我们要做一个销毁方法,方便与Activity的onDestory绑定。
public void cancel(){
if(valueAnimator!=null){
valueAnimator.cancel();
valueAnimator.end();
valueAnimator.addUpdateListener(null);
valueAnimator.addListener(null);
}
}
完整代码:
public class CircularLinesProgress extends View {
private int mLinesNumber = 20;
private int mLineWidth = 6;
private int mWidth;
private int mHeigth;
private Paint mPaint;
private float mCurrentProgress = 0f;
private Paint mChangePaint;
private int mRunContent = 0;
private ValueAnimator valueAnimator;
public void setCurrentProgress(float mCurrentProgress) {
this.mCurrentProgress = mCurrentProgress;
postInvalidate();
}
public CircularLinesProgress(Context context) {
this(context, null);
}
public CircularLinesProgress(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CircularLinesProgress(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint();
mPaint.setDither(true);
mPaint.setAntiAlias(true);
mPaint.setColor(Color.GRAY);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mLineWidth);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mChangePaint = new Paint();
mChangePaint.setDither(true);
mChangePaint.setAntiAlias(true);
mChangePaint.setColor(Color.RED);
mChangePaint.setStyle(Paint.Style.STROKE);
mChangePaint.setStrokeWidth(mLineWidth);
mChangePaint.setStrokeCap(Paint.Cap.ROUND);
mChangePaint.setStrokeJoin(Paint.Join.ROUND);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeigth = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(mWidth >= mHeigth ? mHeigth : mWidth, mWidth >= mHeigth ? mHeigth : mWidth);
mLineWidth = mHeigth / mLinesNumber;
}
@Override
protected void onDraw(Canvas canvas) {
mPaint.setStrokeWidth(mLineWidth);
mChangePaint.setStrokeWidth(mLineWidth);
int x = mWidth / 2;
int y = mHeigth / 2;
int r = (int) (mWidth * 0.45);
canvas.save();
if (mRunContent % 2 == 0) {
for (int i = 0; i < mLinesNumber; i++) {
//绘制下层菊花
canvas.drawLine(x, y - r, x, (float) (y - r + x * 0.4), mPaint);
canvas.rotate(360 / mLinesNumber, x, y);
}
//应该画多少
int currentContent = (int) (mLinesNumber * mCurrentProgress);
for (int i = currentContent; i > 0; i--) {
//绘制下层菊花
canvas.drawLine(x, y - r, x, (float) (y - r + x * 0.4), mChangePaint);
canvas.rotate(360 / mLinesNumber, x, y);
}
} else {
for (int i = 0; i < mLinesNumber; i++) {
//绘制下层菊花
canvas.drawLine(x, y - r, x, (float) (y - r + x * 0.4), mChangePaint);
canvas.rotate(360 / mLinesNumber, x, y);
}
//应该画多少
int currentContent = (int) (mLinesNumber * mCurrentProgress);
for (int i = currentContent; i > 0; i--) {
//绘制下层菊花
canvas.drawLine(x, y - r, x, (float) (y - r + x * 0.4), mPaint);
canvas.rotate(360 / mLinesNumber, x, y);
}
}
canvas.restore();
}
public void start() {
if(valueAnimator==null){
valueAnimator = ValueAnimator.ofFloat(0, 1.0f);
valueAnimator.setDuration(1000);
valueAnimator.setRepeatCount(-1);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationRepeat(Animator animation) {
mRunContent += 1;
}
});
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float value = (float) valueAnimator.getAnimatedValue();
setCurrentProgress(value);
}
});
valueAnimator.start();
}
}
public void cancel(){
if(valueAnimator!=null){
valueAnimator.cancel();
valueAnimator.end();
valueAnimator.addUpdateListener(null);
valueAnimator.addListener(null);
}
}
}