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);
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,820评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,648评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,324评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,714评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,724评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,328评论 1 310
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,897评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,804评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,345评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,431评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,561评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,238评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,928评论 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,417评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,528评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,983评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,573评论 2 359

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,296评论 25 707
  • 虔诚地做更好的自己 你要多出去走一走 多去见见不同的朋友 多去认识一些多彩的世界 认真看完一本书 少食多餐 学会温...
  • 在社会感增强后 我醒了 却又老了 我的快乐原则被剥夺 留给我一个现实的契约 我脆弱的心 却在扑通扑通的跳个不停 现...
    梁运生阅读 139评论 0 0