drawCircle(Offset c, double radius, Paint paint) // 绘制线条 drawLine(Offset p1, Offset p2, Paint paint) // 绘制椭圆 drawOval(Rect rect, Paint paint) // 绘制文字 drawParagraph(Paragraph paragraph, Offset offset) // 绘制路径 drawPath(Path path, Paint paint) // 绘制点 drawPoints(PointMode pointMode, List points, Paint paint) // 绘制Rect drawRect(Rect rect, Paint paint) // 绘制阴影 drawShadow(Path path, Color color, double elevation, bool transparentOccluder)

一些常用的Paint属性:

color:画笔颜色 style:绘制模式,画线 or 充满 maskFilter:绘制完成,还没有被混合到布局上时,添加的遮罩效果,比如blur效果 strokeWidth:线条宽度 strokeCap:线条结束时的绘制样式 shader:着色器,一般用来绘制渐变效果或ImageShader

绘制步骤分析

首先是静态进度条的绘制,我们先拆解这个CircleProgressBar为三部分:底部圆环、进度条和显示当前进度的小圆点。因为Canvas的绘制顺序是按代码顺序一层一层往上叠加的,所以我们的绘制步骤应该是:绘制底部圆环——>绘制进度条——>绘制小圆点。 然后是手势拖动的实现,我们选用GestureDetector来实现就可以了,在onPanUpdate回调中实时刷新进度条与小圆点的位置,这里面需要注意的地方是可触摸区域的计算。

静态CircleProgressBar绘制

绘制所需要的变量基本都标注在上图中了,圆心坐标就是整块画布的中心点,我们定义为(center,center),其中center = size.width * 0.5。小圆点的半径定义为dotRadius。灰色实线部分为底部圆环,progressBar的宽度为红色虚线部分所示,其大小应该比底部圆环略大,至于大多少,你可以自己定义。在本次的例子中,我将灰色实线与红色虚线之间的部定义为radiusOffset = dotRadius * 0.4,这个值尽量不要写死,那么radiusOffset*2就是progressBar宽度比底部圆环大的值。innerRadius和outRadius分别为底部圆环的内/外半径,大小如图上所示(纯数学知识,不解释)。然后我们可以根据innerRadius和outRadius计算出progressBar宽度progressWith = outerRadius - innerRadius + radiusOffset。drawRadius是一个大小为画布宽度的一半减去小圆点半径的变量,这个变量在绘制progressBar和小圆点的时候很有用,用来确定progressBar和小圆点的位置。

Step 1 底部圆环绘制

底部圆环的绘制非常简单,实际上就是画一个圆。为什么说画圆环和画圆会是一样的呢?Paint是画笔,回想一下我们在写字的时候,写出来的字是不是有粗有细?同样地,Paint在画线的时候也是有宽度的,我们画一个有宽度的圆,不就是画一个圆环了吗?

final Offset offsetCenter = Offset(center, center); final ringPaint = Paint() …style = PaintingStyle.stroke …color = ringColor …strokeWidth = (outerRadius - innerRadius); canvas.drawCircle(offsetCenter, drawRadius, ringPaint);

canvas.drawCircle(Offset c, double radius, Paint paint)这个方法就是绘制一个圆,其中c为圆心坐标点,这个offset偏移值是以画布原点(左上角)为坐标轴中心点来计算的,很明显大小为offsetCenter = Offset(center, center);radius为圆环半径,大小其实就是图上标示的drawRadius;paint就是我们的画笔,这里要注意,绘制圆环需要设置style = PaintingStyle.stroke,否则画笔会默认充满内部,那么你绘制出来的就是一个圆了。

Step 2 底部进度条

绘制进度条实际上就是绘制圆弧,我们使用canvas.drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint)。 rect参数就是圆弧所在的整圆的Rect,我们使用Rect.fromCircle来构造这个整圆的Rect:final Rect arcRect = Rect.fromCircle(center: offsetCenter, radius: drawRadius);;startAngle为起始弧度,sweepAngle为需要绘制的圆弧长度,这里要注意,这两个值都是 弧度制 的,canvas里面与角度有关的变量都是弧度制的,在计算的时候一定要注意;useCenter属性标示是否需要将圆弧与圆心相连;paint就是我们的画笔。 补充:弧度与角度的弧线转换:

num degToRad(num deg) => deg * (pi / 180.0); num radToDeg(num rad) => rad * (180.0 / pi);

final angle = 360.0 * progress; final double radians = degToRad(angle); final Rect arcRect = Rect.fromCircle(center: offsetCenter, radius: drawRadius); final progressPaint = Paint() …style = PaintingStyle.stroke …strokeWidth = progressWidth; canvas.drawArc(arcRect, 0.0, degToRad(angle), false, progressPaint);

假设当前进度为progress(范围为0.0~1.0),那么当前角度为angle = 360.0 * progress,当前弧度为radians = degToRad(angle),上述代码可以绘制出一个基础的圆弧。但是我们会发现,圆弧的两端是平的,很影响美观,这时候就需要用到paint的strokeCap属性了。

我们将paint设置为StrokeCap.round,就能得到一个最基本的进度条了。

接下来我们给进度条添加颜色,按照设计稿,我们需要添加一个渐变色。渐变色可以通过paint的shader属性来实现:

final Gradient gradient = new SweepGradient( endAngle: radians, colors: [ Colors.white, currentDotColor, ], ); final progressPaint = Paint() …style = PaintingStyle.stroke …strokeCap = StrokeCap.round …strokeWidth = progressWidth …shader = gradient.createShader(arcRect);

Flutter提供了三种基础的用来绘制渐变效果的类:SweepGradient(扫描渐变)、LinearGradient(线性渐变)和RadialGradient(径向渐变)。

