網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
簡(jiǎn)介
上一篇我們介紹了使用 Flutter 的 Canvas 繪制基本圖形的示例,簡(jiǎn)單的示例沒(méi)什么好玩的,今天這一篇我們來(lái)點(diǎn)有趣的,我們會(huì)完成如下圖形的繪制:
- 發(fā)現(xiàn)數(shù)學(xué)重復(fù)之美:使用等邊三角形組合成彩虹傘面。
- 繪制彩虹。
- 繪制評(píng)分用的五角星。
通過(guò)這一篇,我們可以知道自定義形狀繪制的基本原理,然后可以在這個(gè)基礎(chǔ)上繪制你自己想要繪制的圖形。
等邊三角形構(gòu)建重復(fù)之美
首先我們來(lái)繪制等邊三角形,其實(shí)上一篇我們也有繪制等邊三角形,只是那是將三個(gè)頂點(diǎn)手動(dòng)計(jì)算出來(lái)的,這一篇我們封裝一個(gè)繪制等邊三角形的通用方法。老規(guī)矩,先定義方法的輸入?yún)?shù),如下所示:
-
canvas
:Canvas
畫(huà)布 -
color
:繪制顏色 -
startVertex
:三角形的第一個(gè)頂點(diǎn)位置,這里我們其他邊都是相對(duì)這個(gè)點(diǎn)旋轉(zhuǎn)的 -
length
:邊長(zhǎng) -
startAngle
:第一條邊相對(duì)水平方向旋轉(zhuǎn)的夾角,這樣我們可以改變夾角來(lái)更改三角形的繪制位置。 -
clockwise
:順時(shí)針繪制,如果是順時(shí)針,則繪制的偏移夾角往順時(shí)針?lè)较蜷_(kāi)始,否則逆時(shí)針。 -
filled
:是否填充圖形。
void drawEquilateralTriangle( Canvas canvas, { required Color color, required Offset startVertex, required double length, double startAngle = 0, clockwise = true, filled = true, })
等邊三角形基于一個(gè)頂點(diǎn),一條邊和起始角度后就可以計(jì)算其他兩個(gè)頂點(diǎn)的位置,具體推到通過(guò)三角函數(shù)就可以了。
具體計(jì)算三角形的三個(gè)頂點(diǎn)的方法如下,這里逆時(shí)針?lè)较蚝晚槙r(shí)針?lè)较虻挠?jì)算方式有點(diǎn)不同,需要區(qū)分一下。
static ListgetEquilateralTriangleVertexes( Offset startVertex, double length, {double startAngle = 0, bool clockwise = true}) { double point2X, point2Y, point3X, point3Y; point2X = startVertex.dx + length * cos(startAngle); point2Y = startVertex.dy - length * sin(startAngle); if (clockwise) { point3X = startVertex.dx + length * cos(pi / 3 + startAngle); point3Y = startVertex.dy - length * sin(pi / 3 + startAngle); } else { point3X = startVertex.dx + length * cos(pi / 3 - startAngle); point3Y = startVertex.dy + length * sin(pi / 3 - startAngle); } return [startVertex, Offset(point2X, point2Y), Offset(point3X, point3Y)]; }
有了頂點(diǎn)我們就可以使用 Path 將頂點(diǎn)連起來(lái)就完成等邊三角形的繪制了,繪制三角形的實(shí)現(xiàn)方法如下:
void drawEquilateralTriangle( Canvas canvas, { required Color color, required Offset startVertex, required double length, double startAngle = 0, clockwise = true, filled = true, }) { assert(length > 0); Path trianglePath = Path(); Listvertexes = ShapesUtil.getEquilateralTriangleVertexes( startVertex, length, clockwise: clockwise, startAngle: startAngle, ); trianglePath.moveTo(vertexes[0].dx, vertexes[0].dy); for (int i = 1; i < vertexes.length; i++) { trianglePath.lineTo(vertexes[i].dx, vertexes[i].dy); } trianglePath.close(); Paint paint = Paint(); paint.color = color; if (!filled) { paint.style = PaintingStyle.stroke; } canvas.drawPath(trianglePath, paint); } }
單獨(dú)一個(gè)三角形沒(méi)啥意思,我們通過(guò)畫(huà)6個(gè)等邊三角形,每個(gè)三角形旋轉(zhuǎn)60度,空心繪制看看怎么樣?
一個(gè) 完美的六邊形出來(lái)了,再試試12個(gè)怎么樣。
形狀越多,會(huì)越接近圓形,你會(huì)充分發(fā)現(xiàn)對(duì)稱(chēng)之美。下面是我們用24個(gè)三角形,填充不同顏色后的效果。有點(diǎn)像一把彩虹傘的傘面了,感覺(jué)是不是很美?
上面圖形的實(shí)現(xiàn)代碼如下,其中顏色是通過(guò)一個(gè)顏色數(shù)組完成的。
int number = 24; for (int i = 0; i < number; ++i) { drawEquilateralTriangle( canvas, color: colors[i], startVertex: Offset(center.width, center.height), length: 120, startAngle: i * 2 * pi / number, clockwise: true, filled: true, ); }
繪制彩虹
有了上面的彩虹傘一樣的啟發(fā),我們決定來(lái)繪制彩虹。彩虹其實(shí)比較簡(jiǎn)單,繪制7條不同顏色的弧線(xiàn)即可。這里講一下弧線(xiàn)的繪制約束。如下圖所示,實(shí)際上弧線(xiàn)是通過(guò)矩形的內(nèi)接橢圓限制的(這里用正方形,內(nèi)接為圓形示例)。外面的矩形限制了橢圓位置和尺寸,而通過(guò) startAngle
(起始角度)和 sweepAngle
(弧線(xiàn)覆蓋的角度范圍)就能夠確定弧線(xiàn)的起點(diǎn)和終點(diǎn),從而得到一段弧線(xiàn)。注意的是,數(shù)學(xué)里我們是逆時(shí)針角度為正,但是在 Flutter 默認(rèn)是順時(shí)針為正,因此如果你要從逆時(shí)針?lè)较蜷_(kāi)始角度就要設(shè)置為負(fù)數(shù)。
下面是弧線(xiàn)繪制的示例代碼:
Path path1 = Path(); Rect rect1 = Rect.fromLTWH(startPoint.dx + (width - innerWidth) / 2, startPoint.dy + (width - innerWidth) / 2, innerWidth, innerWidth); path1.arcTo(rect1, -pi / 6, -2 * pi / 3, true); paint.color = colors[i]; canvas.drawPath(path1, paint);
有了這個(gè)基礎(chǔ),我們通過(guò)循環(huán) ,繪制7條弧線(xiàn),保證每條弧線(xiàn)挨著就行。而弧線(xiàn)的線(xiàn)條粗細(xì)可以用畫(huà)筆的寬度來(lái)搞定,代碼如下。我們這里每條弧線(xiàn)的中心、起始角度和覆蓋角度是一樣的,通過(guò)改變不同弧線(xiàn)的正方形邊長(zhǎng)實(shí)現(xiàn)彩虹弧線(xiàn)的位置不同,然后畫(huà)筆粗細(xì)保持為每條彩虹的高度的一半就可以保證每條彩虹是挨著的了。
void drawRainbow( Canvas canvas, { required Offset startPoint, required double width, }) { assert(width > 0); var paint = Paint(); double rowHeight = 12; paint.strokeWidth = rowHeight / 2; Listcolors = [ Color(0xFFE05100), Color(0xFFF0A060), Color(0xFFE0E000), Color(0xFF10F020), Color(0xFF2080F5), Color(0xFF104FF0), Color(0xFFA040E5), ]; paint.style = PaintingStyle.stroke; for (var i = 0; i < 7; i++) { double innerWidth = width - i * rowHeight; Path path1 = Path(); Rect rect1 = Rect.fromLTWH(startPoint.dx + (width - innerWidth) / 2, startPoint.dy + (width - innerWidth) / 2, innerWidth, innerWidth); path1.arcTo(rect1, -pi / 6, -2 * pi / 3, true); paint.color = colors[i]; canvas.drawPath(path1, paint); } }
最終效果如下圖所示。
繪制五角星
五角星相對(duì)來(lái)說(shuō)會(huì)復(fù)雜一些,主要是要知道通過(guò)中心點(diǎn)確定10個(gè)頂點(diǎn)的坐標(biāo),這里就需要利用二維坐標(biāo)的旋轉(zhuǎn)公式了,具體可以查閱相關(guān)資料,結(jié)論是一個(gè)點(diǎn)(x2, y2)圍繞另一個(gè)點(diǎn)(x1, y1)旋轉(zhuǎn)某個(gè)角度(α)后得到的新坐標(biāo)(x, y)計(jì)算方式如下:
x=x1+(x2-x1)*cos(α)-(y2-y1)*sin(α)
y=y1+(y2-y1)*cos(α)+(x2-x1)*sin(α)
有了這個(gè)基礎(chǔ),我們就可以基于五角星的中心點(diǎn),第一個(gè)頂點(diǎn),邊長(zhǎng)(間隔一個(gè)點(diǎn)連線(xiàn)的線(xiàn)段長(zhǎng)度)來(lái)通過(guò)旋轉(zhuǎn)計(jì)算其他頂點(diǎn)了。其中外面5頂點(diǎn)一組計(jì)算,內(nèi)部5個(gè)頂點(diǎn)一組計(jì)算。最終獲取5個(gè)頂點(diǎn)的代碼如下:
static ListgetStarVertexes(Offset center, double length) { assert(length > 0); // 外接圓半徑計(jì)算(五角星銳角為36度) double radius = length / 2 / cos(18 / 180 * pi); // 內(nèi)部頂點(diǎn)的半徑 double innerRadius = radius / (cos(36 / 180 * pi) + sin(36 / 180 * pi) / sin(18 / 180 * pi)); List vertexes = []; Offset outerStartVertex = Offset(center.dx, center.dy - radius); Offset innerStartVertex = Offset( center.dx - innerRadius * sin(36 / 180 * pi), center.dy - innerRadius * cos(36 / 180 * pi), ); vertexes.add(outerStartVertex); vertexes.add(innerStartVertex); // 計(jì)算方式為以第一個(gè)頂點(diǎn)圍繞五角星中心點(diǎn)坐標(biāo)旋轉(zhuǎn)得到 const double rotateAngle = 72 / 180 * pi; for (int i = 1; i < 5; ++i) { vertexes.add(Offset( center.dx + (outerStartVertex.dx - center.dx) * cos(-i * rotateAngle) - (outerStartVertex.dy - center.dy) * sin(-i * rotateAngle), center.dy + (outerStartVertex.dy - center.dy) * cos(-i * rotateAngle) + (outerStartVertex.dx - center.dx) * sin(-i * rotateAngle), )); vertexes.add(Offset( center.dx + (innerStartVertex.dx - center.dx) * cos(-i * rotateAngle) - (innerStartVertex.dy - center.dy) * sin(-i * rotateAngle), center.dy + (innerStartVertex.dy - center.dy) * cos(-i * rotateAngle) + (innerStartVertex.dx - center.dx) * sin(-i * rotateAngle), )); } return vertexes; }
有了頂點(diǎn),繪制方式就和三角形一樣了,將頂點(diǎn)連起來(lái)就好了。下面是我們繪制了一個(gè)常見(jiàn)的五星評(píng)分的圖形。
總結(jié)
本篇介紹了基于 Flutter 的 CustomPaint
繪制定制化圖形的示例,可以看到,其實(shí)只要 UI 小姐姐給出的圖形能夠用數(shù)學(xué)表達(dá)式表示出來(lái),都可以用 CustomPaint
的 Canvas
來(lái)實(shí)現(xiàn)。
原文鏈接:https://juejin.cn/post/7077531241863446541
相關(guān)推薦
- 2024-03-28 SpringBoot項(xiàng)目中的500錯(cuò)誤
- 2022-08-02 使用Dockerfile實(shí)現(xiàn)容器內(nèi)部服務(wù)隨容器自啟動(dòng)的方法_docker
- 2022-10-07 Android開(kāi)發(fā)Jetpack組件Lifecycle原理篇_Android
- 2022-05-16 Qt數(shù)據(jù)庫(kù)應(yīng)用之通用數(shù)據(jù)庫(kù)同步_C 語(yǔ)言
- 2022-11-02 python?pip特殊用法之pip?install?-v?-e?.命令詳解_python
- 2022-07-08 python使用IPython調(diào)試debug程序_python
- 2022-05-15 Python?matplotlib?seaborn繪圖教程詳解_python
- 2023-06-16 C語(yǔ)言函數(shù)調(diào)用底層實(shí)現(xiàn)原理分析_C 語(yǔ)言
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門(mén)
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支