自定义View_一个加特效的圆形加载条

前几天在朋友圈看到了一个圆形的循环的加载效果,于是尝试着实现了一下,照例先放个GitHub传送门:https://github.com/SuperKotlin/CircleProgressBar
先来看一下效果图(fuck!在真机上是很流畅的,切成gif有点掉帧!)。

自定义View_一个加特效的圆形加载条_第1张图片
circleprogressbar.gif

看到这样的一个效果,该如何去实现呢?

总体思路


1.自定义属性:圆弧的颜色和宽度,一开始加载进度的位置;
2.画出需要的效果:设置不同的角度,使用画笔paint在canvas上绘制圆弧;

好的,接下来我们就按照这个思路一步一步的撸:

1.自定义我们需要的属性:


在values文件夹下新建文件attrs.xml

      
        
        
        
        
        
        
            
            
            
            
        
    

然后在自定义View中获取并设置这些属性:
首先,来声明我们的属性类型:

    private Paint mPaintCurrent;//圆弧画笔
    private float mPaintWidth;//画笔宽度
    private int mPaintColor = Color.RED;//画笔颜色
    private int location;//从哪个位置开始
    private float startAngle;//开始角度

获取自定义属性值:

//获取属性值
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressView);
location = array.getInt(R.styleable.CircleProgressView_progress_location, 2);
mPaintWidth = array.getDimension(R.styleable.CircleProgressView_progress_paint_width, dip2px(context, 4));//默认4dp
mPaintColor = array.getColor(R.styleable.CircleProgressView_progress_paint_color, mPaintColor);
array.recycle();

初始化和赋值:

//画笔->进度圆弧
mPaintCurrent = new Paint();
mPaintCurrent.setAntiAlias(true);
mPaintCurrent.setStrokeWidth(mPaintWidth);
mPaintCurrent.setStyle(Paint.Style.STROKE);
mPaintCurrent.setColor(mPaintColor);
mPaintCurrent.setStrokeCap(Paint.Cap.ROUND);
if (location == 1) {
    startAngle = -180;
} else if (location == 2) {//默认从上侧开始
    startAngle = -90;
} else if (location == 3) {
    startAngle = 0;
} else if (location == 4) {
    startAngle = 90;
}

2.画出需要的效果:设置不同的角度,使用画笔paint在canvas上绘制圆弧:


注意:绘制操作是在onDraw(Canvas canvas)方法中。

我们使用cancas的drawArc()方法来绘制圆弧,不清楚这个方法怎么使用以及里面的参数是什么意思的请看:自定义View_手把手教你撸出一个圆形加载进度条里面有详细介绍!
首先来分析一下绘制这个圆弧的过程,我们分为两步,分别如图:

自定义View_一个加特效的圆形加载条_第2张图片
步骤1.png
自定义View_一个加特效的圆形加载条_第3张图片
步骤2.png

整个的效果可以拆分为这两个,第一个是从0°绘制到360°的位置,刚好是一个完整的圆形。第二个是从一个完整的圆形360°慢慢的减少到0°。至于如何让他循环不停地完成这两个过程,我们写个定时器不停地调用invalidate()就OK了。

第一个过程:我们只需要使drawArc方法中的sweepAngle(扫过的角度)从0增大到360就OK了,可以理解为按照顺时针方向一点一点的画出圆弧;

第二个过程:可以理解为按照顺时针方向一点一点的擦除圆弧,那么它的sweepAngle(扫过的角度)从360减小到0就OK了?错!!!,因为我们是顺时针擦除,所以应该加个负号,即从-360减小到-0就OK了。
OK,分析结束开始撸代码:我们定义一个变量sweepAngle=0,只需要在定时器里判断:

sweepAngle++;
    if (sweepAngle== 360) {
         sweepAngle= -360;
    }
invalidate();

然后绘制:

canvas.drawArc(rectF, startAngle, sweepAngle, false, mPaintCurrent);

到此算是完成了整个效果,但是你会发现绘制的很慢,因为定时器就算每隔1毫秒绘制一次那么1毫秒也就仅仅绘制了1°而已,所以我们来设置一个比例参数mCurrent=0。

sweepAngle++;
    if (sweepAngle== 100) {
         sweepAngle= -100;
    }
invalidate();

然后绘制:

float sweepAngle = 360 * mCurrent / 100;
canvas.drawArc(rectF, startAngle, sweepAngle, false, mPaintCurrent);

下面我把全部代码贴上来:

attrs.xml



    
        
        
        
        
        
        
            
            
            
            
        
    


activity_main.xml



    


CircleProgressView.java

package com.example.zhuyong.circleprogressbar;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by zhuyong on 2017/7/2.
 */

public class CircleProgressBar extends View {

    private int mCurrent = 0;//当前进度
    private Paint mPaintCurrent;
    private float mPaintWidth;//画笔宽度
    private int mPaintColor = Color.RED;//画笔颜色
    private int location;//从哪个位置开始
    private float startAngle;//开始角度
    private int TIME = 10;//默认每隔10ms重绘一次
    Handler handler = new Handler();
    Runnable runnable = new Runnable() {

        @Override
        public void run() {
            try {
                handler.postDelayed(this, TIME);
                mCurrent++;
                if (mCurrent == 100) {
                    mCurrent = -100;
                }
                invalidate();
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    };

    public CircleProgressBar(Context context) {
        this(context, null);
    }

    public CircleProgressBar(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CircleProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //获取属性值
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressBar);
        location = array.getInt(R.styleable.CircleProgressBar_progress_location, 2);
        mPaintWidth = array.getDimension(R.styleable.CircleProgressBar_progress_paint_width, dip2px(context, 4));//默认4dp
        mPaintColor = array.getColor(R.styleable.CircleProgressBar_progress_paint_color, mPaintColor);
        array.recycle();
        initPaint();
    }


    private void initPaint() {
        //画笔->进度圆弧
        mPaintCurrent = new Paint();
        mPaintCurrent.setAntiAlias(true);
        mPaintCurrent.setStrokeWidth(mPaintWidth);
        mPaintCurrent.setStyle(Paint.Style.STROKE);
        mPaintCurrent.setColor(mPaintColor);
        mPaintCurrent.setStrokeCap(Paint.Cap.ROUND);

        if (location == 1) {
            startAngle = -180;
        } else if (location == 2) {//默认从上侧开始
            startAngle = -90;
        } else if (location == 3) {
            startAngle = 0;
        } else if (location == 4) {
            startAngle = 90;
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        int size = width > height ? height : width;
        setMeasuredDimension(size, size);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制当前进度
        RectF rectF = new RectF(mPaintWidth / 2, mPaintWidth / 2, getWidth() - mPaintWidth / 2, getHeight() - mPaintWidth / 2);
        float sweepAngle = 360 * mCurrent / 100;
        canvas.drawArc(rectF, startAngle, sweepAngle, false, mPaintCurrent);
    }

    /**
     * 开始
     */
    public void start() {
        handler.postDelayed(runnable, TIME); //每隔TIMEms执行
    }

    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

}


MainActivity.java

package com.example.zhuyong.circleprogressbar;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    
    private CircleProgressBar mCircleProgressView;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mCircleProgressView = (CircleProgressBar) findViewById(R.id.circle_view);
        mCircleProgressView.start();
    }
}

GitHub传送门:源码

你可能感兴趣的:(自定义View_一个加特效的圆形加载条)