模拟器上运行有些锯齿,真机上和预期一样好
看效果,第一直觉肯定是Android原生态控件中没有这样的控件实现这种效果,自然想到应该需要自定义控件了,没错,这就是通过自定义控件来绘制的一个圆环进度条。仔细分析发现这个效果的进度条应该由几个部分组成,首先是无进度时的浅色圆环,然后是一个随进度变化的深色圆弧,而中间部分是一个深蓝色的实心圆,最后就是显示进度百分比的文字。这几部分大部分都是图形,所以使用图形绘制技术应该可以绘制出分部分效果,然后加上进度控制部分应该心里就有底了。
重写onDraw()方法的目的是为了找到绘制图形的时机与场所。
需求准备Paint画笔,并设置画笔宽度,颜色,样式等,然后绘制圆环。
重置Paint画笔,并设置画笔颜色,绘制进度圆弧。
测试进度圆弧是否会随进度的变化正常工作。
重置Paint画笔,并设置画笔颜色,绘制实心圆。
计算进度百分比,绘制文本。
package com.kedi.myprogressbar;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
public class MyProgressBar extends View {
public MyProgressBar(Context context) {
this(context, null);
}
public MyProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
}
有了自定义MyProgressBar,此时就可以在XML布局中先占位了,下面是XML布局文件。
Paint 画笔:
|- new Paint() 创建画笔|- setColor() 设置画笔颜色|- setStyle()设置画笔样式(Paint.Style.STROKE 虚框样式,Paint.Style.FILL 实心样式)|- setStrokeWidth()设置画笔线宽|- setAntiAlias()消除锯齿或毛边|- setTextSize()设置画文本时的文本大小|- setTypeface()设置画文本时的文本字体
Canvas画布:
|- drawCircle(cx, cy, radius, paint)绘制圆环或圆。cx,cy:圆心坐标,radius:圆半径,paint:画笔。|- drawText(text, x, y, paint)绘制文本。text:文本,x,y:文本坐标,paint:画笔。|- drawArc(oval, startAngle, sweepAngle, useCenter, paint)绘制圆弧。oval:矩形区域,用来确定圆弧形状和大小。new RectF(left, top, right, bottom)确定一个矩形区域。startAngle:圆弧始端角度。sweepAngle:圆弧末端角度。paint:画笔。userCenter:设置是否显示圆弧的两边线条,false时只画圆弧没有两边,true时带两边。如下图:
public class MyProgressBar extends View {
private Paint paint;// 画笔
public MyProgressBar(Context context) {
this(context, null);
}
public MyProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/**
* 初始化方法
*/
private void init() {
paint = new Paint();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
}
接下来绘制浅色圆环
private int roundW;// 圆环宽
/**
* 初始化方法
*/
private void init() {
paint = new Paint();
//初始化圆环宽,这里考虑了适配把15dp进行了对应平台的像素转换。
roundW = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, getResources().getDisplayMetrics());
}
// 1.圆心(x,y)坐标值
int centerX = getWidth() / 2;
int centerY = centerX;
// 2.圆环半径
int radius1 = (centerX - roundW / 2);
// 3.设置圆环颜色(浅色)
paint.setColor(Color.parseColor("#11339ED4"));
// 4.设置画笔的风格
paint.setStyle(Paint.Style.STROKE);
// 5.设置画圆环的宽度
paint.setStrokeWidth(roundW);
// 6.消除锯齿
paint.setAntiAlias(true);
// 7.画圆环
canvas.drawCircle(centerX, centerY, radius1, paint);
package com.kedi.myprogressbar;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
public class MyProgressBar extends View {
private Paint paint;// 画笔
private int roundW;// 圆环宽
public MyProgressBar(Context context) {
this(context, null);
}
public MyProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/**
* 初始化方法
*/
private void init() {
paint = new Paint();
// 初始化圆环宽,这里考虑了适配把15dp进行了对应平台的像素转换。
roundW = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, getResources().getDisplayMetrics());
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制浅色圆环
// 1.圆心(x,y)坐标值
int centerX = getWidth() / 2;
int centerY = centerX;
// 2.圆环半径
int radius1 = (centerX - roundW / 2);
// 3.设置画大圆环颜色
paint.setColor(Color.parseColor("#11339ED4"));
// 4.设置画笔的风格
paint.setStyle(Paint.Style.STROKE);
// 5.设置画圆环的宽度
paint.setStrokeWidth(roundW);
// 6.消除锯齿
paint.setAntiAlias(true);
// 7.画圆环
canvas.drawCircle(centerX, centerY, radius1, paint);
}
}
圆弧的绘制重点在开始角度和末端角度的计算和确定,因为我们的圆弧代表进度,所以开始角度和末端角度都与进度有关,会跟着进度的变化而变化,所以首先我们的确定当前进度和最大进度。然后就可以计算始末角度了。
private int progress = 0;// 当前进度值
private int maxProgress = 100;// 最大进度值
/**
* 更新进度和界面的方法
*
* @param progress
*/
public void setProgress(int progress) {
if (progress < 0) {
progress = 0;
} else {
this.progress = progress;
}
invalidate();
}
计算始末角度:
开始角度 0
末端角度 (float)360 * progress / (float)maxProgress
//绘制深色进度圆弧
// 1.设置圆孤的宽度
paint.setStrokeWidth(roundW);
// 2.设置圆孤进度的颜色
paint.setColor(Color.parseColor("#339ED4"));
// 3.定义圆弧的形状和大小区域
RectF oval = new RectF(centerX - radius1, centerY - radius1, centerX + radius1, centerY + radius1);
// 4.设置空心样式
paint.setStyle(Paint.Style.STROKE);
// 5.根据进度画圆弧
canvas.drawArc(oval, 0, (float)360 * progress / (float)maxProgress, false, paint);
package com.kedi.myprogressbar;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
public class MyProgressBar extends View {
private Paint paint;// 画笔
private int roundW;// 圆环宽
private int progress = 20;// 当前进度值
private int maxProgress = 100;// 最大进度值
/**
* 更新进度和界面的方法
*
* @param progress
*/
public void setProgress(int progress) {
if (progress < 0) {
progress = 0;
} else {
this.progress = progress;
}
invalidate();
}
public MyProgressBar(Context context) {
this(context, null);
}
public MyProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/**
* 初始化方法
*/
private void init() {
paint = new Paint();
roundW = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, getResources().getDisplayMetrics());
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制浅色圆环
// 1.圆心(x,y)坐标值
int centerX = getWidth() / 2;
int centerY = centerX;
// 2.圆环半径
int radius1 = (centerX - roundW / 2);
// 3.设置画大圆环颜色
paint.setColor(Color.parseColor("#11339ED4"));
// 4.设置画笔的风格
paint.setStyle(Paint.Style.STROKE);
// 5.设置画圆环的宽度
paint.setStrokeWidth(roundW);
// 6.消除锯齿
paint.setAntiAlias(true);
// 7.画圆环
canvas.drawCircle(centerX, centerY, radius1, paint);
// 绘制深色进度圆弧
// 1.设置圆孤的宽度
paint.setStrokeWidth(roundW);
// 2.设置圆孤进度的颜色
paint.setColor(Color.parseColor("#339ED4"));
// 3.定义圆弧的形状和大小区域界限
RectF oval = new RectF(centerX - radius1, centerY - radius1, centerX + radius1, centerY + radius1);
// 4.设置空心样式
paint.setStyle(Paint.Style.STROKE);
// 5.根据进度画圆弧
canvas.drawArc(oval, 0, (float) 360 * progress / (float) maxProgress, false, paint);
}
}
完整代码:
package com.kedi.myprogressbar;
import android.app.Activity;
import android.os.Bundle;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
public class MainActivity extends Activity {
//自定义进度条控件
private MyProgressBar pg;
//SeekBar控件
private SeekBar sb;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
initEvents();
}
/**
* 初始化View
*/
private void initViews(){
pg =(MyProgressBar) findViewById(R.id.pg);
sb = (SeekBar) findViewById(R.id.sb);
}
/**
* 初始化事件监听与处理
*/
private void initEvents() {
sb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
//改变圆弧的进度,并重新绘制圆弧,主要是通过触发自定义控件的onDraw()方法达到目的
pg.setProgress(progress);
}
});
}
}
实心圆的绘制也是通过drawCircle()方法,只是画笔的样式为FILL实心,所以比较简单。
核心代码:
// 绘制深蓝色实心圆
// 1.实心圆半径
int radius2 = centerX - roundW;
// 2.实心圆颜色
paint.setColor(Color.parseColor("#336799"));
// 3.设置画笔风格为实心
paint.setStyle(Paint.Style.FILL);
// 4.画实心圆
canvas.drawCircle(centerX, centerY, radius2, paint);
效果图:
绘制文本需要知道文本大小,文本字体,文本颜色,文本x,y坐标位置以及文本内容。
文本大小我们定义个成员变量,然后在init()方法中初始化,当然可以对外提供接口方法,设置文本大小,
文本颜色使用白色,当然也可以像文本大小那样定义成员变量,然后初始化,也可对外提供接口方法,设置文本颜色
文本x,y坐标需要根据圆心坐标与文本自身的宽高进行计算。
文本内容是进度百分比,需要根据进度进行计算。(float) progress / (float) maxProgress) * 100
核心逻辑:
// 绘制百分比文本
// 1.设置无边框
paint.setStrokeWidth(0);
// 2.设置字体颜色
paint.setColor(Color.WHITE);
paint.setAntiAlias(true);
// 3.设置字体大小,定义成员变量textSize,然后在初始化方法中赋初始值
paint.setTextSize(textSize);
// 4.设置字体
paint.setTypeface(Typeface.DEFAULT_BOLD);
// 5.计算进度百分比
int percent = (int) (((float) progress / (float) maxProgress) * 100);
// 6.测量字体宽度
float textWidth = paint.measureText(percent + "%");
// 7.画出进度百分比文本
canvas.drawText(percent + "%", centerX - textWidth / 2, centerY + textSize / 2, paint);
完整代码:
package com.kedi.myprogressbar;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
public class MyProgressBar extends View {
private Paint paint;// 画笔
private int roundW;// 圆环宽
private int textSize;// 字体大小
private int progress = 0;// 当前进度值
private int maxProgress = 100;// 最大进度值
/**
* 更新进度和界面的方法
*
* @param progress
*/
public void setProgress(int progress) {
if (progress < 0) {
progress = 0;
} else {
this.progress = progress;
}
invalidate();
}
/**
* @param context
*/
public MyProgressBar(Context context) {
this(context, null);
}
/**
* @param context
* @param attrs
*/
public MyProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* @param context
* @param attrs
* @param defStyleAttr
*/
public MyProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/**
* 初始化方法
*/
private void init() {
paint = new Paint();
roundW = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, getResources().getDisplayMetrics());
textSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 25, getResources().getDisplayMetrics());
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制浅色圆环
// 1.圆心(x,y)坐标值
int centerX = getWidth() / 2;
int centerY = centerX;
// 2.圆环半径
int radius1 = (centerX - roundW / 2);
// 3.设置画大圆环颜色
paint.setColor(Color.parseColor("#11339ED4"));
// 4.设置画笔的风格
paint.setStyle(Paint.Style.STROKE);
// 5.设置画圆环的宽度
paint.setStrokeWidth(roundW);
// 6.消除锯齿
paint.setAntiAlias(true);
// 7.画圆环
canvas.drawCircle(centerX, centerY, radius1, paint);
// 绘制深色进度圆弧
// 1.设置圆孤的宽度
paint.setStrokeWidth(roundW);
// 2.设置圆孤进度的颜色
paint.setColor(Color.parseColor("#339ED4"));
// 3.定义圆弧的形状和大小区域界限
RectF oval = new RectF(centerX - radius1, centerY - radius1, centerX + radius1, centerY + radius1);
// 4.设置空心样式
paint.setStyle(Paint.Style.STROKE);
// 5.根据进度画圆弧
canvas.drawArc(oval, 0, (float) 360 * progress / (float) maxProgress, false, paint);
// 绘制深蓝色实心圆
// 1.实心圆半径
int radius2 = centerX - roundW;
// 2.实心圆颜色
paint.setColor(Color.parseColor("#336799"));
// 3.设置画笔风格为实心
paint.setStyle(Paint.Style.FILL);
// 4.画实心圆
canvas.drawCircle(centerX, centerY, radius2, paint);
// 绘制百分比文本
// 1.设置无边框
paint.setStrokeWidth(0);
// 2.设置字体颜色
paint.setColor(Color.WHITE);
// 3.设置字体大小
paint.setTextSize(textSize);
// 4.设置字体
paint.setTypeface(Typeface.DEFAULT_BOLD);
// 5.计算进度百分比
int percent = (int) (((float) progress / (float) maxProgress) * 100);
// 6.测量字体宽度
float textWidth = paint.measureText(percent + "%");
// 7.画出进度百分比文本
canvas.drawText(percent + "%", centerX - textWidth / 2, centerY + textSize / 2, paint);
}
}
效果图:
效果已经实现完了,但是如果我的项目中需要一个其它主题风格的进度条,那我们不得不重新Copy一份,然后修改其中的颜色值什么的,这样的自定义控件显然不够灵活和通用,如果进度条提供了对主题风格的定制接口那就灵活不少,所以接下来做为扩展部分,要做的是为进度条提供主题风格定制途径---自定义属性与Setter方法。
经分析,把与样式相关的特征都定义成成员变量,有如下几个:
//圆环相关成员变量
private int roundW;// 圆环宽
private int roundColor;//圆环颜色
//圆弧相关成员变量
private int progress;// 当前进度值
private int progressColor;//进度圆弧颜色
private int maxProgress = 100;// 最大进度值
//实心圆相关成员变量
private int circleColor;//实心圆颜色
//百分比文本相关成员变量
private int textColor;//字体颜色
private int textSize;// 字体大小
定义自定义属性文件values/attrs.xml,并定义成上面成员变量一一对应的自定义属性:
构造方法中获取自定义属性,然后初始化上面的那几个成员变量(将原来的值做为默认值):public MyProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
private void init(AttributeSet attrs) {
paint = new Paint();
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.MyProgressBarSytle);
roundW = (int) typedArray.getDimension(R.styleable.MyProgressBarSytle_roundW, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, getResources().getDisplayMetrics()));
roundColor = typedArray.getColor(R.styleable.MyProgressBarSytle_roundColor, Color.parseColor("#11339ED4"));
progress = typedArray.getInt(R.styleable.MyProgressBarSytle_progress, 0);
progressColor = typedArray.getColor(R.styleable.MyProgressBarSytle_progressColor, Color.parseColor("#339ED4"));
circleColor = typedArray.getColor(R.styleable.MyProgressBarSytle_circleColor, Color.parseColor("#336799"));
textColor = typedArray.getColor(R.styleable.MyProgressBarSytle_textColor, Color.parseColor("#ffffff"));
textSize = (int) typedArray.getDimension(R.styleable.MyProgressBarSytle_textSize, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 25, getResources().getDisplayMetrics()));
typedArray.recycle();
}
将绘制逻辑各分部中样式原来的值,换成成员变量:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制浅色圆环
// 1.圆心(x,y)坐标值
int centerX = getWidth() / 2;
int centerY = centerX;
// 2.圆环半径
int radius1 = (centerX - roundW / 2);
// 3.设置画大圆环颜色
paint.setColor(roundColor);
// 4.设置画笔的风格
paint.setStyle(Paint.Style.STROKE);
// 5.设置画圆环的宽度
paint.setStrokeWidth(roundW);
// 6.消除锯齿
paint.setAntiAlias(true);
// 7.画圆环
canvas.drawCircle(centerX, centerY, radius1, paint);
// 绘制深色进度圆弧
// 1.设置圆孤的宽度
paint.setStrokeWidth(roundW);
// 2.设置圆孤进度的颜色
paint.setColor(progressColor);
// 3.定义圆弧的形状和大小区域界限
RectF oval = new RectF(centerX - radius1, centerY - radius1, centerX + radius1, centerY + radius1);
// 4.设置空心样式
paint.setStyle(Paint.Style.STROKE);
// 5.根据进度画圆弧
canvas.drawArc(oval, 0, (float) 360 * progress / (float) maxProgress, false, paint);
// 绘制深蓝色实心圆
// 1.实心圆半径
int radius2 = centerX - roundW;
// 2.实心圆颜色
paint.setColor(circleColor);
// 3.设置画笔风格为实心
paint.setStyle(Paint.Style.FILL);
// 4.画实心圆
canvas.drawCircle(centerX, centerY, radius2, paint);
// 绘制百分比文本
// 1.设置无边框
paint.setStrokeWidth(0);
// 2.设置字体颜色
paint.setColor(textColor);
paint.setAntiAlias(true);
// 3.设置字体大小
paint.setTextSize(textSize);
// 4.设置字体
paint.setTypeface(Typeface.DEFAULT_BOLD);
// 5.计算进度百分比
int percent = (int) (((float) progress / (float) maxProgress) * 100);
// 6.测量字体宽度
float textWidth = paint.measureText(percent + "%");
// 7.画出进度百分比文本
canvas.drawText(percent + "%", centerX - textWidth / 2, centerY + textSize / 2, paint);
}
如果我们希望通过代码也可以修改这些样式值,就对外提供Setter方法:
/**
* 设置圆环宽度
* @param roundW
*/
public void setRoundW(int roundW) {
this.roundW = roundW;
}
/**
* 设置圆环颜色
* @param roundColor
*/
public void setRoundColor(int roundColor) {
this.roundColor = roundColor;
}
/**
* 设置进度圆弧颜色
* @param progressColor
*/
public void setProgressColor(int progressColor) {
this.progressColor = progressColor;
}
/**
* 设置最大进度值
* @param maxProgress
*/
public void setMaxProgress(int maxProgress) {
this.maxProgress = maxProgress;
}
/**
* 设置实心圆颜色
* @param circleColor
*/
public void setCircleColor(int circleColor) {
this.circleColor = circleColor;
}
/**
* 设置百分比进度文本颜色
* @param textColor
*/
public void setTextColor(int textColor) {
this.textColor = textColor;
}
/**
* 设置百分比进度文本字体大小
* @param textSize
*/
public void setTextSize(int textSize) {
this.textSize = textSize;
}
/**
* 更新进度和界面的方法
*
* @param progress
*/
public void setProgress(int progress) {
if (progress < 0) {
progress = 0;
} else {
this.progress = progress;
}
invalidate();
}
最后演示一下使用自定义属性定制主题的方式:
(1)在根布局中添加命名空间
xmlns:my="http://schemas.android.com/apk/res-auto"
(2)使用自定义属性并指定属性值
完整布局代码:
效果图:
到此就完成了比较灵活的圆形进度条的制作。