天天看点

android 自定义圆形渐变进度条 一个自定义的圆形颜色渐变进度条

 一个自定义的圆形颜色渐变进度条

首先,先上效果图,有图有真相

android 自定义圆形渐变进度条 一个自定义的圆形颜色渐变进度条

自定义基础的知识就不讲了,各路大神都有说明,下面直接说思路和核心的代码

  •   思路

    观察view,都是需要canvas去画。大概分为4部分

  1. 内圆
  2. 圆弧背景
  3. 圆弧
  4. 发光小圆

上代码,每个view都用一个Paint去画,定义4个Paint,初始化Paint属性,属性不明白的童鞋可以去百度。主要就是设置颜色,画笔宽度,画笔风格,是否抗锯齿。

/**
 * 初始化画笔
 */
private void initPaint() {
        // 内圆
        mCirclePaint = new Paint();
        mCirclePaint.setAntiAlias(true);
        mCirclePaint.setColor(Color.BLUE);
        mCirclePaint.setStyle(Paint.Style.FILL);

        // 圆弧背景
        mBgArcPaint = new Paint();
        mBgArcPaint.setAntiAlias(true);
        mBgArcPaint.setColor(Color.WHITE);
        mBgArcPaint.setStyle(Paint.Style.STROKE);
        mBgArcPaint.setStrokeWidth(mBgArcWidth);

        // 圆弧
        mArcPaint = new Paint();
        mArcPaint.setAntiAlias(true);
        mArcPaint.setStyle(Paint.Style.STROKE);
        mArcPaint.setStrokeWidth(mArcWidth);
        mArcPaint.setStrokeCap(Paint.Cap.ROUND);

        // 发光小圆
        mSmallCirclePaint = new Paint();
        mSmallCirclePaint.setAntiAlias(true);
        mSmallCirclePaint.setColor(getResources().getColor(R.color.color_7cffff));
        mSmallCirclePaint.setStyle(Paint.Style.FILL);
        mSmallCirclePaint.setMaskFilter(new BlurMaskFilter(mSmallRadius / 2, BlurMaskFilter.Blur.SOLID));
    }
           
  •  onSizeChange()

在onSizeChange() 中,需要计算所画view的一些属性,例如圆心,半径和弧的边界等,

画圆,需要有圆心坐标,半径,画弧需要圆心坐标,所画弧的角度,弧边界,我们这个例子就是用的这种方法。

废话少说,上代码解析

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {

        super.onSizeChanged(w, h, oldw, oldh);
        //1.求圆弧和背景圆弧的最大宽度
        float maxArcWidth = Math.max(mArcWidth, mBgArcWidth);
        //2.求最小值作为实际值,这是直径
        int minSize = Math.min(w - getPaddingLeft() - getPaddingRight() - 2 * (int) maxArcWidth,

                h - getPaddingTop() - getPaddingBottom() - 2 * (int) maxArcWidth);

        //3.内圆半径: 进度圆环内半径 - 中间空余距离 = 内圆半径
        mRadius = minSize / 2 - 28;

        //4.获取圆的相关参数
        mCenterPoint.x = w / 2;
        mCenterPoint.y = h / 2;

        //5.绘制进度圆弧的边界
        mRectF.left = mCenterPoint.x - mRadius - maxArcWidth / 2;
        mRectF.top = mCenterPoint.y - mRadius - maxArcWidth / 2;
        mRectF.right = mCenterPoint.x + mRadius + maxArcWidth / 2;
        mRectF.bottom = mCenterPoint.y + mRadius + maxArcWidth / 2;

        //6.绘制背景圆弧的边界
        mBgRectF.left = mCenterPoint.x - mRadius - (mArcWidth - mBgArcWidth) - mBgArcWidth / 2;
        mBgRectF.top = mCenterPoint.y - mRadius - (mArcWidth - mBgArcWidth) - mBgArcWidth / 2;
        mBgRectF.right = mCenterPoint.x + mRadius + (mArcWidth - mBgArcWidth) + mBgArcWidth / 2;
        mBgRectF.bottom = mCenterPoint.y + mRadius + (mArcWidth - mBgArcWidth) + mBgArcWidth / 2;

        // 7.设置渐变
        updateArcPaint();

    }
           
  1. 由于圆弧背景和进度圆弧的宽度不一样,所以求出最大值,用来计算对应圆弧的边界。
  2. 根据onSizeChange()得到view的宽高,然后算出我们可利用的view宽度最大值。
  3. minSize / 2  得到的是最大半径,由于内圆不会占满view,所以减去内圆与view宽度的空隙,得到内圆半径。
  4. view是个正方形,将中心作为我们的圆心,也是画弧的圆心。
  5. mRectF作为圆弧的边界时,left,top,right,bottom所指的是圆弧宽度的中心点,由于圆弧有一定的宽度,所以,需要将mRectF的值加上或者减去圆弧宽度的一半,我们这里用圆心计算,减去内圆半径,再减(加)上进度弧的宽度。
  6. mArcWidth - mBgArcWidth 为进度圆弧和背景圆弧中间的空白间隙,减去这个间隙,然后再减去(加)背景圆弧的一半,即可得到背景圆弧的边界。
  7. 用来设置画笔的渐变颜色

