#自定义带图标数字圆形进度条
1.首先新建一个类MySelfView,集成View
public class MySelfView extends View {
public MySelfView(Context context) {
this(context,null);
}
public MySelfView(Context context, @Nullable AttributeSet attrs) {
this(context,attrs,0);
}
@SuppressLint("ResourceAsColor")
public MySelfView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
2.在arrts.xml中设计需要自定义的属性,name要和类名一样的。
<declare-styleable name="MySelfView">
内环颜色
<attr name="circle_in_bg" format="color"/>
外环颜色
<attr name="circle_out_bg" format="color"/>
环宽
<attr name="circle_width" format="integer"/>
当前进度数值
<attr name="circle_num" format="integer"/>
内环图标
<attr name="circle_icon" format="reference"/>
</declare-styleable>
在xml视图中引用
<com.ui.view.MySelfView
android:id="@+id/sv_humidity"
android:layout_width="72dp"
android:layout_height="72dp"
circleview:circle_icon="@mipmap/icon_battery10"
circleview:circle_in_bg="#3C6366"
circleview:circle_num="30"
circleview:circle_out_bg="#4FB8F5"
circleview:circle_width="8" />
3.在构造函数中引用定义刚刚自定义的属性,并设置默认值
private int width = 0;
private int height = 0;
private int defaultsize = 100;
private int mArcCenterX;
private int mCircleInBg = getResources().getColor(R.color.colorAccent1);
private int mCircleOutBg = getResources().getColor(R.color.colorAccent5);
private int mCircleWidth = 20;//圆弧的宽度
private int mCircleNum=0;//显示百分比
private int mIntervalWith=5;
......
public MySelfView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MySelfView);
mCircleInBg = a.getInteger(R.styleable.MySelfView_circle_in_bg, mCircleInBg);
mCircleOutBg = a.getInteger(R.styleable.MySelfView_circle_out_bg, mCircleOutBg);
mCircleWidth = a.getInteger(R.styleable.MySelfView_circle_width, mCircleWidth);
mCircleNum=a.getInteger(R.styleable.MySelfView_circle_num,mCircleNum);
a.recycle();
}
4.定义各个画笔属性,画笔的初始化也在构造函数中,避免多次重复创建画笔占用过多内存
private Paint mTextPaint;//中间文字
private Paint mImgPaint;//中间图标
private Paint inPaint;//底部圆弧画笔
private Paint outPaint;//外部圆弧画笔
private Paint inSelPaint;//圆弧百分比显示画笔
......
public MySelfView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
/**
* Paint.Style.FILL:填充内部
* Paint.Style.FILL_AND_STROKE :填充内部和描边
* Paint.Style.STROKE :描边
*/
inPaint.setColor(mCircleInBg);
inPaint.setStrokeWidth(mCircleWidth);
inPaint.setStyle(Paint.Style.STROKE);
//设置抗锯齿,如果不设置,加载位图的时候可能会出现锯齿状的边界,如果设置,边界就会变的稍微有点模糊,锯齿就看不到了。
inPaint.setAntiAlias(true);//去锯齿
inPaint.setStrokeCap(Paint.Cap.ROUND); // 设置转弯处为圆角
outPaint=new Paint();
outPaint.setColor(getResources().getColor(R.color.white));
outPaint.setStrokeWidth(mIntervalWith);
outPaint.setStyle(Paint.Style.FILL);
//设置抗锯齿,如果不设置,加载位图的时候可能会出现锯齿状的边界,如果设置,边界就会变的稍微有点模糊,锯齿就看不到了。
outPaint.setAntiAlias(true);//去锯齿
inSelPaint = new Paint();
inSelPaint.setColor(getResources().getColor(R.color.colorAccent2));
inSelPaint.setStrokeWidth(mCircleWidth);
inSelPaint.setStyle(Paint.Style.STROKE);
//设置抗锯齿,如果不设置,加载位图的时候可能会出现锯齿状的边界,如果设置,边界就会变的稍微有点模糊,锯齿就看不到了。
inSelPaint.setAntiAlias(true);//去锯齿
// inSelPaint.setStrokeCap(Paint.Cap.ROUND); // 设置转弯处为圆角
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mImgPaint=new Paint();
mImgPaint.setAntiAlias(true);
}
5.在onMeasure方法中定义图形宽高,因为要绘制的是圆形,是宽高相等的,所以计算宽高,获取短的为圆环直径
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = getSize(widthMeasureSpec);
height = getSize(heightMeasureSpec);
if (width < height) {
height = width;
} else {
width = height;
}
setMeasuredDimension(width, height);
}
private int getSize(int measureSpec) {
int mySize = defaultsize;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
switch (mode) {
case MeasureSpec.UNSPECIFIED: {//如果没有指定大小:就设置为默认大小
mySize = defaultsize;
break;
}
case MeasureSpec.AT_MOST: {//如果测里模式是最大取值为size
//我们将大小取最大值,你也可以取其他值
mySize = size;
break;
}
case MeasureSpec.EXACTLY: {//如果是固定的大小,那就不要去改变它
mySize = size;
break;
}
}
return mySize;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mArcCenterX = (int) (w / 2.f);
}
6.在onDraw中进行绘制
先绘制图标,根据图标的位置来计算绘制文字的位置,然后绘制底部圆环,外部圆环
canvas中的rotate方法是绘制圆环的起始角度(起始位置)。
注意:RectF是从左上角开始算起,绘制位置的时候需要考虑到图标和文字的宽度高度
这里简单介绍下Canvas绘制:
画文字:
//文本的x轴的开始位置,文本Y轴的结束位置,画笔对象
canvas.drawText(“开始”, 50, 50, p);
//参数2:路径,参数3:距离路径开始位置的偏移量,参数4:距离路径上下的偏移量(可以为负数)
canvas.drawTextOnPath(“123456”, path, 0, -50, p);
画圆:
//圆心X 圆心Y 半径R
canvas.drawCircle(20,20,10, p);
画线
canvas.drawLine(20,20,10, 20, p);
画一个椭圆
RectF oval = new RectF(15,20,50,40);
canvas.drawOval(oval, p);
画弧度
canvas.drawArc(oval,20,180,false, p);
矩形
canvas.drawRect(10,10,20,20, p);
画圆角矩形
RectF oval3 = new RectF(8,26,20,30);
canvas.drawRoundRect(oval3,20,5, p);
画点
canvas.drawPoint(60,390, p);
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mTextPaint.setTextSize(26);
canvas.drawText("1234",mArcCenterX-mTextPaint.measureText("1234")/2,height/2,mTextPaint);
// Bitmap bmp = BitmapFactory.decodeResource(getResources(),R.mipmap.icon_battery10);
Bitmap bmp= ImageUtil.drawableToBitamp(getResources().getDrawable(R.mipmap.icon_battery10));
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bmp, 53, 53, true);
canvas.drawBitmap(scaledBitmap,width/2-scaledBitmap.getWidth()/2,height/2-scaledBitmap.getHeight()/2-mTextPaint.measureText("1234"),mImgPaint);
bmp.recycle();
canvas.rotate(135, mArcCenterX, mArcCenterX);
//绘制大圆环
int viewRadius = getWidth() / 2;
int canterRadius = (getWidth() - mCircleWidth) / 2;
RectF rectF = new RectF(mCircleWidth / 2, mCircleWidth / 2, canterRadius+viewRadius, canterRadius+viewRadius);
canvas.drawArc(rectF, 0, 360, false, inPaint);//底部弧形
canvas.drawArc(rectF, 0, (float) (mCircleNum*3.6), false, inSelPaint);//表示进度的弧形
//绘制刻度线
canvas.translate(width / 2, height / 2);
for (int i = 0; i < 10; i++) {//60等分
canvas.save();//画布保存
canvas.rotate(360 + i * 36);//绘制图标的旋转
// int alpha = (int) ((i / 60f * 255 + circleAlpha) % 255);
// mPaint.setAlpha(alpha);//设置画笔的透明度[0-255],0是完全透明,255是完全不透明
canvas.translate(width/2-mCircleWidth, 0);//绘图坐标的平移。
canvas.drawLine(0, 0, mCircleWidth, 0, outPaint);//绘制线.drawLine,drawLines绘制多条线
canvas.restore();//合并保存后的图层
}
}
将drawable转bitmap
public static Bitmap drawableToBitamp(Drawable drawable)
{
//声明将要创建的bitmap
Bitmap bitmap = null;
//获取图片宽度
int width = drawable.getIntrinsicWidth();
//获取图片高度
int height = drawable.getIntrinsicHeight();
//图片位深,PixelFormat.OPAQUE代表没有透明度,RGB_565就是没有透明度的位深,否则就用ARGB_8888。详细见下面图片编码知识。
Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
//创建一个空的Bitmap
bitmap = Bitmap.createBitmap(width,height,config);
//在bitmap上创建一个画布
Canvas canvas = new Canvas(bitmap);
//设置画布的范围
drawable.setBounds(0, 0, width, height);
//将drawable绘制在canvas上
drawable.draw(canvas);
return bitmap;
}
7.如果需要动态加载圆弧进度,添加修改进度值的方法,修改后调用invalidate()方法刷新UI
//设置当前进度
public void setmCircleNum(int mCircleNum) {
this.mCircleNum = mCircleNum;
invalidate();
}
完结,以上已经为全部代码
补充知识:
- canvas.drawArc绘制圆时的坐标方向是顺时针,顺时针为正,逆时针为负;右0,下90,左180,上270
/**
* 第二个参数:startAngle起始的角度
* 第三个参数:sweepAngle绘制的弧度
* userCenter如果为true,则将椭圆的中心包括在圆弧中
*/
canvas.drawArc(rectF, 110, 1, false, paint);//画圆弧
2.RectF rectF = new RectF(left,top,right,bottom)
左上为起点,右下为终点
- 参数一:left–矩形左侧的X坐标,矩形区域左上角的横坐标
- 参数二:top–矩形顶部的Y坐标 ,矩形区域左上角的纵坐标
- 参数三:right–矩形右侧的X坐标,矩形区域右下角的横坐标
- 参数四:bottom–矩形底部的Y坐标,矩形区域右下角的纵坐标
3.设置渐变色画笔
//渐变色颜色组
private int[] colors1 = new int[]{
Color.parseColor("#9ce3d5"),
Color.parseColor("#84cecf"),
Color.parseColor("#65b6c7")
};
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
......
//渐变色角度设置
int cY = getHeight() / 2;
int cX = cY;
if (getHeight() > getWidth()) {
cX = getWidth() / 2;
cY = cX;
}
LinearGradient gradientRight = new LinearGradient(cX - radius, cY - radius, // 渐变区域,左上右下
cX + radius, cY + radius, colors1, null, Shader.TileMode.REPEAT);
rightCirclePaint.setShader(gradientRight);
canvas.drawArc(rectF, 10, 100, false, paint);//画圆弧
}
绘制多圆弧进度代码:
public class ThreePercentageView extends View {
//左边圆弧画笔
private Paint leftCirclePaint;
//右边圆弧画笔
private Paint rightCirclePaint;
//中间画笔
private Paint centerCirclePaint;
private Paint leftPaint, rightPaint, centerPaint;
//圆弧宽度
private int circleWidth = 30;
private int indexCircle = 10;//当前进度
private int width = 0;
private int height = 0;
private int topIndex = 50, leftIndex = 50, rightIndex = 50;
/**
* 外层圆弧需要
*/
private int mArcCenterX;
private int[] colors1 = new int[]{
Color.parseColor("#9ce3d5"),
Color.parseColor("#84cecf"),
Color.parseColor("#65b6c7")
};
private int[] colors2 = new int[]{
Color.parseColor("#40598c"),
Color.parseColor("#348ba9"),
Color.parseColor("#32b0b4")
};
private int[] colors3 = new int[]{
Color.parseColor("#5a09fb"),
Color.parseColor("#7215f4"),
Color.parseColor("#9527ea")
};
public ThreePercentageView(Context context) {
this(context, null);
}
public ThreePercentageView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ThreePercentageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PercentageView);
circleWidth = a.getInteger(R.styleable.PercentageView_circle_width, circleWidth);
indexCircle = a.getInteger(R.styleable.PercentageView_index_circle, indexCircle);
/**
* Paint.Style.FILL:填充内部
* Paint.Style.FILL_AND_STROKE :填充内部和描边
* Paint.Style.STROKE :描边
*/
leftCirclePaint = new Paint();
leftCirclePaint.setStrokeWidth(circleWidth);
leftCirclePaint.setStyle(Paint.Style.STROKE);
//设置抗锯齿,如果不设置,加载位图的时候可能会出现锯齿状的边界,如果设置,边界就会变的稍微有点模糊,锯齿就看不到了。
leftCirclePaint.setAntiAlias(true);//去锯齿
leftCirclePaint.setStrokeCap(Paint.Cap.ROUND); // 设置转弯处为圆角
rightCirclePaint = new Paint();
rightCirclePaint.setStrokeWidth(circleWidth);
rightCirclePaint.setStyle(Paint.Style.STROKE);
//设置抗锯齿,如果不设置,加载位图的时候可能会出现锯齿状的边界,如果设置,边界就会变的稍微有点模糊,锯齿就看不到了。
rightCirclePaint.setAntiAlias(true);//去锯齿
rightCirclePaint.setStrokeCap(Paint.Cap.ROUND); // 设置转弯处为圆角
centerCirclePaint = new Paint();
centerCirclePaint.setStrokeWidth(circleWidth);
centerCirclePaint.setStyle(Paint.Style.STROKE);
//设置抗锯齿,如果不设置,加载位图的时候可能会出现锯齿状的边界,如果设置,边界就会变的稍微有点模糊,锯齿就看不到了。
centerCirclePaint.setAntiAlias(true);//去锯齿
centerCirclePaint.setStrokeCap(Paint.Cap.ROUND); // 设置转弯处为圆角
leftPaint = new Paint();
leftPaint.setStrokeWidth(circleWidth);
leftPaint.setStyle(Paint.Style.STROKE);
leftPaint.setColor(getResources().getColor(R.color.white));
leftPaint.setAntiAlias(true);
leftPaint.setStrokeCap(Paint.Cap.ROUND);
rightPaint = new Paint();
rightPaint.setStrokeWidth(circleWidth);
rightPaint.setStyle(Paint.Style.STROKE);
rightPaint.setColor(getResources().getColor(R.color.white));
rightPaint.setAntiAlias(true);
rightPaint.setStrokeCap(Paint.Cap.ROUND);
centerPaint = new Paint();
centerPaint.setStrokeWidth(circleWidth);
centerPaint.setStyle(Paint.Style.STROKE);
centerPaint.setColor(getResources().getColor(R.color.white));
centerPaint.setAntiAlias(true);
centerPaint.setStrokeCap(Paint.Cap.ROUND);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = getSize(widthMeasureSpec);
height = getSize(heightMeasureSpec);
if (width < height) {
height = width;
} else {
width = height;
}
setMeasuredDimension(width, height);
}
private int getSize(int measureSpec) {
int mySize = 100;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
switch (mode) {
case MeasureSpec.UNSPECIFIED: {//如果没有指定大小:就设置为默认大小
mySize = 100;
break;
}
case MeasureSpec.AT_MOST: //如果测里模式是最大取值为size,我们将大小取最大值,你也可以取其他值
case MeasureSpec.EXACTLY: {//如果是固定的大小,那就不要去改变它
mySize = size;
break;
}
}
return mySize;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w < h) {
mArcCenterX = (int) (w / 2.f);
} else {
mArcCenterX = (int) (h / 2.f);
}
super.onSizeChanged(w, h, oldw, oldh);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int viewRadius = getHeight() / 2;
int canterRadius = (getHeight() - circleWidth) / 2;
RectF rectF = new RectF(circleWidth / 2, circleWidth / 2, canterRadius + viewRadius, canterRadius + viewRadius);
float radius = getHeight() / 2; //圆半径
int cY = getHeight() / 2;
int cX = cY;
if (getHeight() > getWidth()) {
cX = getWidth() / 2;
cY = cX;
}
canvas.rotate(-30, mArcCenterX, mArcCenterX);
LinearGradient gradientRight = new LinearGradient(cX - radius, cY - radius, // 渐变区域,左上右下
cX + radius, cY + radius, colors1, null, Shader.TileMode.REPEAT);
rightCirclePaint.setShader(gradientRight);
canvas.drawArc(rectF, 10, 100, false, rightCirclePaint);//画圆弧
LinearGradient gradient2 = new LinearGradient(cX - radius, cY - radius, // 渐变区域,左上右下
cX + radius, cY + radius, colors2, null, Shader.TileMode.REPEAT);
leftCirclePaint.setShader(gradient2);
canvas.drawArc(rectF, 130, 100, false, leftCirclePaint);//画圆弧
LinearGradient gradient3 = new LinearGradient(cX - radius, cY - radius, // 渐变区域,左上右下
cX + radius, cY + radius, colors3, null, Shader.TileMode.REPEAT);
centerCirclePaint.setShader(gradient3);
canvas.drawArc(rectF, 250, 100, false, centerCirclePaint);//画圆弧
/**
* 画的方向为顺时针,顺时针为正,逆时针为负,右0,下90,左180,上270
* startAngle起始的角度
* sweepAngle绘制的弧度
* userCenter如果为true,则将椭圆的中心包括在圆弧中
*/
//左边起始角度130
//top起始角度250
//右边起始角度10
// 最大angle是100度,根据当前数据来计算需要的角度,
// 左边:弧度angle=100*num/max,绘制的偏移角度
canvas.drawArc(rectF, 110 - rightIndex, 1, false, rightPaint);//画圆弧
canvas.drawArc(rectF, 130 + leftIndex, 1, false, leftPaint);//画圆弧
canvas.drawArc(rectF, 250 + topIndex, 1, false, centerPaint);//画圆弧
}
//绘制的偏移角度
public void setAngle(int top, int left, int right) {
this.leftIndex = left;
this.rightIndex = right;
this.topIndex = top;
invalidate();
}
}