自定义View实现圆形进度条及圆形Loading

一、自定义View的四部曲:

1、曲一:在res/values文件夹下,新建一个attrs.xml文件,然后在文件中去自定义属性,并且在<declare-styleable>...</declare-styleable>中去声明这些属性

1.1:圆形进度条:

`@code

<?xml version="1.0" encoding="utf-8"?>    
<resources>
<attr name="myFirstCircleColor" format="color"></attr>
<attr name="mySecondCircleColor" format="color"></attr>
<attr name="myCircleWidth" format="dimension"></attr>
<attr name="myCircleSpeed" format="integer"></attr>
<attr name="myCircleTextColor" format="color"></attr>
<attr name="myCircleTextSize" format="dimension"></attr>
<declare-styleable name="ZqhCircle">
<attr name="myFirstCircleColor"></attr>
<attr name="mySecondCircleColor"></attr>
<attr name="myCircleWidth"></attr>
<attr name="myCircleSpeed"></attr>
<attr name="myCircleTextColor"></attr>
<attr name="myCircleTextSize"></attr>
</declare-styleable>
</resources>

`

1.2:圆形loading:

`@code

<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="myFirstCircleColor" format="color"></attr>
<attr name="mySecondCircleColor" format="color"></attr>
<attr name="myCircleWidth" format="dimension"></attr>
<attr name="myCircleSpeed" format="integer"></attr>
<attr name="myCircleText" format="string"></attr>
<attr name="myCircleTextSize" format="dimension"></attr>
<attr name="myCircleTextWidth" format="dimension"></attr>
<attr name="myCircleCount" format="integer"></attr>
<attr name="myCircleTextColor" format="color"></attr>

<declare-styleable name="ZqhCircle">
    <attr name="myFirstCircleColor"></attr>
    <attr name="mySecondCircleColor"></attr>
    <attr name="myCircleWidth"></attr>
    <attr name="myCircleSpeed"></attr>
    <attr name="myCircleText"></attr>
    <attr name="myCircleTextSize"></attr>
    <attr name="myCircleTextWidth"></attr>
    <attr name="myCircleTextColor"></attr>
    <attr name="myCircleCount"></attr>
</declare-styleable>

</resources>

` 2、曲二:去定义一个class继承View然后再重写3个构造器,分别是1个参数的构造器、2个参数的构造器、3个参数的构造器 ;3个参数的构造器中去获取布局文件中使用的自定义的属性所有key值,然后根据key值获取到相应的value值,并做一些初始化的操作

2.1:圆形进度条:

`@code

 public myCircleProgress(Context context, AttributeSet attrs,
        int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    TypedArray array=context.obtainStyledAttributes(attrs, R.styleable.ZqhCircle, defStyleAttr, 0);
    for (int i = 0; i < array.getIndexCount(); i++) {
        int attr=array.getIndex(i);
        switch (attr) {
        case R.styleable.ZqhCircle_myFirstCircleColor:
            myFirstColor=array.getColor(attr, Color.YELLOW);
            break;
        case R.styleable.ZqhCircle_mySecondCircleColor:
            mySecondColor=array.getColor(attr, Color.RED);
            break;
        case R.styleable.ZqhCircle_myCircleWidth:
            myCircleWidth=array.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, getResources().getDisplayMetrics()));
            break;
        case R.styleable.ZqhCircle_myCircleSpeed:
            mySpeed=array.getInteger(attr, 1000);
            break;
        case R.styleable.ZqhCircle_myCircleTextSize:
            myCircleTextSize=array.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
            break;
        case R.styleable.ZqhCircle_myCircleTextColor:
            myCircleTextColor=array.getColor(attr, Color.RED);
            break;
        default:
            break;
        }
    }
    array.recycle();
    myPaint=new Paint();
    myCircleTextBound=new Rect();
    myCircleText=((currentProgress/360)*100)+"%";
    myPaint.setTextSize(myCircleTextSize);
    new Thread(new Runnable() {
        @Override
        public void run() {

            while (currentProgress!=maxProgress) {
                currentProgress++;
                myCircleText=((int)((currentProgress/360.0f)*100))+"%";
                postInvalidate();
                try {
                    Thread.sleep(mySpeed);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }).start();
}

`