很明显,我们需要用到的是SweepGradient:

final Gradient gradient = new SweepGradient( endAngle: radians, colors: [ Colors.white, currentDotColor, ], );

注意,这里有一个很大的坑,我们可以从上面的SweepGradient事例图上看到,默认情况下是从90°的地方作为起点的,这跟我们的要求明显是不符的。SweepGradient有一个startAngle属性,那么我们是否可以将其设置为degToRad(-90°)就可以解决问题了呢?答案是:不可以。这里怀疑是Flutter的一个bug,startAngle属性不生效,我们可以看一下这个issue:SweepGradient startAngle doesn’t work as expected.

那么怎么解决呢?我想了很久之后决定采用一个曲线救国的方法,那就是:旋转画布!!。反正是一个圆弧嘛,那我把画布逆时针旋转90°不就行了嘛(这里还要注意,画布默认旋转中心为坐标轴原点,而且貌似不能更改,至少我没找到,所以需要旋转后再平移,对canvas的位置操作需要倒着写,所以实际代码是先写translate,再写rotate):

canvas.save(); canvas.translate(0.0, size.width); canvas.rotate(degToRad(-90.0)); ······ canvas.drawArc(arcRect, 0.0, degToRad(angle), false, paint); canvas.restore();

画到这里你是不是觉得已经很OK了呢?运行一下,啊嘞,怎么会这样纸?

这是我们给stroke设置了StrokeCap.round导致的,因为Flutter在给线绘制圆角时,是在线长的外面加了一段圆角,导致实际长度会超过我们定义的长度。那怎么办呢?还是曲线救国,我们在drawArc的时候,将起始角度往后偏移一段不就可以了吗?我们将这段偏移弧度定义为offset,其大小为offset = asin(progressWidth * 0.5 / drawRadius)(怎么算出来的?数学问题,自己那张草稿纸画画就知道啦~)。 所以最终的绘制代码应该为:

canvas.drawArc(arcRect, offset, degToRad(angle) - offset, false, progressPaint);

那么到此为止,我们的进度条部分也绘制完成了。

Step 3 绘制小圆点

绘制小圆点就比较简单了,只要计算出小圆点的圆心位置就可以了,纯初中数学计算,自己拿纸画画就知道啦。绘制函数依然是canvas.drawCircle,因为是绘制圆,所以不需要更改PaintingStyle。

final double dx = center + drawRadius * sin(radians); final double dy = center - drawRadius * cos(radians); final dotPaint = Paint()…color = currentDotColor; canvas.drawCircle(new Offset(dx, dy), dotRadius, dotPaint); dotPaint …color = dotEdgeColor …style = PaintingStyle.stroke …strokeWidth = dotRadius * 0.3; canvas.drawCircle(new Offset(dx, dy), dotRadius, dotPaint);

Step 4 细节修饰:绘制底部圆环阴影和小圆点外圈

绘制圆环阴影

绘制阴影有两种方法,实现出来的效果也不太一样。 1)使用canvas.drawShadow()来绘制: drawShadow(Path path, Color color, double elevation, bool transparentOccluder),根据API要求,我们需要先计算出圆环的Path,Path的相关API只支持向path中添加圆、弧线、直线、点等属性,我们没法直接构建一个圆环对应的对象Path。换个角度思考一下,圆环的Path其实是外层圆与内层圆组合的结果,所以我们使用Path.combine()方法来获得圆环的路径,通过设置组合模式为PathOperation.difference可以获取内外两个圆的公共部分的Path,也就是圆环的Path:

Path path = Path.combine(PathOperation.difference, Path()…addOval(Rect.fromCircle(center: offsetCenter, radius: outerRadius)), Path()…addOval(Rect.fromCircle(center: offsetCenter, radius: innerRadius))); canvas.drawShadow(path, shadowColor, 4.0, true);

2)使用paint的MaskFilter.blur()来绘制: 这个方法其实是用来绘制毛玻璃效果的,用来绘制阴影,听起来也有些曲线救国的意味,但是官方注释中有一句话:

Creates a mask filter that takes the shape being drawn and blurs it. This is commonly used to approximate shadows.

所以这个真的也是可以用来绘制阴影的,而且Flutter在绘制一些Button控件的时候也是使用来blur的效果来实现的。MaskFilter.blur()其实就是将你绘制的东西变模糊,所以我们可以绘制一个圆环,然后将其进行高斯模糊,造成一种加了“阴影”的假象。

final shadowPaint = Paint() …style = PaintingStyle.stroke …color = shadowColor …strokeWidth = shadowWidth …maskFilter = MaskFilter.blur(BlurStyle.normal, shadowWidth); canvas.drawCircle(offsetCenter, outerRadius, shadowPaint); canvas.drawCircle(offsetCenter, innerRadius, shadowPaint);

两者绘制结果的区别很明显,canvas.drawShadow()是将整个圆环作为一个整体,为其添加阴影;而MaskFilter.blur()其实就是绘制两个模糊的圆环,作为一种阴影的替代品。使用哪种方式绘制,还是取决于你需要什么样的效果。

小圆点外圈绘制

这个没什么难度的,就是在小圆点外面再绘制一个圆环而已:

dotPaint …color = dotEdgeColor …style = PaintingStyle.stroke …strokeWidth = dotRadius * 0.3; canvas.drawCircle(new Offset(dx, dy), dotRadius, dotPaint);

到此为止,一个静态的CircleProgressBar就绘制完成了:

添加手势控制

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【算法合集】

【延伸Android必备知识点】

【Android部分高级架构视频学习资源】

**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

精彩文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: