一个自定义圆环进度View

本文分享了一个自定义圆环进度View的实现过程,包括自定义属性、画笔初始化、测量逻辑、绘制方法等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

啥都不说看,先贴一个图看看最终要实现的效果
这里写图片描述

首先,这个是根据项目需求写出来的一个自定义View,很简单,全是画出来的~~~
好吧,对于一个刚出来的应届毕业生,这是个挑战啊,加上之前的工作很紧张,项目进度太赶,然后Android开发只有我一个人,我能不能说我是个挑大梁的人额。。
哈哈~
好了,废话不多说。。现在开始整题:
1.一开始看到原型的时候,这个控件我就在想能不能用View嵌套View去实现,答案是可以的,但是后来试了一下,发现View嵌套View的形式好像很难控制,所以还是决定自己画出来
2.既然要画,那首先要去了解一下自定义View的介绍吧,然后就去看了一下鸿洋大神的自定义View有了一点理解,然后开始着手去写了~
3.一开始要确定这个View有哪些方法需要抛出来,然后做了一下整理
(1)自定义属性,这个必须的嘛,有些属性不需要在代码中写可以在布局中固定就好了,所以我写了这些自定义的属性:

<declare-styleable name="WorkCircleView">
        <attr name="BorderRadiu" format="dimension"/>
        <attr name="RingRadiu" format="dimension"/>
        <attr name="RingWidth" format="dimension"/>
        <attr name="BorderColor" format="color"/>
        <attr name="RingColor" format="color"/>
        <attr name="ScheduleColor" format="color"/>
        <attr name="UpTextSize" format="dimension"/>
        <attr name="ImageWidth" format="dimension"/>
        <attr name="DownBigTextSize" format="dimension"/>
        <attr name="DownTextSize" format="dimension"/>
        <attr name="ImageHeight" format="dimension"/>
        <attr name="Image" format="reference"/>
    </declare-styleable>

这个自定义属性应该还是能看懂的吧,简单说一下 :
declare-styleable 是给自定义控件添加自定义属性用的
然后declare-styleable后面的name就是这个自定义属性的明明,需要在获取自定义属性时根据这个name去调用的,所以这个名字最好可以和自定义控件名称一样,这样方便查看

attr这个就是自定义属性的标签,里面的name就是自定属性的名称,这个name可以在布局中使用控件后使用app:xxxx=”“这样去调用,想想还是挺简单的,最后说一下这个format这个是自定义属性的类型,至于类型常用的可能是dimension尺寸,可以用dp或者sp,就是说可以定义字体和大小的,然后是reference这个是引用资源的,可以获取资源文件的,然后是color这个是引用的颜色值的,其实也还有其他的类型,需要的可以去网上找找哈~

解释了一大堆,接下来还是说说我的实现思路吧,下面多数会贴代码:
一、新建一个Class集成自View,然后实现里面的3个构造函数,因为用到了自定义属性,所以必须要实现3个构建函数,代码如下:

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

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

    public WorkCircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WorkCircleView, 0, 0);
        mBorderRadiu = a.getDimensionPixelOffset(R.styleable.WorkCircleView_BorderRadiu, 100);
        mRingRadiu = a.getDimensionPixelOffset(R.styleable.WorkCircleView_RingRadiu, 70);
        mRingWidth = a.getDimensionPixelOffset(R.styleable.WorkCircleView_RingWidth, 10);
        mBorderColor = a.getColor(R.styleable.WorkCircleView_BorderColor, Color.parseColor("#eaf1f9"));
        mRingColor = a.getColor(R.styleable.WorkCircleView_RingColor, Color.GRAY);
        mScheduleColor = a.getColor(R.styleable.WorkCircleView_ScheduleColor, Color.parseColor("#1cad59"));
        mUpTextSize = a.getDimensionPixelOffset(R.styleable.WorkCircleView_UpTextSize,
                DensityUtil.sp2px(context,14));
        mImageWidth = a.getDimensionPixelOffset(R.styleable.WorkCircleView_ImageWidth, 30);
        mDownBigSize = a.getDimensionPixelOffset(R.styleable.WorkCircleView_DownBigTextSize, 40);
        mDownSize = a.getDimensionPixelSize(R.styleable.WorkCircleView_DownTextSize,30);
        mImageHeight = a.getDimensionPixelOffset(R.styleable.WorkCircleView_ImageHeight,30);
        a.recycle();
        init();
    }

