原文:https://www.jianshu.com/p/be71f6ffe512
整体流程:
1. 在value文件夹下新建 attrs 文件,加入style声明
2. 在view类中定义 Paint 以及相应的变量
3. 在构造函数中,获取由XML文件传来的属性值 ,并完成对不同 Paint的初始化
4.在onDraw() 方法中,用 canvas.drawArc() 对底层圆圈和进度圆圈以及数字进行绘制
5. 用属性动画,用 animator.addUpdateListener() 方法将动画的更新与 View中的数值参数关联
View类代码:
public class CircleProgressView extends View { private int mCurrent; private Paint mPaintOut; private Paint mPaintCurrent; private Paint mPaintText; private float mPaintWidth; private int mPaintColor = Color.GREEN; private int mTextColor = Color.BLUE; private float mTextSize; private int location; private float startAngle; private OnLoadCompleteListener onLoadCompleteListener; 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); TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressView); location = array.getInt(R.styleable.CircleProgressView_location, 1); mPaintWidth = array.getDimension(R.styleable.CircleProgressView_progress_paint_width, dip2px(context, 8));//默认4dp mPaintColor = array.getColor(R.styleable.CircleProgressView_progress_paint_color, mPaintColor); mTextSize = array.getDimension(R.styleable.CircleProgressView_progress_text_size, dip2px(context, 18));//默认18sp mTextColor = array.getColor(R.styleable.CircleProgressView_progress_text_color, Color.BLACK); array.recycle(); //灰色背景 mPaintOut = new Paint(); mPaintOut.setAntiAlias(true); mPaintOut.setStrokeWidth(mPaintWidth); mPaintOut.setStyle(Paint.Style.STROKE); mPaintOut.setColor(Color.GRAY); mPaintOut.setStrokeCap(Paint.Cap.ROUND); //彩色填充 mPaintCurrent = new Paint(); mPaintCurrent.setAntiAlias(true); mPaintCurrent.setStrokeWidth(mPaintWidth); mPaintCurrent.setStyle(Paint.Style.STROKE); mPaintCurrent.setColor(mPaintColor); mPaintCurrent.setStrokeCap(Paint.Cap.ROUND); mPaintText = new Paint(); mPaintText.setAntiAlias(true); mPaintText.setStyle(Paint.Style.FILL); mPaintText.setColor(mTextColor); mPaintText.setTextSize(mTextSize); if (location == 1) { startAngle = -90; } else if (location == 2) { startAngle = 0; } else if (location == 3) { startAngle = 90; } else if (location == 4) { startAngle = 180; } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, 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); RectF rect = new RectF(mPaintWidth / 2, mPaintWidth / 2, getWidth() - mPaintWidth / 2, getHeight() - mPaintWidth / 2); // canvas.drawArc(rectF, -90, 360, false, mPaintOut); canvas.drawArc(rect, 0, 360, false, mPaintOut); //绘制进度条 float sweepAngle = 360 * mCurrent / 100; canvas.drawArc(rect, startAngle, sweepAngle, false, mPaintCurrent); //绘制进度数字 String text = mCurrent + "%"; //获取文字宽度 float textWidth = mPaintText.measureText(text, 0, text.length()); float dx = getWidth() / 2 - textWidth / 2; Paint.FontMetricsInt fontMetricsInt = mPaintText.getFontMetricsInt(); float dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom; float baseLine = getHeight() / 2 + dy; canvas.drawText(text, dx, baseLine, mPaintText); if (onLoadCompleteListener != null && mCurrent == 100) { onLoadCompleteListener.complete(); } } public int getmCurrent() { return mCurrent; } public void setmCurrent(int mCurrent) { this.mCurrent = mCurrent; invalidate(); } public void setOnLoadCompleteListener(OnLoadCompleteListener loadCompleteListener) { this.onLoadCompleteListener = loadCompleteListener; } public interface OnLoadCompleteListener { void complete(); } public static int dip2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } }
MainAcitivity 中onCreate() 方法:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mCircleProgressView = (CircleProgressView) findViewById(R.id.circle_view); //进度条从0到100 final ValueAnimator animator = ValueAnimator.ofFloat(0, 100); animator.setDuration(4000); animator.setInterpolator(new LinearInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float current = (float) animation.getAnimatedValue(); mCircleProgressView.setmCurrent((int) current); } }); animator.start(); mCircleProgressView.setOnLoadCompleteListener(new CircleProgressView.OnLoadCompleteListener() { @Override public void complete() { animator.start(); } }); }
PS: 在AS中继承View后,会提示生成构造函数,自动生成的构造函数之间不是依次调用关系,若将PAINT初始化放在其中,则不一定会被调用,当onDraw()时,就会有空指针异常。需要手动将其修改。
如果view不需要从attrs获取属性,则可以另写一个 init()方法,在所有构造函数中都调用init()进行初始化。