2.2:圆形loading:

`@code

public myLoadingProgress(Context context, AttributeSet attrs,
        int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    TypedArray array=context.obtainStyledAttributes(attrs, R.styleable.ZqhCircle, defStyleAttr, 0);
    for (int i = 0; i < array.getIndexCount(); i++) {
        int attr=array.getIndex(i);
        switch (attr) {
        case R.styleable.ZqhCircle_myFirstCircleColor:
            myFirstColor=array.getColor(attr, Color.YELLOW);
            break;
        case R.styleable.ZqhCircle_mySecondCircleColor:
            mySecondColor=array.getColor(attr, Color.RED);
            break;
        case R.styleable.ZqhCircle_myCircleWidth:
            myCircleWidth=array.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, getResources().getDisplayMetrics()));
            break;
        case R.styleable.ZqhCircle_myCircleSpeed:
            mySpeed=array.getInteger(attr, 1000);
            break;
        case R.styleable.ZqhCircle_myCircleCount:
            myCircleCount=array.getInteger(attr, 5);
            break;
        case R.styleable.ZqhCircle_myCircleTextWidth:
            myCircleTextWidth=array.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 5, getResources().getDisplayMetrics()));
            break;
        case R.styleable.ZqhCircle_myCircleTextSize:
            myCircleTextSize=array.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16,getResources().getDisplayMetrics()));
            break;
        case R.styleable.ZqhCircle_myCircleTextColor:
            myCircleTextColor=array.getColor(attr, Color.BLACK);
            break;
        default:
            break;
        }
    }
    array.recycle();
    myPaint=new Paint();
    myCircleTextBound=new Rect();
    myPaint.setTextSize(myCircleTextSize);
    myText=""+myCircleCount;
    myPaint.getTextBounds(myText,0, myText.length(), myCircleTextBound);
    new Thread(new Runnable() {
        @Override
        public void run() {
            while (true) {
                currentProgress++;
                if (currentProgress==360) {
                    --myCircleCount;
                    /**
                     * @author zhongqihong
                     * 在此判断主要为了考虑两种情况
                     * 第一种就是:正常的我们不需要显示中间的text那么我们就在布局文件中不去指定相关属性即可
                     * 此时的myCircleCount默认为0,若为0即表示一般的圆形loading,需要让它不断去转动,
                     * 当--myCircleCount执行完后就为负数,也就不会执行下面的if---else if中的语句,也就不会break,永远循环下去
                     * 第二种就是需要显示中的文本的,那么此时我们从布局文件中制定该属性的值,且该值为正数, 当--myCircleCount执行完后就为非负数
                     * 那么就会执行下面的if--else if并且当myCircleCount减到0就直接break跳出循环,就不会再去绘制canvas
                     * */
                    if (myCircleCount>0) {
                        myText=""+myCircleCount;
                    }else if (myCircleCount==0) {
                        myText="ok!";
                        break;
                    }
                    currentProgress=0;
                    if (isNext) {
                        isNext=false;
                    }else{
                        isNext=true;
                    }
                }
                handler.sendEmptyMessage(0x111);
                try {
                    Thread.sleep(mySpeed);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }).start();
}

` 3、曲三:重写onMeasure方法,根据不同的测量模式最后定义不同的宽度和高度。

`@onMeasure code

    @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // TODO Auto-generated method stub
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    System.out.println("I am onMeasure");
}

` 4、曲四:重写onDraw方法,画出相应的文本和圆形及扇形 本次的绘制出圆形进度条的原理是,先在矩形中间画出文本,然后接着就是画圆环(实际上就是一个一定厚度的圆,似圆环),然后以相同的圆心坐标,开启一个子线程动态的画扇形,子线程的目的就是动态每隔一定的时间就去增加绘制扇形的角度,动态绘制的扇形覆盖在原来的圆环上,就出现圆形进度效果和圆形loading的效果。