上渐变代码

private void updateArcPaint() {
        // 设置渐变
        int[] mGradientColors = {getResources().getColor(R.color.color_start), getResources().getColor(R.color.color_end)};
      
        float[] positions = {0f, 0.5f};
        mSweepGradient = new SweepGradient(mCenterPoint.x, mCenterPoint.y, mGradientColors, positions);

    }
           

就一个圆形渐变的一个构造方法,设置颜色的int[], 设置颜色变化位置,值为从0f-1.0f , positions可以为null,默认均分渐变。

  • onDraw()

好了,重头戏已经结束了,剩下的就是用canvas进行绘制了,调用对应的方法即可

对了,还有一个需要注意的,由于画布的0°,是从3点钟方向开始的,不是从12点开始,所以需要旋转画布。

旋转94度,是因为画笔是头尾都是圆弧状,如果从90开始,看起来会有点歪。

// 逆时针旋转94度
 canvas.rotate(mStartAngle, mCenterPoint.x, mCenterPoint.y);
           

画内圆

// 画内圆
canvas.drawCircle(mCenterPoint.x, mCenterPoint.y, mRadius, mCirclePaint);
           

画背景弧

// 圆环背景
canvas.drawArc(mBgRectF, 0, 360, false, mBgArcPaint);
           

画进度弧

// 设置渐变
mArcPaint.setShader(mSweepGradient);
float currentAngle = finalCurrentValue * 1.0f / mMaxValue * 360;
// +4 是因为绘制的时候出现了圆弧起点有尾巴的问题
canvas.drawArc(mRectF, 4, currentAngle, false, mArcPaint);
           

最后一个画发光小圆需要注意一下,这里的算法是根据当前旋转角度和半径来计算圆上一点的坐标的,这个不知道如何计算的,应该是知识都还给数学老师了,童鞋们可以某度一下计算圆上一点坐标的公式。知道坐标之后,计算出与圆心的距离,然后画出。

// 设置发光的圆
setLayerType(LAYER_TYPE_SOFTWARE, null);

// 计算小圆距离中心点的距离
float tempRadius = mRadius + mArcWidth / 2 + 1;
// 根据求圆上一点的方式,求出圆上的点相对于圆心的距离
if (currentAngle >= 360) currentAngle = 358;
float y1 = tempRadius * (float) Math.sin((currentAngle + 4) * Math.PI / 180);
float x1 = tempRadius * (float) Math.cos((currentAngle + 4) * Math.PI / 180);
// 算出小圆圆心坐标,根据此坐标,画出小圆
canvas.drawCircle(mCenterPoint.x + x1, mCenterPoint.y + y1, mSmallRadius, mSmallCirclePaint);
           
  • 动态画弧

最后再讲一下动态画弧吧

正常思维下,一般有两种,一种是开子线程,不断改变需要绘制的圆弧,然后延时,达到动态变化的目的。

另一种是利用差值器,系统计算出不同的值,然后来自己改变,自己无需开子线程,只需要设置持续时间和最大值即可。

private void startAnimator() {

        ValueAnimator animator = ValueAnimator.ofInt(0, mCurrentValue);
        animator.setDuration(1000);
        final int finalTempCurrentValue = mCurrentValue;
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                finalCurrentValue = (int) (animation.getAnimatedValue());
                isRun = finalCurrentValue != finalTempCurrentValue;
                if (finalCurrentValue > tempValue) {
                    tempValue = finalCurrentValue;
                    invalidate();
                }
            }
        });
        animator.start();
    }
           