前两个构造函数直接引用第三个构造函数就可以了,然后在第三个构造函数中获取自定义属性,还有初始化画笔,画笔还是一开始初始化完比较好。

二、初始化画笔,把画笔先初始化好先,我这里用了挺多画笔的 =- =,不想在下面清除画笔然后重新定义画笔,是不是很懒,O(∩_∩)O哈哈~,好了,代码如下:

private void init() {
        //初始化画笔
        //边框画笔------start
        mBorderPaint = new Paint();
        mBorderPaint.setAntiAlias(true);
        mBorderPaint.setStyle(Paint.Style.STROKE);
        mBorderPaint.setColor(mBorderColor);
        //-------------end
        //圆环画笔------start
        mRingPaint = new Paint();
        mRingPaint.setAntiAlias(true);
        mRingPaint.setStyle(Paint.Style.STROKE);
        mRingPaint.setColor(mRingColor);
        //-------------end
        //进度画笔------start
        mSchedulePaint = new Paint();
        mSchedulePaint.setAntiAlias(true);
        mSchedulePaint.setStyle(Paint.Style.STROKE);
        //-------------end
        //初始化文字画笔---start
        mUpTextPaint = new Paint();
        mUpTextPaint.setAntiAlias(true);
        mUpTextPaint.setTextSize(mUpTextSize);
        mUpTextPaint.setTypeface(Typeface.DEFAULT_BOLD);//粗体
        mDownBigPaint = new Paint();
        mDownBigPaint.setAntiAlias(true);
        mDownBigPaint.setTextSize(mDownBigSize);
        mDownBigPaint.setTypeface(Typeface.DEFAULT_BOLD);
        mDownPaint = new Paint();
        mDownPaint.setAntiAlias(true);
        mDownPaint.setTextSize(mDownSize);
        //----------------end

        //初始化RectF
        mRectf = new RectF();
    }

三、好了,画笔也初始化完了,现在该考虑的是控件的测量问题了,这个控件不能太小,一小了就会变形,而且什么都没有看到的,所以我们给它一个最小的大小,圆形嘛,宽高一致,在自定义属性那里已经写好了的,所以我们只需要测量就好了:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int width;
        int height;
        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else {
            width = mBorderRadiu * 2 + getPaddingRight() + getPaddingLeft() + 2;

        }
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            height = mBorderRadiu * 2 + getPaddingTop() + getPaddingBottom()+ 2;
        }
        int chang = Math.min(width, height);
        mWidth = chang;
        mBorderRadiu = chang / 2;
        mRingRadiu = (int) (mBorderRadiu * 0.87);
        setMeasuredDimension(chang, chang);
    }

这里测量了最大的宽度,然后宽度和高度也是一样的。