4.1:圆形进度条:

`@code

@Override
protected void onDraw(Canvas canvas) {
    myPaint.getTextBounds(myCircleText, 0, myCircleText.length(), myCircleTextBound);
    int textX=getWidth()/2-myCircleTextBound.width()/2+myCircleWidth/4;
    int textY=(getHeight()-myCircleTextBound.height())/2+myCircleTextBound.height()-myCircleWidth/2;
    myPaint.setStyle(Paint.Style.FILL);
    myPaint.setColor(myCircleTextColor);
    canvas.drawText(myCircleText, textX, textY, myPaint);
    int center=getWidth()/2;
    int radius=center-myCircleWidth;
    myPaint.setStrokeWidth(myCircleWidth);
    myPaint.setAntiAlias(true);
    myPaint.setStyle(Paint.Style.STROKE);
    /**
     * @author zhongqihong
     * RectF的四个参数:
     * left:带内边距内部左上角点的X坐标
     * top:带内边距内部左上角点的Y坐标
     * right:带内边距内部右下角点的X坐标
     * bottom:带内边距内部右下角点的Y坐标
     * 注意:此时的圆环实际上就是画一个一个圆,只是这个圆的边厚度尺寸更大,显示出是一个圆环
     * 圆的厚度,也即是RectF矩形的尺寸厚度
     * 
     * */
    /*RectF oval=new RectF(center-radius, center-radius, center+radius, center+radius);*/
    RectF oval=new RectF(center-radius, center-radius, (center-radius)+2*radius, (center-radius)+2*radius);
    myPaint.setColor(myFirstColor);
    //先用第一种颜色画出圆,第一个第二个参数是圆心坐标,第三个是半径
    canvas.drawCircle(center, center, radius, myPaint);
    //再用第二种颜色在圆形的基础上覆盖画出扇形,覆盖原来的圆轨迹
    myPaint.setStrokeWidth(myCircleWidth-13);
    myPaint.setAntiAlias(true);
    myPaint.setStyle(Paint.Style.STROKE);
    myPaint.setColor(mySecondColor);
    canvas.drawArc(oval, -90, currentProgress, false, myPaint);
}
/**
 * @author zhongqihong
 * 设置圆环开始的起始位置
 * */
public void setCurrentCircleProgress(int progress){
    currentProgress=progress;
    invalidate();
    requestLayout();
}
/**
 * @author zhongqihong
 * */
public void setMaxCircleProgress(int progress){
    maxProgress=progress;
    invalidate();
    requestLayout();
}
public void setCircleSpeed(int speed){
    mySpeed=speed;
    invalidate();
    requestLayout();
}

`

4.2:圆形loading:

