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

 

 

 

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
自定义圆形进度条,我们可以通过继承ProgressBar类,并重写其中的一些方法来实现。 首先,我们需要创建一个自定义的ProgressBar类,并在构造方法中定义一些必要的属性,如进度条颜色、进度值等。然后,我们可以通过重写onMeasure方法来测量进度条的大小,保证其为一个半圆形。接着,我们需要重写onDraw方法来绘制进度条的样式。在这个方法中,我们可以利用Canvas和Paint来绘制一个半圆形的背景,并使用同样的方式绘制进度条的进度部分。 在绘制进度条的进度部分时,我们需要根据当前的进度值来确定进度的角度,并使用Path类的arcTo方法来绘制一个与进度值对应的扇形。同时,我们还可以调用Paint的setShader方法来设置进度条渐变效果,使得进度从一种颜色平滑过渡到另一种颜色。 除了绘制进度条的样式外,我们还可以根据需要为进度条添加一些动效果。例如,我们可以使用ValueAnimator类来实现进度的平滑过渡,通过不断改变进度值并调用invalidate方法来触发重绘,从而实现进度条的动态效果。 最后,我们还可以根据需要为自定义的半圆形进度条添加一些其他功能,如进度文字显示、进度监听等。这些功能的实现方式与一般的ProgressBar类似,只需在自定义类中添加相应的方法即可。 通过以上的步骤,我们可以实现一个自定义的半圆形进度条,满足我们对进度条样式和功能的需求。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值