今天说下怎么自定义一个简单的进度条=。=
自定义View首要确定两个东西,第一个就是 View 的大小,第二个就是绘图位置!!剩下的就是画笔啥的,这方面的就没有什么好讲的了。。。
确定view的大小
首先,确定View 的大小,和自定义ViewGroup一样,重写onMeasure() 方法来测量:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = 0;
int height = 0;
if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY){
width = MeasureSpec.getSize(widthMeasureSpec);
}
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY){
height = MeasureSpec.getSize(heightMeasureSpec);
}
//如果边长有预设值,则取最长的边组成正方形,否则用半径*2 + padding作为边长
if ( width > height ){
height = width;
}else if ( width < height){
width = height;
}else if ( width == 0 && height == 0){
width = circleR *2;
height = circleR * 2;
}
setMeasuredDimension(width + padding, height + padding);
}
其中还是要优先使用布局中给好的尺寸,先得到长宽的模式啥的。(在这篇自定义ViewGroup里面已经讲过了:http://blog.csdn.net/ocwvar/article/details/50682213)
由于咱们的是圆形,所以咱们要统一长宽为一样的尺寸来组合成正方形。
如果布局中没有规定尺寸,咱们就使用圆圈的半径+padding来确定View的大小。上面代码已经写得很清楚了。
确定画布大小位置
由于View的绘制是在一张 Canvas上面 的,所以咱们 使用RectF 来定位绘图位置。
方法generateCircelPosition()
private void generateCircelPosition(){
reDrawCircle = false; //需要重绘标记归位
float cLeft,cRight,cTop,cBottom;
cLeft = ( getMeasuredWidth() - 2*circleR ) / 2;
cRight = ( cLeft + 2*circleR );
cTop = ( getMeasuredHeight() - 2*circleR ) / 2;
cBottom = ( cTop + 2*circleR );
circlePosition = new RectF( cLeft , cTop , cRight , cBottom );
}
咱们要让圆圈居中显示,而且咱们在确定View大小的时候已经让其成为了正方形,所以咱们的坐标为:
- 左坐标:( View总长 - 直径 ) / 2
- 右坐标:左坐标 + 直径
- 顶坐标:( View总高 - 直径 ) / 2
- 底坐标:顶坐标 + 直径
新建文字画笔
创建方法
initPaintText()
private void initPaintText(){
if (paintText == null || reDrawText){
reDrawText = false; //重绘文字标记归位
paintText = new Paint();
if (textColor == 0){
paintText.setColor(Color.DKGRAY);
}else {
paintText.setColor(textColor);
}
paintText.setAntiAlias(true);
paintText.setTextSize(textSize);
paintText.setTextAlign(Paint.Align.CENTER);
Paint.FontMetricsInt fontMetrics = paintText.getFontMetricsInt();
textBaseLine = (circlePosition.bottom + circlePosition.top - fontMetrics.bottom - fontMetrics.top) / 2;
}
}
咱们先介绍这些画笔的属性
- setColor 设置画笔颜色
- setAntiAlias 是否使用反锯齿
- setTextSize 顾名思义,文字大小
- setTextAlign 文字居中 ( 特指文字从中开始出现,与文字整体坐标无关 )
关于文字坐标垂直居中
也就是最下面两行代码,来确定文字的垂直位置。
我这里参考了这篇文章:http://blog.csdn.net/hursing/article/details/18703599
新建圆圈画笔
创建方法
initPaintCircle()
private void initPaintCircle(){
if (paintcircle == null || reDrawProgressBar){
reDrawProgressBar = false; //重绘标记归位
paintcircle = new Paint();
paintcircle.setAntiAlias(true);
paintcircle.setColor(progressBarColor);
paintcircle.setStyle(Paint.Style.STROKE);
paintcircle.setStrokeWidth(5f);
}
}
- setStyle 设定画笔风格 ( 这里我们设置成 空心 )
- setStrokeWidth 设定画笔厚度
绘制图像onDraw()
这个方法是自定义View中几乎都要重写的方法 ( 除非你的View不需要显示东西…… )
onDraw(Canvas canvas)
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (circlePosition == null || reDrawCircle){
generateCircelPosition(); //首次绘制或需要重绘。计算绘制画布位置
}
if (showCircle){
initPaintCircle();
canvas.drawArc(circlePosition, -90, (progress * (360f / max)), false, paintcircle);
}
if (showText){
initPaintText();
if (!TextUtils.isEmpty(text)){
canvas.drawText(text,circlePosition.centerX(),textBaseLine,paintText);
}else {
canvas.drawText(Integer.toString(progress)+"%",circlePosition.centerX(),textBaseLine,paintText);
}
}
}
这里要好好解释下canvas.drawArc、canvas.drawText
canvas.drawArc(circlePosition, -90, (progress * (360f / max)), false, paintcircle);
drawArc绘制扇形
- 参数1:绘图位置。这里就是咱们确定下来的画布绘制位置。
- 参数2:起始绘制角度。12点位置是 -90° 所以咱们从这里开始绘制
- 参数3:结束绘制角度。咱们根据进度 progress 来确定占多少份360°
- 参数4:是否绘制到圆心的线。上面动态图中的是不使用的,下面有个使用的样式图。
- 参数4:绘制画笔。咱们用之前做好的画笔 paintcircle
绘制到圆心的线后:
canvas.drawText(Integer.toString(progress)+"%",circlePosition.centerX(),textBaseLine,paintText);
drawText绘制文字
- 参数1:绘制的文字。
- 参数2:绘制X坐标。这里使用绘制区域的中间
- 参数3:绘制Y坐标。这里使用计算好的垂直居中的坐标
- 参数4:绘制画笔。使用之前做好的画笔 paintText
请求更新图像
这里用更新进度方法来做例子
创建方法
setProgress(int progress)
public void setProgress(int progress){
this.progress = progress;
ViewCompat.postInvalidateOnAnimation(this);
}
这里使用 ViewCompat.postInvalidateOnAnimation(this); 来请求更新图像。就不需要用原来的那个 invalidate() 来更新了,毕竟这个不是异步更新的。
完毕