兄弟篇上
自定义条形进度条的文章——条形进度条的自定义
完成后的样式
分析
从完成的样式来看,这个进度条比之前自定义的条形进度条唯一多出来的就是圆形进度条的半径,剩余的其他属性我们都可以使用条形进度条的
所以,圆形的进度条可以继承自条形进度条,然后在条形进度条的基础上再自定义一个圆形进度条所需要的半径属性就好了。
在 attrs 中添加属性
<declare-styleable name="RoundProgressBarWithProgress"> <attr name="progress_radius" format="dimension"></attr> </declare-styleable>
创建 view
这个很简单,只要继承条形进度条就好了
public class RoundProgressBarWithProgress extends HorizontalProgressBar { public RoundProgressBarWithProgress(Context context) { this(context, null); } public RoundProgressBarWithProgress(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RoundProgressBarWithProgress(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } }
获取自定义属性
这里我们要获得用户的输入,也就是圆的半径
首先定义一个成员变量,并给它一个默认值
private int mRadius = dpToPx(30);
这里调用了 dpToPx 方法,这个方法是在条形进度条的类中的,圆形进度条和条形进度条在同一个包里面的时候,只要把条形进度条里面的这个方法的修饰符改成 protected 就好了,其他我们使用的各项条形进度条的成员变量同样修改为 protected 即可
之后就可以获得用户设置的圆形的半径大小了
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.RoundProgressBarWithProgress); mRadius = (int) ta.getDimension(R.styleable.RoundProgressBarWithProgress_progress_radius, mRadius); ta.recycle();
测量控件
接下来测量控件的大小,先放代码
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mMaxPaintWidth = Math.max(mReachHeight, mUnreachHeight); //直径 (我们根据用户输入的半径算得的直径) int expect = mRadius * 2 + mMaxPaintWidth + getPaddingRight() + getPaddingLeft(); //将我们的直径传入,根据父控件告诉我们的模式(我自己的模式),算出来我真正的高度和宽度 int width = resolveSize(expect, widthMeasureSpec); int height = resolveSize(expect, heightMeasureSpec); //从两者中选出一个小的,作为直径 int realWidth = Math.min(width, height); //直径减去左右边距,减去画笔宽度(画笔在直径上有两个/边,但是直径是在画笔的中间的,也就是画笔画出来的线其实是横跨在直径的那条线上的),最后除 2,算出真实的半径 mRadius = (realWidth - getPaddingLeft() - getPaddingRight() - mMaxPaintWidth) / 2; setMeasuredDimension(realWidth, realWidth); }
接着再来看一张图
从图上可以看到,我们要画出来的图形,并不只是包括一个圆,除了圆以外,还有 ReachProgress 的宽度,还有边上的各种 padding
我们要根据用户设置的半径加上其他的一些因素形成的宽和高来计算出在程序中要画的图形的宽和高
从图上我们就可以看到,整个图形的直径 = mRadius (用户设置的半径) * 2 +两个 progress 中最宽的画笔的宽度(reachProgress 的宽度其实有一半是包括在半径中的)+ 左右边距
这样我们就获得了整个图形的宽度,因为我们画的是一个圆,我们默认宽和高是相等的(也就是默认左右边距与上下边距是相等的)
然后我们用 resolveSize 方法根据模式来计算出程序中要真是绘制的宽度和高度
这里我把 resolveSize 方法放出来,这个方法其实和之前在条形进度条中自己写的是一样的
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { final int specMode = MeasureSpec.getMode(measureSpec); final int specSize = MeasureSpec.getSize(measureSpec); final int result; switch (specMode) { case MeasureSpec.AT_MOST: if (specSize < size) { result = specSize | MEASURED_STATE_TOO_SMALL; } else { result = size; } break; case MeasureSpec.EXACTLY: result = specSize; break; case MeasureSpec.UNSPECIFIED: default: result = size; } return result | (childMeasuredState & MEASURED_STATE_MASK); }
虽然在我们的预期中,它计算出来的是相等的,但是结果毕竟会根据用户的设置改变,所以我们取两者中小的来作为我们使用的直径,这样那个比较大的在它的方向上就只是变短了一些而已
接下来我们看着图来计算我们需要的真实的半径,这个半径在绘制的时候会用到
半径 = (真实的直径 – 左右边距 – reachProgress 的宽度)/2 注意:要记得 reachProgress 的一半是包括在半径中的
最后调用 setMeasuredDimension 方法传入测量出来的宽和高
画出进度条
接下来就要在 onDraw 方法中进行绘制了
protected synchronized void onDraw(Canvas canvas) { //计算文字相关高度以及宽度 String text = getProgress() + "%"; int textWidth = (int) mPaint.measureText(text); int textHeight = (int) ((mPaint.descent() + mPaint.ascent())); canvas.save(); //移动坐标到左上角 左上角的位置为除去 padding 和画笔宽度的一半 canvas.translate(getPaddingLeft() + mMaxPaintWidth / 2, getPaddingTop() + mMaxPaintWidth / 2); //画 unReachBar mPaint.setColor(mUnreachColor); mPaint.setStrokeWidth(mUnreachHeight); mPaint.setStyle(Paint.Style.STROKE); canvas.drawCircle(mRadius, mRadius, mRadius, mPaint); //画 reachBar //计算要画出的角度,度数的百分比乘以 360 float sweepAngle = getProgress() * 1.0f / getMax() * 360; mPaint.setStyle(Paint.Style.STROKE); mPaint.setColor(mReachColor); mPaint.setStrokeWidth(mReachHeight); //画圆 第一个参数是圆的外接正方形,第二三个参数分别为其实度数以及要画的度数,第四个参数表示是否过圆心 canvas.drawArc(mRectF, 0, sweepAngle, false, mPaint); //画出文字 mPaint.setColor(mTextColor); mPaint.setTextSize(mTextSize); mPaint.setStyle(Paint.Style.FILL); //第一个参数是文字,第二、三个参数是画文字的左上角的坐标(mRadius 是圆心, 圆心的左上角的横坐标就是所有文字的宽度/2,高度类似) canvas.drawText(text, mRadius - textWidth / 2, mRadius - textHeight / 2, mPaint); canvas.restore(); }
首先获得当前进度,并进一步获得要显示的文字,通过 mPaint 测量出文字的宽度以及高度
并将画点移动到指定的位置(图中左上角的 b 点,很容易看到这个点的位置是如何算出来的)
接着开始绘制 unReachBar、reachBar 以及文字
首先设置画笔的颜色,然后设置画笔的宽度以及样式。之后所有的画笔设置都类似,所以之后对画笔工具的设置就不做介绍了
在画图形的时候,我们可以想到:unReachBar 是” 天生” 存在的,当进度进行变更的时候,只是在 unReachBar 的上面画出 reachBar 而已
所以,画 unReachBar 只要绘制一个圆形就可以
现在画笔的位置是在 b 点,我们在 drawCircle 的时候要传入参数,分别是圆点以及半径,那么相对于 b 点来说,圆点的坐标就是 半径、半径
接下来绘制的是 reachBar
reachBar 在进度增长的过程中,从一个点经由弧线最终变为一个圆,通过绘制这个弧线,就可以将整个过程都包括进去
要绘制弧线,就要获得这段弧线所对应圆的外接正方形/长方形(这里这个弧线对应的是一个圆,而不是椭圆,所以对应的是一个正方形)
这个正方形就是以 b 点作为左上角顶点的正方形,通过以下代码构造这样一个正方形
private RectF mRectF = new RectF(0, 0, mRadius * 2, mRadius * 2);
在画弧的 drawArc 方法中,还需要另外几个参数,一个是绘制的起始角度,另一个是圆弧扫过的角度,分别对应代码中的第二、三个参数
canvas.drawArc(mRectF, 0, sweepAngle, false, mPaint);
扫过的角度可以通过进度的百分比乘以 360 来计算
第四个参数代表绘制的这段弧线的两边是否要和圆心连起来,参看下面的两个图形, 上面/左侧的为设置为 true 的,下面/右侧是设置为 false 的
因为文字是要居中的,所以文字的左上角就是半径减去文字的宽度的一半
文字的高度就是半径减去文字高度的一半
最后将文字绘制上去就可以了
慕课网视频学习后实践整理:原视频
GitHub 地址转载请注明:热爱改变生活.cn » 自定义圆形进度条的学习与实现
本博客只要没有注明“转”,那么均为原创。 转载请注明链接:sumile.cn » 自定义圆形进度条的学习与实现