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
绘制原理图:
效果图:
源码下载:
源码下载