`@code

    @Override
protected void onDraw(Canvas canvas) {
    /**
     * @author zhongqihong
     * 绘制中间文本
     * */
    myPaint.getTextBounds(myText,0, myText.length(), myCircleTextBound);//这里获取TextBound是因为每次重新绘制Text的边界,
    //都需要最新的边界尺寸,所以在画之前都必须重新拿到最新的文本的边界尺寸
    int textX=(getMeasuredWidth()-myCircleTextBound.width())/2+myCircleWidth/8;
    int textY=(getMeasuredHeight()-myCircleTextBound.height())/2+myCircleTextBound.height()-myCircleWidth/2;
    System.out.println("getMeasureWidth-------++++------>"+getMeasuredHeight());
    System.out.println("TextX----->"+textX+"    TextY-------->"+textY);
    myPaint.setAntiAlias(true);
    myPaint.setStyle(Paint.Style.FILL);
    myPaint.setTextSize(myCircleTextSize);
    if (!isNext) {
        myCircleTextColor=mySecondColor;
    }else{
        myCircleTextColor=myFirstColor;
    }
    myPaint.setColor(myCircleTextColor);
    canvas.drawText(myText, textX,textY, myPaint);
   /**
      * @author zhongqihong
      * 绘制圆环即圆环上的覆盖的扇形
      * */
    int center=getWidth()/2;
    int radius=center-myCircleWidth;
    myPaint.setStrokeWidth(myCircleWidth);
    myPaint.setAntiAlias(true);
    myPaint.setStyle(Paint.Style.STROKE);
    /**
     * @author zhongqihong
     * RectF的四个参数:
     * left:带内边距内部左上角点的X坐标
     * top:带内边距内部左上角点的Y坐标
     * right:带内边距内部右下角点的X坐标
     * bottom:带内边距内部右下角点的Y坐标
     * 注意:此时的圆环实际上就是画一个一个圆,只是这个圆的边厚度尺寸更大,显示出是一个圆环
     * 圆的厚度,也即是RectF矩形的尺寸厚度
     * 
     * */
    /*RectF oval=new RectF(center-radius, center-radius, center+radius, center+radius);*/
    RectF oval=new RectF(center-radius, center-radius, (center-radius)+2*radius, (center-radius)+2*radius);
    if (isNext) {
        myPaint.setColor(myFirstColor);
        //先用第一种颜色画出圆,第一个第二个参数是圆心坐标,第三个是半径
        canvas.drawCircle(center, center, radius, myPaint);
        //再用第二种颜色在圆形的基础上覆盖画出扇形,覆盖原来的圆轨迹
        myPaint.setColor(mySecondColor);
        canvas.drawArc(oval, -90, currentProgress, false, myPaint);
    }else{
        myPaint.setColor(mySecondColor);
        canvas.drawCircle(center, center, radius, myPaint);
        myPaint.setColor(myFirstColor);
        canvas.drawArc(oval, -90, currentProgress, false, myPaint);
    }
}

`

4.1绘制圆形进度条原理:

绘制扇形与上面一致,然后就是更新中间的文本,中间的文本就是根据当前处于子线程的那个自增的变量值,当前(刻度值/总的)*100+“%”产生一个百分比的效果去更新文本显示,如何使得动态的绘制扇形呢?通过postInvalidate方法,在子线程的循环中每次都调用该方法,该方法就是将原来的旧的绘制的图形在UI线程中pop掉,然后重新调用onMeasure,和onDraw方法重新绘制新的图形,每调用一次就pop旧的,插入新的,从而使得产生一个动态的绘制扇形的效果

4.2绘制圆形loading原理:

绘制扇形与上面一致,然后就是更新中间的文本,中间的文本就是根据当前处于子线程的那个自增的变量值,当该刻度值满了一圈就去更新中间的文本,如何使得动态的绘制扇形呢?通过postInvalidate方法,在子线程的循环中每次都调用该方法,该方法就是将原来的旧的绘制的图形在UI线程中pop掉,然后重新调用onMeasure,和onDraw方法重新绘制新的图形,每调用一次就pop旧的,插入新的,从而使得产生一个动态的绘制扇形的效果

注意:invalidate和postInvalidate异同:

相同点:Invalidate和postInvalidate的相同点都是把原来的旧的绘制图形从主UI线程中给pop掉,插入新的绘制图型。一般还用与set方法中,改变绘制图形的属性值的set方法中。 不同点:Invalidate是用于主线程中更新UI的,而postInvalidate则用于在子线程中调用,来更新UI 实际上它里面封装一个Handler对象发送消息,然后处理消息调用invalidate来更新主UI。

注意:requestLayout使用,用于主线程中,它的作用就是重新测量,否则调用invalidate后不调用requestLayout的话重绘的尺寸为原来旧的尺寸。一般调用完invalidate后就调用requestLayout

绘制原理图: 


效果图:

                            

源码下载:

源码下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

熊喵先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值