自定义CircleProgressBar
来源:互联网 发布:qq五笔mac版 编辑:程序博客网 时间:2024/06/08 06:43
概述
这是一个自定义实线分数,进度展示的progressBar。
实现的效果差强人意,但我们可以一起探究下自定义控件的过程。
除此之外,我发现绘制弧线的过程,相对于绘制其他的图形,是比较难以理解的。我后面总结下绘制弧线的过程。
效果图
因为第一次做gif图形,效果不好。其实图形的变化是连贯的。
1. 实线实现的效果
2. 虚线实线的效果
实现过程分析
1.自定义控件,有两个方法几乎是必写的,那就是onMeasure(int widthMeasureSpec, int heightMeasureSpec)和onDraw(Canvas canvas)方法。
onMeasure()方法实现测量。如果我们直接将控件的大小给一个确定值或者match_parent,就可以不用重写该方法。
如果我们将控件的宽高属性设置为wrap_content,那么我们就必须重写该方法了,毕竟,若你不告诉系统,系统怎么会知道这个content是多大呢。
/** * 如果测量模式不是EXACTLY(即精确测量),我们就将该View的长和宽都设置为250dp。 * * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int viewWidth,viewHeight; /** * 设置宽度 * 获取测量模式和测量的大小 */ int specMode = MeasureSpec.getMode(widthMeasureSpec); int specSize = MeasureSpec.getSize(widthMeasureSpec); if (specMode == MeasureSpec.EXACTLY)// match_parent , accurate { viewWidth = specSize; } else{ /** * 如果view直接赋值为250,则是250px,通过下面的代码,将sp转化为dip(dp). */ viewWidth=(int)(2*TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,250,getResources().getDisplayMetrics())); } /** * 设置高度 */ specMode = MeasureSpec.getMode(heightMeasureSpec); specSize = MeasureSpec.getSize(heightMeasureSpec); if (specMode == MeasureSpec.EXACTLY)// match_parent , accurate { viewHeight = specSize; Log.d("liang","viewHeight"+viewHeight); } else{ viewHeight=(int)(2*TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,250,getResources().getDisplayMetrics())); } setMeasuredDimension(viewWidth, viewHeight); }
onDraw()方法是绘制图形的时候调用的方法。我们需要绘制的元素有三个,圆弧(实现或者虚线),图片,分数。
在绘制过程中,需要实现颜色渐变的效果。我们借助Shader进行渲染。
mLinearGradient1 = new LinearGradient(0, 0, 0, 2*radius, new int[] { firstColor, secondColor, thirdColor }, null, Shader.TileMode.CLAMP);mPaint.setShader(mLinearGradient1);
数组前的四个参数确定渲染的范围,int数组里面的三个元素是渐变的颜色。最后一个参数是渲染模式,感兴趣的读者可以自行查询LinearGradient的用法,这里不做过多的表述。
整个onDraw()方法的代码段:
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPaint.setShader(mLinearGradient1); RectF rectF=new RectF(50,50,2*radius,2*radius); //Rect rect=new Rect(50,50,2*radius,2*radius); //canvas.drawRect(rect,mPaint); mPaint.setColor(Color.BLUE); canvas.drawArc(rectF,-90,progress,false,mPaint); Paint paint=new Paint(); paint.setColor(textColor); paint.setTextAlign(Paint.Align.CENTER); paint.setTextSize(textSize); paint.setSubpixelText(false); Paint.FontMetrics fontMetrics = paint.getFontMetrics(); float y = radius - fontMetrics.descent + (fontMetrics.bottom - fontMetrics.top) / 2; int margin=(int)(radius*0.2); int offest=(int)(radius*0.28); Rect rect1=new Rect(radius-margin,radius-margin-offest, radius+margin,radius+margin-offest); canvas.drawBitmap(icon,null,rect1,paint); canvas.drawText(grade2+"",radius,y+70,paint); }
2.进度和数字变化
为了实现进度和数字变化,这里开了两个线程。并且在每个线程里面用postInvalidate()更新画布。
为了实现这个功能,我首先记录了圆弧画完一圈所用的时间,然后用这一圈所花费的时间除以分数的最大值,所得的结果就是每次更新分数所需要的时间。
这么说太过抽象,还是看代码吧:
private int progress=0; /** * 绘制的速度 */ private int speed=20; /** * 进度或者分数 */ private int grade=60; private int grade2=0; new Thread(new Runnable() { @Override public void run() { while (true){ if(progress<360){ progress++; }else { break; } try { Thread.sleep(speed); } catch (InterruptedException e) { e.printStackTrace(); }finally { postInvalidate(); } } } }).start(); new Thread(new Runnable() { @Override public void run() { int count =360*speed; while (true){ if(grade2<grade){ grade2++; }else { break; } try { int spaceTime=count/grade; Thread.sleep(spaceTime); } catch (InterruptedException e) { e.printStackTrace(); }finally { postInvalidate(); } } } }).start();
注意,每次执行postInvalidate()方法,android都会调用onDraw()方法。
圆弧的绘制
- 简述
首先我们要知道,所谓圆弧就是椭圆的一部分。圆是一种特殊的椭圆。
在本例中,我们圆弧其实是一个360度的圆环。
绘制椭圆,我们需要确定它的外接矩形的大小和位置。所谓大小和位置的我确定,我们只需要确定就行左上角和右下角的坐标即可。这四个值就是我们绘制椭圆RectF的参数。
确定椭圆的大小位置:
RectF rectF=new RectF(50,50,2*radius,2*radius);
(50,50)是左上角的坐标,(2*radius,2*radius)是右下角的坐标。
有了椭圆,我们就可以绘制圆弧了。
canvas.drawArc(rectF,-90,progress,false,mPaint)
第一个参数rectF就是我们的椭圆,它确定了圆弧所在的位置。第二个参数-90表示开始绘制的角度。-90代表从最上边的顶点处开始绘制。第三个参数表示绘制多少度。在本例中,progress的值是定值360。即绘制整整一圈。
第四个参数确定圆弧的形状。如果是true,则和圆心相连接成为扇形。如果是false,那么就仅仅是弧形。感兴趣的读者可以在网上找资料看看,这里就不贴图举例啦。
代码
- 自定义的控件
public class MyCircleProgressBar extends View { private Paint mPaint; /** * 圆弧的宽度 */ private int circleWidth=40; private int progress=0; /** * 绘制的速度 */ private int speed=20; /** * 圆弧的半径 */ private int radius=250; /** * 线性着色器,使画笔在这三种色彩上形成渐变的效果 */ private Shader mLinearGradient1 = null; /** * 绘制过程中,三种渐变的色彩 */ private int firstColor=Color.RED; private int secondColor=Color.GREEN; private int thirdColor=Color.BLUE; /** * 进度或者分数 */ private int grade=60; private int grade2=0; /** * 控件中间的小图片 */ private Bitmap icon; /** * 字体的大小和颜色 */ private int textSize=150; private int textColor; /** * 是否为虚线 */ private boolean isDashedLine=true; public void setTextSize(int textSize) { this.textSize = textSize; } public void setCircleWidth(int circleWidth) { this.circleWidth = circleWidth; } public void setSpeed(int speed) { this.speed = speed; } public void setRadius(int radius) { this.radius = radius; } public void setFirstColor(int firstColor) { this.firstColor = firstColor; } public void setSecondColor(int secondColor) { this.secondColor = secondColor; } public void setThirdColor(int thirdColor) { this.thirdColor = thirdColor; } public void setGrade(int grade) { this.grade = grade; } public void setIcon(Bitmap icon) { this.icon = icon; } public void setDashedLine(boolean dashedLine) { isDashedLine = dashedLine; } public MyCircleProgressBar(Context context) { this(context, null); } public MyCircleProgressBar(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyCircleProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); getAttrs(context,attrs,defStyleAttr); mPaint=new Paint(); mPaint.setColor(getResources().getColor(R.color.colorAccent)); mPaint.setAntiAlias(true); mPaint.setStrokeWidth(circleWidth); mPaint.setStyle(Paint.Style.STROKE); if(isDashedLine){ PathEffect effect = new DashPathEffect(new float[] { 10, 10}, 1); mPaint.setPathEffect(effect); } mLinearGradient1 = new LinearGradient(0, 0, 0, 2*radius, new int[] { firstColor, secondColor, thirdColor }, null, Shader.TileMode.CLAMP); new Thread(new Runnable() { @Override public void run() { while (true){ if(progress<360){ progress++; }else { break; } try { Thread.sleep(speed); } catch (InterruptedException e) { e.printStackTrace(); }finally { postInvalidate(); } } } }).start(); new Thread(new Runnable() { @Override public void run() { int count =360*speed; while (true){ if(grade2<grade){ grade2++; }else { break; } try { int spaceTime=count/grade; Thread.sleep(spaceTime); } catch (InterruptedException e) { e.printStackTrace(); }finally { postInvalidate(); } } } }).start(); } /** * 如果测量模式不是EXACTLY(即精确测量),我们就将该View的长和宽都设置为250dp。 * * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int viewWidth,viewHeight; /** * 设置宽度 * 获取测量模式和测量的大小 */ int specMode = MeasureSpec.getMode(widthMeasureSpec); int specSize = MeasureSpec.getSize(widthMeasureSpec); if (specMode == MeasureSpec.EXACTLY)// match_parent , accurate { Log.e("xxx", "EXACTLY"); viewWidth = specSize; } else{ /** * 如果view直接赋值为250,则是250sp,通过下面的代码,将sp转化为dip(dp). */ viewWidth=(int)(2*TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,250,getResources().getDisplayMetrics())); } /** * 设置高度 */ specMode = MeasureSpec.getMode(heightMeasureSpec); specSize = MeasureSpec.getSize(heightMeasureSpec); if (specMode == MeasureSpec.EXACTLY)// match_parent , accurate { Log.e("xxx", "EXACTLY"); viewHeight = specSize; Log.d("liang","viewHeight"+viewHeight); } else{ viewHeight=(int)(2*TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,250,getResources().getDisplayMetrics())); } setMeasuredDimension(viewWidth, viewHeight); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPaint.setShader(mLinearGradient1); RectF rectF=new RectF(50,50,2*radius,2*radius); //Rect rect=new Rect(50,50,2*radius,2*radius); //canvas.drawRect(rect,mPaint); mPaint.setColor(Color.BLUE); canvas.drawArc(rectF,-90,progress,false,mPaint); Paint paint=new Paint(); paint.setColor(textColor); paint.setTextAlign(Paint.Align.CENTER); paint.setTextSize(textSize); paint.setSubpixelText(false); Paint.FontMetrics fontMetrics = paint.getFontMetrics(); float y = radius - fontMetrics.descent + (fontMetrics.bottom - fontMetrics.top) / 2; int margin=(int)(radius*0.2); int offest=(int)(radius*0.28); Rect rect1=new Rect(radius-margin,radius-margin-offest, radius+margin,radius+margin-offest); canvas.drawBitmap(icon,null,rect1,paint); canvas.drawText(grade2+"",radius,y+70,paint); } /** * 获取自定义属性 * @param context * @param attrs * @param defStyleAttr */ void getAttrs(Context context, AttributeSet attrs, int defStyleAttr){ TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomProgressBar, defStyleAttr, 0); int circleWidth1=(int)a.getDimension(R.styleable.CustomProgressBar_circleWidth,40); circleWidth=(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,circleWidth1, getResources().getDisplayMetrics()); speed=a.getInt(R.styleable.CustomProgressBar_speed,20); radius=(int)a.getDimension(R.styleable.CustomProgressBar_radius,250); firstColor=a.getColor(R.styleable.CustomProgressBar_firstColor,Color.RED); secondColor=a.getColor(R.styleable.CustomProgressBar_secondColor,Color.GREEN); thirdColor=a.getColor(R.styleable.CustomProgressBar_thirdColor,Color.BLUE); grade=a.getInt(R.styleable.CustomProgressBar_grade,20); Drawable drawable=a.getDrawable(R.styleable.CustomProgressBar_icons); if(null!=drawable){ BitmapDrawable bd = (BitmapDrawable)drawable; icon = bd.getBitmap(); }else{ icon= BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher); } textSize=(int)a.getDimension(R.styleable.CustomProgressBar_textSizes,150); textColor=a.getColor(R.styleable.CustomProgressBar_textColor,Color.RED);a.recycle(); }}
2.自定义的属性
<resources> <attr name="firstColor" format="color" /> <attr name="secondColor" format="color" /> <attr name="thirdColor" format="color" /> <attr name="textColor" format="color" /> <attr name="circleWidth" format="dimension" /> <attr name="speed" format="integer" /> <attr name="radius" format="dimension" /> <attr name="grade" format="integer" /> <attr name="icons" format="reference|color" /> <attr name="textSizes" format="dimension" /> <declare-styleable name="CustomProgressBar"> <attr name="firstColor" /> <attr name="secondColor" /> <attr name="thirdColor" /> <attr name="circleWidth" /> <attr name="speed" /> <attr name="radius" /> <attr name="grade" /> <attr name="icons" /> <attr name="textSizes" /> <attr name="textColor" /> </declare-styleable></resources>
3.布局文件
<com.zhimei.customcircleprogressbar.widget.MyCircleProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" custom:circleWidth="10dp" custom:speed="20" custom:firstColor="@color/colorAccent" custom:icons="@mipmap/ic_launcher" custom:grade="46" custom:textSizes="100sp" custom:radius="150dp" custom:textColor="@color/colorPrimaryDark" />
源码下载
点击下载
- 自定义CircleProgressBar
- 自定义view CircleProgressBar
- 104_自定义CircleProgressBar
- 自定义圆形 进度条 circleProgressbar
- CircleProgressBar
- Android自定义控件实现圆形进度CircleProgressBar
- Android自定义控件实现圆形进度CircleProgressBar
- QT自定义控件:CircleProgressBar环形进度条
- 自定义的CircleProgressBar,支持自定义宽度,颜色等等。
- CircleProgressBar 两个进度值的 圆环进度条,原谅我是个小白,只能写一写简单的自定义控件
- 超屌CircleProgressBar
- Android 画图详解 圆形进度条CircleProgressBar
- Android CircleProgressBar好看的圆形进度条
- CircleProgressBar For Android(圆形进度条)
- CircleProgressBar 带载入动画的环形进度条
- android 圆形渐变进度条CircleProgressBar的开源框架
- Android动画篇(一):圆形进度条CircleProgressBar
- CircleProgressBar 一个界面漂亮功能强大的圆形进度条,支持多种属性
- filesystem-e2fsprog
- 并查集(删除节点,就是增加一个新的爹)
- poj2376
- 11g R2 节点系统重建后,删除节点及添加节点 过程和问题解决
- PopupWindow简单使用
- 自定义CircleProgressBar
- Tyvj P2002 扑克牌
- (一)STM32固件库详解(转载)
- WiFi、ZigBee、BLE用哪个?小米内部是这样选的
- Kotlin实现配置化网络请求
- Java代码优化之字符串String学习
- POJ 2663 Tri Tiling
- (4)配置路由器的路由表(静态)
- iOS面试题三