Bootstrap

Android自定义View03-实现弧形进度条效果

话不多说,先看效果
初始状态
在这里插入图片描述
中间动画状态
在这里插入图片描述
最终状态
在这里插入图片描述
类似一个仪表盘,进度条从0增长到3000。

show you code :
分析图像,我们需要如下属性:
外圈颜色、内圈颜色、弧宽度、数字文本大小、数字文本颜色

<declare-styleable name="QQStepView">
        <attr name="outerColor" format="color"/>
        <attr name="innerColor" format="color"/>
        <attr name="borderWidth" format="dimension"/>
        <attr name="stepTextSize" format="dimension"/>
        <attr name="stepTextColor" format="color"/>
    </declare-styleable>

1.创建一个自定义View : QQStepView,让所有的构造方法都执行到同一个,并且在这个构造方法中去获取xml里设置的属性。

package com.example.view_day01;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import androidx.annotation.Nullable;

public class QQStepView extends View {

    private int mOuterColor = Color.GRAY;
    private int mInterColor = Color.RED;
    private int mBorderWidth = 20; //20px
    private int mStepTextSize;
    private int mStepTextColor;

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

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

    public QQStepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //获取xml里的属性值
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.QQStepView);
        mOuterColor = array.getColor(R.styleable.QQStepView_outerColor, mOuterColor);
        mInterColor = array.getColor(R.styleable.QQStepView_innerColor, mInterColor);
        mBorderWidth = (int) array.getDimension(R.styleable.QQStepView_borderWidth, mBorderWidth);
        mStepTextSize = array.getDimensionPixelSize(R.styleable.QQStepView_stepTextSize, mStepTextSize);
        mStepTextColor = array.getColor(R.styleable.QQStepView_stepTextColor, mStepTextColor);
        //释放资源
        array.recycle();

    }

  1. 重写onMeasure()方法,确定view的宽高,保证是个正方形
 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //宽高不一致,取小的,保证是个正方形
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);

        setMeasuredDimension(width > height ? height : width, width > height ? height : width);
    }
  1. 重写绘制方法onDraw(Canvas canvas)
    绘制就是用Paint在一定区域内Rect绘制到Canvas上
    想画什么样的图形就调用Canvas.drawxxx方法
    开始画之前,要在构造方法里先初始化画笔
//初始化外部画笔
        mOuterPaint = new Paint();
        mOuterPaint.setAntiAlias(true);
        mOuterPaint.setColor(mOuterColor);
        mOuterPaint.setStrokeWidth(mBorderWidth);//width 是弧的内外扩散的
        mOuterPaint.setStyle(Paint.Style.STROKE);//Fill画笔实心,Stroke描边
        mOuterPaint.setStrokeCap(Paint.Cap.ROUND);//线条尾部设置成圆角

        //初始化内部画笔
        mInnerPaint = new Paint();
        mInnerPaint.setAntiAlias(true);
        mInnerPaint.setColor(mInterColor);
        mInnerPaint.setStrokeWidth(mBorderWidth);//width 是弧的内外扩散的
        mInnerPaint.setStyle(Paint.Style.STROKE);//Fill画笔实心,Stroke描边
        mInnerPaint.setStrokeCap(Paint.Cap.ROUND);

		//画文字
        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setColor(mStepTextColor);
        mTextPaint.setTextSize(mStepTextSize);

准备好画笔后,就可以在onDraw方法里绘制图形了

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int center = getWidth() / 2; //150
        int radius = getWidth() / 2 - mBorderWidth/2; //132  /2达到正切效果
        //会显示不完整,描边有宽度 mBorderWidth, 圆弧边界修圆
        RectF rectF = new RectF(center-radius, center-radius, center+radius, center+radius);
        //画外圆弧、内圆弧、文字 x-left is + | y-down is +
        //canvas.drawRect(rectF, mRectPaint);
        canvas.drawArc(rectF, 135, 270, false, mOuterPaint);

        //画内圆弧,百分比,肯定不能写死,使用者从外面传
        if (mMaxStep == 0) {
            return;
        }
        float sweepAngle = (float) mCurrentStep / mMaxStep;
        canvas.drawArc(rectF, 135, sweepAngle * 270, false, mInnerPaint);

        //画文字
        String stepText = mCurrentStep + "";
        Rect textBounds = new Rect();
        mTextPaint.getTextBounds(stepText, 0, stepText.length(), textBounds);
        //水平偏移dx
        int dx = getWidth()/2 - textBounds.width()/2;
        //基线baseLine
        Paint.FontMetricsInt fontMetricsInt = mTextPaint.getFontMetricsInt();
        int dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
        int baseLine = getHeight() / 2 + dy;
        canvas.drawText(stepText, dx, baseLine, mTextPaint);
    }

drawArc是第一次使用
其实很简单
canvas.drawArc(rectF, 135, 270, false, mOuterPaint);
共5个参数
rectF是绘制的区域
135是绘制弧开始的角度(水平向右是0度,顺时针角度增大)
270是扫过的角度
false 是是否绘制中间部分(也就是绘制的效果是弧,还是个封闭的扇形)
mOuterPaint 最后传入一个画笔

写到这里,就能绘制出初始图形了。
设置好如下属性

<com.example.view_day01.QQStepView
        android:id="@+id/qq_step_view"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_margin="20dp"
        app:stepTextColor="@color/white"
        app:stepTextSize="30dp"
        app:outerColor="@color/black"
        android:background="@color/purple_200"
        app:borderWidth="6dp"/>

绘制出来的图形如下
在这里插入图片描述
4.让图形动起来
想让内圈弧形动起来,就要修改不断的内圈圆弧的绘制角度

public synchronized void setStepMax(int mMaxStep) {
       this.mMaxStep = mMaxStep;
    }

    public synchronized void setCurrentStep(int mCurrentStep) {
        this.mCurrentStep = mCurrentStep;
        //Invalidate the whole view. If the view is visible,
        // onDraw(Canvas) will be called at some point in the future.
        invalidate();
    }

提供两个设置方法,去设置最大值和当前值。
在外部调用这两个方法

qqStepView = findViewById(R.id.qq_step_view);
        qqStepView.setStepMax(4000);

        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 3000);
        valueAnimator.setDuration(2000);
        //先快后慢的效果
        valueAnimator.setInterpolator(new DecelerateInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float currentStep = (float) animation.getAnimatedValue();
                qqStepView.setCurrentStep((int)currentStep);
            }
        });
        qqStepView.postDelayed(new Runnable() {
            @Override
            public void run() {
                //延迟1s
                valueAnimator.start();
            }
        }, 1000);

使用了属性动画来生成当前值(2s内从0增长到3000),生成设置给QQStepView。


最后放上完整的自定义View代码

package com.example.view_day01;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import androidx.annotation.Nullable;

public class QQStepView extends View {

    private int mOuterColor = Color.GRAY;
    private int mInterColor = Color.RED;
    private int mBorderWidth = 20; //20px
    private int mStepTextSize;
    private int mStepTextColor;

    private Paint mOuterPaint, mInnerPaint, mTextPaint;
    private Paint mRectPaint;

    private int mMaxStep = 0;
    private int mCurrentStep = 0;

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

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

    public QQStepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.QQStepView);
        mOuterColor = array.getColor(R.styleable.QQStepView_outerColor, mOuterColor);
        mInterColor = array.getColor(R.styleable.QQStepView_innerColor, mInterColor);
        mBorderWidth = (int) array.getDimension(R.styleable.QQStepView_borderWidth, mBorderWidth);
        mStepTextSize = array.getDimensionPixelSize(R.styleable.QQStepView_stepTextSize, mStepTextSize);
        mStepTextColor = array.getColor(R.styleable.QQStepView_stepTextColor, mStepTextColor);
        array.recycle();

        //初始化外部画笔
        mOuterPaint = new Paint();
        mOuterPaint.setAntiAlias(true);
        mOuterPaint.setColor(mOuterColor);
        mOuterPaint.setStrokeWidth(mBorderWidth);//width 是弧的内外扩散的
        mOuterPaint.setStyle(Paint.Style.STROKE);//Fill画笔实心,Stroke描边
        mOuterPaint.setStrokeCap(Paint.Cap.ROUND);

        //初始化内部画笔
        mInnerPaint = new Paint();
        mInnerPaint.setAntiAlias(true);
        mInnerPaint.setColor(mInterColor);
        mInnerPaint.setStrokeWidth(mBorderWidth);//width 是弧的内外扩散的
        mInnerPaint.setStyle(Paint.Style.STROKE);//Fill画笔实心,Stroke描边
        mInnerPaint.setStrokeCap(Paint.Cap.ROUND);

        //画文字
        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setColor(mStepTextColor);
        mTextPaint.setTextSize(mStepTextSize);

        //画背景
        mRectPaint = new Paint();
        mRectPaint.setAntiAlias(true);
        mRectPaint.setColor(Color.BLUE);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //宽高不一致,取小的,保证是个正方形
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);

        setMeasuredDimension(width > height ? height : width, width > height ? height : width);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int center = getWidth() / 2; //150
        int radius = getWidth() / 2 - mBorderWidth/2; //132  /2达到正切效果
        //D/jingyide: width = 300,mBorderWidth = 18
        Log.d("jingyide", "width = " + getWidth() + ",mBorderWidth = " + mBorderWidth);
        //会显示不完整,描边有宽度 mBorderWidth, 圆弧边界修圆
        RectF rectF = new RectF(center-radius, center-radius, center+radius, center+radius);
        //画外圆弧、内圆弧、文字 x-left is + | y-down is +
        //canvas.drawRect(rectF, mRectPaint);
        canvas.drawArc(rectF, 135, 270, false, mOuterPaint);


        //画内圆弧,百分比,肯定不能写死,使用者从外面传
        if (mMaxStep == 0) {
            return;
        }
        float sweepAngle = (float) mCurrentStep / mMaxStep;
        canvas.drawArc(rectF, 135, sweepAngle * 270, false, mInnerPaint);

        //画文字
        String stepText = mCurrentStep + "";
        Rect textBounds = new Rect();
        mTextPaint.getTextBounds(stepText, 0, stepText.length(), textBounds);
        //水平偏移dx
        int dx = getWidth()/2 - textBounds.width()/2;
        //基线baseLine
        Paint.FontMetricsInt fontMetricsInt = mTextPaint.getFontMetricsInt();
        int dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
        int baseLine = getHeight() / 2 + dy;
        canvas.drawText(stepText, dx, baseLine, mTextPaint);
    }

    public synchronized void setStepMax(int mMaxStep) {
       this.mMaxStep = mMaxStep;
    }

    public synchronized void setCurrentStep(int mCurrentStep) {
        this.mCurrentStep = mCurrentStep;
        //Invalidate the whole view. If the view is visible,
        // onDraw(Canvas) will be called at some point in the future.
        invalidate();
    }
}

;