自定义圆形进度条的学习与实现

兄弟篇上

自定义条形进度条的文章——条形进度条的自定义

完成后的样式

roundProgressBarFinish

分析

从完成的样式来看,这个进度条比之前自定义的条形进度条唯一多出来的就是圆形进度条的半径,剩余的其他属性我们都可以使用条形进度条的 所以,圆形的进度条可以继承自条形进度条,然后在条形进度条的基础上再自定义一个圆形进度条所需要的半径属性就好了。

在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);
    }
接着再来看一张图 progressBarInfo 从图上可以看到,我们要画出来的图形,并不只是包括一个圆,除了圆以外,还有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的 roundProgressCircleTrue roundProgressCircleFalse 因为文字是要居中的,所以文字的左上角就是半径减去文字的宽度的一半 文字的高度就是半径减去文字高度的一半 最后将文字绘制上去就可以了 慕课网视频学习后实践整理:原视频 [gt href='https://github.com/wudkj/DemoApplication/tree/master/progress']GitHub地址[/gt]
编写pptp连接批处理 2016-06-26
Android中一些小问题的解决办法 2016-07-21

评论区