finalCurrentValue即为产生的值,作为圆弧进度值,不断绘制view。其他的代码就是一些健壮性的判断啦,比如动画持续中不能重新开始,每次设定需要重新开始等等。

Finally,附上view完整代码,最后啰嗦一句,为了简单省事,这里并没有自定义属性,如果有需要的朋友可以自定义属性,需要自己设定背景弧和进度弧宽度,渐变颜色等属性的朋友,可以自己添加getset方法。

                                                                                                                                                      初次创作,如有错误,请多指教。

工程下载地址:https://download.csdn.net/download/u010017719/10741645

CircleProgressView完整代码

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.RectF;
import android.graphics.SweepGradient;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by schema on 2018/3/12.
 */

public class CircleProgressView extends View {

    private static final String TAG = "CircleProgressView";

    /**
     * 内圆半径
     */
    private int mRadius;
    /**
     * 圆弧宽度
     */
    private float mArcWidth = 40;
    /**
     * 背景弧宽度
     */
    private float mBgArcWidth = 20;

    /**
     * 小圆半径
     */
    private float mSmallRadius = 30;

    /**
     * 圆心点坐标
     */
    private Point mCenterPoint = new Point();

    /**
     * 圆弧边界
     */
    private RectF mRectF = new RectF();

    private RectF mBgRectF = new RectF();

    /**
     * 圆弧一周最大值
     */
    private int mMaxValue;

    /**
     * 开始角度
     */
    private int mStartAngle = -94;

    /**
     * 当前值
     */
    private int mCurrentValue;

    /**
     * 圆弧背景画笔
     */
    private Paint mBgArcPaint;

    /**
     * 圆弧画笔
     */
    private Paint mArcPaint;

    /**
     * 内圆画笔
     */
    private Paint mCirclePaint;

    /**
     * 小圆圈画笔
     */
    private Paint mSmallCirclePaint;

    /**
     * 渐变器
     */
    private SweepGradient mSweepGradient;

    /**
     * 当前需要画的进度
     */
    private int finalCurrentValue;

    /**
     * 差值器缓存数
     */
    private int tempValue;

    /**
     * 进度圆环内半径
     */
    private int progressInsideRadius;


    private boolean isRun = false;

    private boolean isNeedToReset = false;


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

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

