Android自定义View实现环形进度条的思路与实例
前言
前段时间看到了豆瓣fm的音乐播放界面,有一个环形的进度条,非常的好看,于是想了想,为什么不自己做一个呢,于是就开始了自定义的过程
豆瓣fm的播放界面如下图:
功能分析
虽然功能比较简单,但是仍然需要仔细分析
1.图标外还有一圈圆圈,可以设置宽度
2.圆形进度条和进度条底部,可以设置宽度,颜色等
3.内部有一个圆形图片,可旋转
实现思路分析
1.可以设置宽度的圆圈
这个比较容易,直接在ondraw方法中使用canvas绘制即可,当然,在间距和半径的处理上需要仔细,控件本体其实还是一个长方形,我们需要选取较短的那一边作为直径,同时也要处理内部的padding
2.圆形进度条和进度条底部,可以设置宽度,颜色等
这个可以用canvas的drawarc方法来实现,通过绘制不同长度的弧形来达到显示进度的目的,但是需要注意的是,我们需要计算好弧形的半径以及开始和结束点。
3.内部有一个圆形图片,可旋转
这个需求可以分为三个部分,有图片,圆形,可以旋转
先说有图,很简单,canvas的drawbitmap方法绘制(canvas真是好东西)
再说圆形,这就比较复杂了,但是整体来说依然是使用canvas来对bitmap进行操作,会在代码中细说
最后是可以旋转,我们可以通过canvas的rotate方法来做。
效果展示
说了这么多,那么最后的效果是怎样的呢?毕竟空口无凭,在进入代码展示的环节之前还是看看最后的效果吧。
这是我自己做的一个定时锁屏的项目,地址是这里是地址或者本地下载
这是这个项目运行锁屏的时候的动图(大家都喜欢动图)
代码实现
下面开始展示代码,并加以分析
我们主要的工作是在一个自定义的view中的ondraw方法实现的,所以,我们需要有一个继承view类的子类,我们就叫他myprogress吧
我们展示的就是这个myprogress的ondraw方法
1.可以设置宽度的圆圈
很简单,我们只需要调用canvas的drawcircle方法即可,但是需要注意对padding的处理,因为不处理就会无效
super.ondraw(canvas); //需要在函数开始的地方调用父类的ondraw final int paddingleft = getpaddingleft(); final int paddingright = getpaddingright(); final int paddingtop = getpaddingtop(); final int paddingbottom = getpaddingbottom(); //获取padding //get the view's width and height and decide the radiu int width = getwidth() - paddingleft - paddingright; int height = getheight() - paddingtop - paddingbottom; radiu = math.min(width , height) / 2 - boundwidth - progresswidth; //计算半径,选取长宽中短的那个做处理,boundwidth是圆圈的宽度,progresswidth是进度条的宽度 //setup the paint paint.setstyle(paint.style.stroke); //设置paint为画轮廓 paint.setstrokewidth(boundwidth); //设置宽度 paint.setcolor(color.black); //设置颜色 //draw the inner circle int centerx = paddingleft + getwidth()/2; int centery = paddingtop + getheight() / 2; //计算圆的中心点 canvas.drawcircle(centerx,centery, radiu, paint); //绘制圆形
2.圆形进度条和进度条底部,可以设置宽度,颜色等
这里需要注意的就是开始的角度和结束的角度了,为了达到进度条目的,所以我们需要随着业务状态的改变来改变这个值
//set paint for arc paint.setstrokewidth(progresswidth); paint.setstrokecap(paint.cap.round);//设置进度宽度,设置末端是一个圆弧 //prepare for draw arc rectf oval = new rectf(); oval.left = centerx -totalradiu ; oval.top =centery- totalradiu ; oval.right = centerx + totalradiu; oval.bottom = centery+ totalradiu; //新建一个椭圆,设置其四个点的坐标 paint.setcolor(progressbackcolor);//设置进度条背景的颜色 //draw background arc canvas.drawarc(oval, arcstar, arcend, false, paint); //绘制底部的一个圆弧,作为背景 //draw progress arc paint.setcolor(progresscolor);//设置进度条的颜色 canvas.drawarc(oval, arcstar, progress, false, paint);//绘制进度条
3.内部有一个圆形图片,可旋转
这一段比较复杂,直接用代码解释
float totalradiu = radiu +boundwidth +progresswidth/2;//设置外径 //draw the circlr pic if (drawable != null&&bitmap == null) { image = ((bitmapdrawable) drawable).getbitmap();//获取设置的bitmap资源 bitmap = bitmap.createbitmap((int)(2*totalradiu),(int)(2*totalradiu), bitmap.config.argb_8888); canvas bitmapcanvas = new canvas(bitmap);//新建一个bitmap并新建一个canvas用以操作 paint bitmappaint = new paint(); bitmappaint.setantialias(true);//新建一个paint并设置反锯齿 bitmapcanvas.drawcircle(totalradiu, totalradiu, radiu, bitmappaint);//画一个圆 bitmappaint.setxfermode(new porterduffxfermode(porterduff.mode.src_in));//关键代码,设置为交集模式,会让后面的内容和已有内容取交集 bitmapcanvas.drawbitmap(image,null,new rectf(0,0,2*totalradiu,2*totalradiu) , bitmappaint);//绘制自己的图片到现有画布上 } rect rect = new rect((int)(centerx -totalradiu),(int)(centery-totalradiu),(int)(centerx+totalradiu),(int)(centery+ totalradiu));//新建一个rect,设定边界点 canvas.save(); if(isrotate) canvas.rotate(rotatedegree,centerx,centery);//设置旋转,为了实现图片转动效果,rotatedegree为旋转角度 canvas.drawbitmap(bitmap,null ,rect, paint);//绘制处理过的图片
有了上面这些代码,我们自定义view的主体部分就完成了,当然还有一些辅助的部分,比如更新进度和选择角度的函数,设置一些颜色和宽度之类的参数等
完整代码
myprogress
public class myprogressbar extends view { float progress = 360; float arcstar = 270; float arcend = 360; double rotatestep = 0.2; bitmap bitmap; int totaltime; bitmap image; drawable drawable; int boundwidth = 5; private int progresswidth = 30; private boolean isrotate = false; private int progresscolor = color.green; private int progressbackcolor = color.green; private float rotatedegree = 0; public myprogressbar(context context) { super(context); } public myprogressbar(context context, attributeset attrs) { super(context, attrs); } public myprogressbar(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); } private float radiu; private paint paint = new paint(paint.anti_alias_flag); public void setradiu(float radiu) { this.radiu = radiu; invalidate(); } //start 函数使用 countdowntimer类来更新progress和旋转角度 public void start(long time) { bitmap = null; time *= 60000; final float step = (float) 360 / (time / 30); countdowntimer mtimer = new countdowntimer(time, 30) { public void ontick(long millisuntilfinished) { progress -= step; rotatedegree -= rotatestep; invalidate(); } @override public void onfinish() { end(step); } }; mtimer.start(); } private void end(float step) { progress -= step; invalidate(); progress = 0; rotatedegree = 0; invalidate(); } public void setboundwidth(int width) { boundwidth = width; } public void setprogresswidth(int width) { progresswidth = width; } public void setprogresscolor(int color) { progresscolor = color; } public void setprogressbackcolor(int color) { progressbackcolor = color; } public void setdrawable(drawable drawable) { this.drawable = drawable; invalidate(); } public void setisrote(boolean rotate) { this.isrotate = rotate; invalidate(); } @override protected void ondraw(canvas canvas) { super.ondraw(canvas); final int paddingleft = getpaddingleft(); final int paddingright = getpaddingright(); final int paddingtop = getpaddingtop(); final int paddingbottom = getpaddingbottom(); //get the view's width and height and decide the radiu int width = getwidth() - paddingleft - paddingright; int height = getheight() - paddingtop - paddingbottom; radiu = math.min(width , height) / 2 - boundwidth - progresswidth; //setup the paint paint.setstyle(paint.style.stroke); paint.setstrokewidth(boundwidth); paint.setcolor(color.black); //draw the inner circle int centerx = paddingleft + getwidth()/2; int centery = paddingtop + getheight() / 2; canvas.drawcircle(centerx,centery, radiu, paint); float totalradiu = radiu +boundwidth +progresswidth/2; //draw the circlr pic if (drawable != null&&bitmap == null) { image = ((bitmapdrawable) drawable).getbitmap(); bitmap = bitmap.createbitmap((int)(2*totalradiu),(int)(2*totalradiu), bitmap.config.argb_8888); canvas bitmapcanvas = new canvas(bitmap); paint bitmappaint = new paint(); bitmappaint.setantialias(true); bitmapcanvas.drawcircle(totalradiu, totalradiu, radiu, bitmappaint); bitmappaint.setxfermode(new porterduffxfermode(porterduff.mode.src_in)); bitmapcanvas.drawbitmap(image,null,new rectf(0,0,2*totalradiu,2*totalradiu) , bitmappaint); } rect rect = new rect((int)(centerx -totalradiu),(int)(centery-totalradiu),(int)(centerx+totalradiu),(int)(centery+ totalradiu)); canvas.save(); if(isrotate) canvas.rotate(rotatedegree,centerx,centery); canvas.drawbitmap(bitmap,null ,rect, paint); canvas.restore(); //set paint for arc paint.setstrokewidth(progresswidth); paint.setstrokecap(paint.cap.round); //prepare for draw arc rectf oval = new rectf(); oval.left = centerx -totalradiu ; oval.top =centery- totalradiu ; oval.right = centerx + totalradiu; oval.bottom = centery+ totalradiu; paint.setcolor(progressbackcolor); //draw background arc canvas.drawarc(oval, arcstar, arcend, false, paint); //draw progress arc paint.setcolor(progresscolor); canvas.drawarc(oval, arcstar, progress, false, paint); } }
完整的工程,包括对这个自定义view的应用例子可以参考我在github上的工程地址在这里,也可以本地下载
总结
这个看似简单的自定义view的制作当中还是遇到了不少值得思考的问题,这也是为什么有这篇文章的原因
1.在处理圆形剪裁图片的时候,要注意剪裁的canvas所用的坐标是相对于处理图片的,而不是整体坐标
2.在绘制时,应该尽量减少重复的处理,比如圆形图片剪裁,一次就够了,如果次数过多,每次更新进度的时候就会去进行一次,导致整个view比较卡,进度不准确
3.对于自定义view中几个关键点的坐标,应该用一个比较简单易懂的表达式表示,否则做到后期会搞混淆,而陷入坐标的泥潭之中
4.某些看起来很厉害的效果只要合理分析,分步实现,并不会很难
好了,以上就是这篇文章的全部内容了,希望本文的内容对各位android开发者们能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持。
上一篇: Android 消息队列模型详解及实例
推荐阅读
-
Android自定义View实现环形进度条的思路与实例
-
android自定义进度条渐变色View的实例代码
-
Android自定义View实现等级滑动条的实例
-
Android自定义View实现环形进度条的思路与实例
-
Android自定义控件实现带文本与数字的圆形进度条
-
Android自定义View实现简单炫酷的球体进度球实例代码
-
Android如何自定义View实现横向的双水波纹进度条
-
android自定义进度条渐变色View的实例代码
-
Android自定义View实现等级滑动条的实例
-
Android自定义控件:图形报表的实现(折线图、曲线图、动态曲线图)(View与SurfaceView分别实现图表控件)