前沿
最近有看到不少貝塞爾曲線相關(guān)的文章,勾發(fā)了我對Flutter貝塞爾曲線探究的興趣,這里通過Flutter對貝塞爾曲線做幾個(gè)有趣的探索,希望對大家有所幫助。
貝塞爾曲線是一個(gè)大家比較熟悉的繪制曲線了,這里就不過多介紹。如果有不清楚原理的同學(xué),可以點(diǎn)擊這里了解。
探索
一、貝塞爾曲線實(shí)現(xiàn)自定義圖案
- 通過貝塞爾曲線可視化工具預(yù)先繪制圖案,這里以繪制愛心為例,我們先繪制如下形狀:

- Flutter中繪制。Flutter的Path封裝了貝塞爾曲線的對應(yīng)api,我們只需要將上述工具繪制的坐標(biāo)點(diǎn)傳入。注意當(dāng)我們繪制完左半愛心后,右半愛心只需要根據(jù)中心點(diǎn)坐標(biāo)對稱繪制即可。
// 繪制背景
canvas.drawColor(const Color(0xFFF1F1F1), BlendMode.color);
// 獲取屏幕中心點(diǎn)坐標(biāo)
centerX = size.width * 0.5;
// 左半愛心
leftPath.moveTo(centerX, 400);
leftPath.cubicTo(centerX - 207, 267, centerX - 107, 100, centerX, 194);
// 右半愛心
rightPath.moveTo(centerX, 400);
rightPath.cubicTo(centerX + 207, 267, centerX + 107, 100, centerX, 194);
// 繪制曲線
canvas.drawPath(leftPath, _paint1);
canvas.drawPath(rightPath, _paint1);
效果圖如下:

ok,我們完成了一個(gè)通過貝塞爾曲線繪制自定義圖形的探索,是不是感覺非常簡單!
二、繪制的曲線添加繪制動(dòng)畫
如果僅僅實(shí)現(xiàn)上面的實(shí)例,那太簡單了,我們做一點(diǎn)更有難度的探索。比如說在繪制的過程中添加動(dòng)畫,讓繪制過程動(dòng)起來。
剛開始有這個(gè)想法的時(shí)候,大腦毫無頭緒。因?yàn)槲覀兊膱D形是通過貝塞爾曲線畫出來的,我們沒有掌握到path軌跡繪制的細(xì)節(jié),也就無法通過動(dòng)畫來增量更新path的繪制...
那我們有沒有辦法獲取到Path的細(xì)節(jié)呢,結(jié)論是有的。經(jīng)過一系列對Flutter的Api探索(其實(shí)是百度、Google)后,Path.computeMetrics()給了我答案。我們可以通過PathMetric獲取到Path的測量位置,然后遍歷獲取到Path的所有Point,然后通過drawPoints()按照時(shí)間維度進(jìn)行繪制,最后實(shí)現(xiàn)我們想要的繪制動(dòng)畫效果。
好了,廢話不多說直接上代碼:
// 獲取path上的點(diǎn)數(shù)據(jù)
// 左邊
PathMetrics leftPms = leftPath.computeMetrics();
PathMetric leftPm = leftPms.elementAt(0);
double leftLen = leftPm.length;
double tmpStart = 0;
double leftEnd = min(_fraction, leftLen);
var isCompleted = leftEnd == leftLen;
for (; tmpStart < leftEnd; tmpStart += 1) {
Tangent? t = leftPm.getTangentForOffset(tmpStart);
if (t != null) {
_leftPoints.add(t.position);
}
}
// 右邊
PathMetrics rightPms = rightPath.computeMetrics();
PathMetric rightPm = rightPms.elementAt(0);
double rightLen = rightPm.length;
tmpStart = 0;
double rightEnd = min(_fraction, rightLen);
for (; tmpStart < rightEnd; tmpStart += 1) {
Tangent? t = rightPm.getTangentForOffset(tmpStart);
if (t != null) {
_rightPoints.add(t.position);
}
}
// 繪制背景線
canvas.drawPath(leftPath, _paint1);
canvas.drawPath(rightPath, _paint1);
// 繪制點(diǎn)
canvas.drawPoints(PointMode.points, _leftPoints, _paint2);
canvas.drawPoints(PointMode.points, _rightPoints, _paint2);
然后通過動(dòng)畫增量更新Points的繪制進(jìn)度
_animation = Tween(begin: 0.0, end: len).animate(_controller)
..addListener(() {
setState(() {
_fraction = _animation.value;
});
});
當(dāng)動(dòng)畫執(zhí)行完成的時(shí)候,我們把愛心填充,這樣更加突出
// 繪制愛心
// 動(dòng)畫結(jié)束時(shí),填充愛心
if (isCompleted) {
_paint1.style = PaintingStyle.fill;
_paint1.color = red;
}
最后不要忘記了將CustomPainter的shouldRepaint設(shè)置為true,允許widget觸發(fā)build的時(shí)候重新繪制
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
現(xiàn)在我們來看看最終實(shí)現(xiàn)的效果吧:

后續(xù)
目前,我們嘗試了:(1)通過貝塞爾曲線繪制自定義圖形;(2)為繪制的曲線添加繪制動(dòng)畫;2種曲線繪制的有趣探索,后續(xù)在《Flutter貝塞爾曲線的有趣探索(下)》文章中將嘗試更多有趣的探索:(3)通過手勢繪制自定義圖形;(4)將繪制的圖形生成個(gè)人作品并添加自定義特效;
歡迎大家點(diǎn)贊收藏持續(xù)關(guān)注我!最后貼上項(xiàng)目源碼的Github。