    public CircleProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        initPaint();

    }

    private void initPaint() {
        // 内圆
        mCirclePaint = new Paint();
        mCirclePaint.setAntiAlias(true);
        mCirclePaint.setColor(Color.BLUE);
        mCirclePaint.setStyle(Paint.Style.FILL);

        // 圆弧背景
        mBgArcPaint = new Paint();
        mBgArcPaint.setAntiAlias(true);
        mBgArcPaint.setColor(Color.WHITE);
        mBgArcPaint.setStyle(Paint.Style.STROKE);
        mBgArcPaint.setStrokeWidth(mBgArcWidth);

        // 圆弧
        mArcPaint = new Paint();
        mArcPaint.setAntiAlias(true);
        mArcPaint.setStyle(Paint.Style.STROKE);
        mArcPaint.setStrokeWidth(mArcWidth);
        mArcPaint.setStrokeCap(Paint.Cap.ROUND);

        // 发光小圆
        mSmallCirclePaint = new Paint();
        mSmallCirclePaint.setAntiAlias(true);
        mSmallCirclePaint.setColor(getResources().getColor(R.color.color_7cffff));
        mSmallCirclePaint.setStyle(Paint.Style.FILL);
        mSmallCirclePaint.setMaskFilter(new BlurMaskFilter(mSmallRadius / 2, BlurMaskFilter.Blur.SOLID));
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {

        super.onSizeChanged(w, h, oldw, oldh);
        //求圆弧和背景圆弧的最大宽度
        float maxArcWidth = Math.max(mArcWidth, mBgArcWidth);
        //求最小值作为实际值,这是直径
        int minSize = Math.min(w - getPaddingLeft() - getPaddingRight() - 2 * (int) maxArcWidth,

                h - getPaddingTop() - getPaddingBottom() - 2 * (int) maxArcWidth);

        //内圆半径: 进度圆环内半径 - 中间空余距离为17px = 内圆半径
        mRadius = minSize / 2 - 28;

        //获取圆的相关参数
        mCenterPoint.x = w / 2;
        mCenterPoint.y = h / 2;

        //绘制进度圆弧的边界
        mRectF.left = mCenterPoint.x - mRadius - maxArcWidth / 2;
        mRectF.top = mCenterPoint.y - mRadius - maxArcWidth / 2;
        mRectF.right = mCenterPoint.x + mRadius + maxArcWidth / 2;
        mRectF.bottom = mCenterPoint.y + mRadius + maxArcWidth / 2;

        //绘制背景圆弧的边界
        mBgRectF.left = mCenterPoint.x - mRadius - (mArcWidth - mBgArcWidth) - mBgArcWidth / 2;
        mBgRectF.top = mCenterPoint.y - mRadius - (mArcWidth - mBgArcWidth) - mBgArcWidth / 2;
        mBgRectF.right = mCenterPoint.x + mRadius + (mArcWidth - mBgArcWidth) + mBgArcWidth / 2;
        mBgRectF.bottom = mCenterPoint.y + mRadius + (mArcWidth - mBgArcWidth) + mBgArcWidth / 2;

        updateArcPaint();

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        drawArc(canvas);
    }

    private void drawArc(Canvas canvas) {
        // 逆时针旋转94度
        canvas.rotate(mStartAngle, mCenterPoint.x, mCenterPoint.y);

        // 画内圆
        canvas.drawCircle(mCenterPoint.x, mCenterPoint.y, mRadius, mCirclePaint);

        // 圆环背景
        canvas.drawArc(mBgRectF, 0, 360, false, mBgArcPaint);

        if (mCurrentValue == 0) {
            return;
        }

        if (isNeedToReset) {
            isNeedToReset = false;
            tempValue = 0;
            startAnimator();
            return;
        }
        // 设置渐变
        mArcPaint.setShader(mSweepGradient);
        float currentAngle = finalCurrentValue * 1.0f / mMaxValue * 360;
        // +4 是因为绘制的时候出现了圆弧起点有尾巴的问题
        canvas.drawArc(mRectF, 4, currentAngle, false, mArcPaint);

        // 设置发光的圆
        setLayerType(LAYER_TYPE_SOFTWARE, null);

        // 计算小圆距离中心点的距离
        float tempRadius = mRadius + mArcWidth / 2 + 1;
        // 根据求圆上一点的方式,求出圆上的点相对于圆心的距离
        if (currentAngle >= 360) currentAngle = 358;
        float y1 = tempRadius * (float) Math.sin((currentAngle + 4) * Math.PI / 180);
        float x1 = tempRadius * (float) Math.cos((currentAngle + 4) * Math.PI / 180);
        // 算出小圆圆心坐标,根据此坐标,画出小圆
        canvas.drawCircle(mCenterPoint.x + x1, mCenterPoint.y + y1, mSmallRadius, mSmallCirclePaint);

    }


    private void updateArcPaint() {
        // 设置渐变
        int[] mGradientColors = {getResources().getColor(R.color.color_start), getResources().getColor(R.color.color_end)};
        // 0点钟和9点钟位置
        float[] positions = {0f, 0.5f};
        mSweepGradient = new SweepGradient(mCenterPoint.x, mCenterPoint.y, mGradientColors, positions);

    }

    /**
     * 开始计算差值器
     */
    private void startAnimator() {

        ValueAnimator animator = ValueAnimator.ofInt(0, mCurrentValue);
        animator.setDuration(1000);
        final int finalTempCurrentValue = mCurrentValue;
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                finalCurrentValue = (int) (animation.getAnimatedValue());
                isRun = finalCurrentValue != finalTempCurrentValue;
                if (finalCurrentValue > tempValue) {
                    tempValue = finalCurrentValue;
                    invalidate();
                }
            }
        });
        animator.start();
    }

    private void needToReset() {
        isNeedToReset = true;
        invalidate();
    }

    /**
     * 设置圆弧当前值
     */
    public void setCurrentValue(int mCurrentValue) {
        this.mCurrentValue = mCurrentValue;
        if (this.mCurrentValue > mMaxValue)
            this.mCurrentValue = mMaxValue;
        if (isRun) return;
        needToReset();
    }

    /**
     * 设置最大值
     */
    public void setMaxValue(int mMaxValue) {
        this.mMaxValue = mMaxValue;
    }


}
           

工程下载地址:https://download.csdn.net/download/u010017719/10741645

继续阅读