Android 菊花加载View

最近想把项目中用到的东西都写出来,刚好做上传时手写了一个加载控件,在这就写下来。效果如图:


截图.png

看到这个,会的人就不说了,很简单,不会的人就想着网上找,但是网上找的有不如意,而且改起来还要读懂别人代码,其他的就要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里面我们先画灰色的,我们先上个图分析下:


图片.png

我们实际看到的刻度线,是不是都夹杂在大圆与小圆的中间?只看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);
        }
    }
}

你可能感兴趣的:(Android 菊花加载View)