四、测量完了,确保了控件能正常显示了,我们要做得就是开始利用Paint(画笔)了,此外,我们要用画笔需要重写onDraw这个方法,onDraw的参数有canvas,这个canvas是什么东东,这个很好解释,有了画笔,我们还需要什么东西?一是画布,二是画家,知道这两个东西了,画布其实就是这个控件本身,而画家嘛,当然是canvas了,canvas里面提供了很多的画法,有画圆弧,圆,矩形,直线,文字。。等等,这个画家还可以嘛,哈哈~,好了,代码贴一下:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int center = mWidth / 2;
        mBorderRadiu = mBorderRadiu - getPaddingLeft() - getPaddingRight();
        if (mBorderRadiu < mRingRadiu || mBorderRadiu - mRingRadiu <= 20) {
            mRingRadiu = (int) (mBorderRadiu * 0.87);
        }
        if (mRingWidth * 3 >= mBorderRadiu) {
            mRingWidth = mBorderRadiu / 4;
        }
        /**
         * 调整圆环的宽度
         */
        mRingPaint.setStrokeWidth(mRingWidth);
        mSchedulePaint.setStrokeWidth(mRingWidth);//跟圆环宽度同宽
        /**
         * 调整进度条颜色
         */
        mSchedulePaint.setColor(mScheduleColor);
        /**
         * 调整文字的颜色
         */
        mUpTextPaint.setColor(Color.BLACK);
        mDownBigPaint.setColor(mScheduleColor);
        mDownPaint.setColor(mScheduleColor);
        //圆环的间隔
        mInterval = mBorderRadiu - mRingRadiu ;
        canvas.drawCircle(center, center, mBorderRadiu, mBorderPaint);
        canvas.drawCircle(center, center, mRingRadiu, mRingPaint);
        //进度的位置
        mRectf.left = mInterval;
        mRectf.top = mInterval;
        mRectf.right = mWidth - mInterval;
        mRectf.bottom = mWidth - mInterval;
        canvas.drawArc(mRectf, -90, -mProgress, false, mSchedulePaint);
        //当图片的宽度小于圆环的半径则画出来
        if (mImageWidth < mBorderRadiu) {
            drawImage(canvas,center);//画图片
            drawUpText(canvas,center);//画上层文字
            drawDownText(canvas,center);//画下层文字
        }
    }

我相信我的注释应该很清晰明了,真正开始画我是另外写了方法的,这样可读性好一点,而且容易找到错误点,所以,每个方法都需要传一个canvas(画家)进去,然后加上算好的位置穿进去,这样就可以画出一个自定义控件了,我觉得还是贴一下代码吧:

//画图片
    private void drawImage(Canvas canvas,int center){
        int imgWidth = Math.min(mImageWidth,mImageHeight);
        if (imgWidth >= mBorderRadiu / 2){
            imgWidth = (int)(mBorderRadiu / 2);
        }
        //画图片
        int imgLeft = center - imgWidth - (mBorderRadiu / 5);
        int imgTop = center - imgWidth;
        int imgRight = center - (mBorderRadiu / 5);
        int imgBottom = center;
        mDrawable.setBounds(imgLeft, imgTop - 5, imgRight, imgBottom - 5);
        mDrawable.draw(canvas);
    }

    //画上层文字
    private void drawUpText(Canvas canvas,int center){
        int textX = center - (mImageWidth / 4);
        int textY = center;
        //画上层大字体,根据图片位置判断字体位置
        canvas.drawText(mUpText, textX, textY - 10 , mUpTextPaint);
    }

    //画下层文字
    private void drawDownText(Canvas canvas,int center){
        //下层字体位置计算
        float DownBigTextWidth = mDownBigPaint.measureText(mDownBigText);
        float downX = center - DownBigTextWidth + (mImageWidth / 5);
        float downY = center + mDownBigSize - (mDownBigSize / 4);
        //画下层大字体
        canvas.drawText(mDownBigText, downX, downY, mDownBigPaint);
        //画下层字体
        float downX2 = center + mImageWidth / 5;
        float downy2 = center + mDownBigSize - (mDownBigSize / 4);
        canvas.drawText(mDownText,downX2 + 5,downy2,mDownPaint);
    }

五、画完了这个之后,就改考虑要抛出的方法了,因为是圆环进度嘛,进度肯定是由用户自己控制的啦,所以,我们把这个进度的控制方法抛出去吧(爸爸不要你了~~):

/**
     * 设置进度
     *
     * @param current 当前值
     * @param total   总值
     */
    public void setProgress(int current, int total) {
        float pro = (float) current / (float) total;
        pro = pro * 360;
        this.mProgress = pro;
        postInvalidate();
    }

    /**
     * 调整颜色进度框
     *
     * @param current  当前值
     * @param total    总值
     * @param colorRes 颜色值
     */
    public void setProgress(int current, int total, int colorRes) {
        float pro = (float) current / (float) total;
        pro = pro * 360;
        this.mProgress = pro;
        this.mScheduleColor = colorRes;
        postInvalidate();
    }

六、到了这里,这个控件就完成了,我们只需要在布局中用起来就好了,还是挺简单的,加班加到奔溃,还是学到挺多的,写个博客激励一下自己,嘿嘿~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值