原文地址 : Playing with Paths in Flutterhtml
在 Flutter 中,萬物皆是 Widget ,同時 Flutter 中也提供了許多了不得的 Widget 供咱們使用,可是這裏面最能使人喜歡的仍是 CustomPaint。git
CustomPaint 這個組件爲咱們提供了一個畫布,在 Flutter 的繪圖(paint)階段,咱們能夠把咱們想要繪畫內容繪製上去。github
想要在 canvas 上繪圖,有多種不一樣的方式,其中最高效和經常使用的就是使用 Path,在本篇文章中,將會展現 Path 的繪製以及在 Path 上應用動畫。若是你對 Path 不熟悉的話,能夠參考一下這篇文章。編程
在 Flutter 中,經過 Path 畫線是很是容易的一件事。 首先,將繪製的啓動經過 moveTo 方法移動到指定位置,而後經過 lineTo 方法進行繪製。canvas
class LinePainter extends CustomPainter {
final double progress;
LinePainter({this.progress});
Paint _paint = Paint()
..color = Colors.black
..strokeWidth = 4.0
..style = PaintingStyle.stroke
..strokeJoin = StrokeJoin.round;
@override
void paint(Canvas canvas, Size size) {
var path = Path();
path.moveTo(0, size.height / 2);
path.lineTo(size.width * progress, size.height / 2);
canvas.drawPath(path, _paint);
}
@override
bool shouldRepaint(LinePainter oldDelegate) {
return oldDelegate.progress != progress;
}
}
複製代碼
效果:api
畫虛線相對畫直線來講就複雜一點了,Flutter 中沒有直接提供畫虛線的方法,可是咱們能夠藉助 PathMetric 來實現。bash
pathMetric 是一個對 Path 進行測量而且可以提取子 Path 的工具。微信
首先,咱們要畫一條直線,和上面畫直線同樣,而後咱們經過 path.computeMetrics() 獲取到 PathMetrics 對象。經過對 PathMetric 遍歷,咱們能夠提取到子 Path,這個子 Path 的起點有當前 distance 指定,而長度是咱們本身定義的 dashWidth 。ide
dashPath.addPath(
pathMetric.extractPath(distance, distance + dashWidth),
Offset.zero,
);
複製代碼
完整代碼:函數
class DashLinePainter extends CustomPainter {
final double progress;
DashLinePainter({this.progress});
Paint _paint = Paint()
..color = Colors.black
..strokeWidth = 4.0
..style = PaintingStyle.stroke
..strokeJoin = StrokeJoin.round;
@override
void paint(Canvas canvas, Size size) {
var path = Path()
..moveTo(0, size.height / 2)
..lineTo(size.width * progress, size.height / 2);
Path dashPath = Path();
double dashWidth = 10.0;
double dashSpace = 5.0;
double distance = 0.0;
for (PathMetric pathMetric in path.computeMetrics()) {
while (distance < pathMetric.length) {
dashPath.addPath(
pathMetric.extractPath(distance, distance + dashWidth),
Offset.zero,
);
distance += dashWidth;
distance += dashSpace;
}
}
canvas.drawPath(dashPath, _paint);
}
@override
bool shouldRepaint(DashLinePainter oldDelegate) {
return oldDelegate.progress != progress;
}
}
複製代碼
效果:
圓形其實本質是一個特殊的橢圓,咱們能夠經過 addOval 方法繪製一個橢圓,這個方法須要一個 Rect 類型的參數,若是咱們想繪製圓形,能夠經過 Rect.fromCircle 來實現。
@override
void paint(Canvas canvas, Size size) {
var path = Path();
path.addOval(Rect.fromCircle(
center: Offset(0, 0),
radius: 80.0,
));
canvas.drawPath(path, myPaint);
}
複製代碼
上面的代碼的效果以下:
畫一個圓形仍是很容易的,接下來嘗試畫一個複雜的,以下:
對上面的繪製圖形簡單分析一下,因此的圓形大小相同,相切於同一個點 (0,0),而後全部圓形的交點能夠組成一個圓形,而且相鄰的兩個點之間的弧度相同。
咱們先嚐試分析一下這些交點的關係。
首先假設一共有 n 個圓形,那麼將有 n 給交點,而後假設其中的一個點(也是一個圓的圓心)的座標是 (x,y) 。
因爲一個圓的弧度是 2π,那麼圓的弧度和個數置級的關係以下:
接下來就是咱們高中學到的三角函數了。經過上面的分析,咱們能夠獲得以下的值:
進而計算得出 x 和 y 的值 :
所以到這裏,咱們有了每一個圓形圓心的 x 與 y 的計算方法,而後圓形的半徑 r 是咱們本身指定的,這樣咱們就知道了繪製圓所須要的所有信息,用代碼表示以下:
@override
void paint(Canvas canvas, Size size) {
var path = createPath();
canvas.drawPath(path, myPaint);
}
Path createPath() {
var path = Path();
int n = circles.toInt();
var range = List<int>.generate(n, (i) => i + 1);
double angle = 2 * math.pi / n;
for (int i in range) {
double x = radius * math.cos(i * angle);
double y = radius * math.sin(i * angle);
path.addOval(Rect.fromCircle(center: Offset(x, y), radius: radius));
}
return path;
}
複製代碼
因爲圓形的個數、半徑、圓心所在位置咱們都是知道的,那麼進一步咱們還能夠進行動態的圓形繪製。
動畫須要使用 AnimationController
class _CirclesState extends State<Circles> with SingleTickerProviderStateMixin {
AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 3),
);
_controller.value = 1.0;
}
複製代碼
而圓形的動態繪製須要用到 pathMetrics,這個類是一個輔助類能夠用來測量和提取子路徑的。
@override
void paint(Canvas canvas, Size size) {
var path = createPath();
PathMetrics pathMetrics = path.computeMetrics();
for (PathMetric pathMetric in pathMetrics) {
Path extractPath = pathMetric.extractPath(
0.0,
pathMetric.length * progress,
);
canvas.drawPath(extractPath, myPaint);
}
}
複製代碼
詳細的代碼參考以下:
效果:
path 繪製裏面另外一個比較重要的部分就是多邊形的繪製,多邊形的每一條邊都是一條直線。
多邊形裏面每一個頂點的座標的計算方式相似與上面說到的圓形圓心的計算。
知道了頂點的座標,繪製每條邊就很容易了。
class PolygonPainter extends CustomPainter {
PolygonPainter({
this.sides,
this.progress,
this.showPath,
this.showDots,
});
final double sides;
final double progress;
bool showDots, showPath;
final Paint _paint = Paint()
..color = Colors.purple
..strokeWidth = 4.0
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round;
@override
void paint(Canvas canvas, Size size) {
var path = createPath(sides.toInt(), 100);
PathMetric pathMetric = path.computeMetrics().first;
Path extractPath =
pathMetric.extractPath(0.0, pathMetric.length * progress);
if (showPath) {
canvas.drawPath(extractPath, _paint);
}
if (showDots) {
try {
var metric = extractPath.computeMetrics().first;
final offset = metric.getTangentForOffset(metric.length).position;
canvas.drawCircle(offset, 8.0, Paint());
} catch (e) {}
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
Path createPath(int sides, double radius) {
var path = Path();
var angle = (math.pi * 2) / sides;
path.moveTo(radius * math.cos(0.0), radius * math.sin(0.0));
for (int i = 1; i <= sides; i++) {
double x = radius * math.cos(angle * i);
double y = radius * math.sin(angle * i);
path.lineTo(x, y);
}
path.close();
return path;
}
}
複製代碼
效果:
曲線能夠理解爲點的移動,畫一個螺旋曲線其實仍是有點難度的。
爲了達到曲線的效果,咱們能夠先把中心的移動到 (x,y) 座標,而後,對於下一個點,咱們讓半徑增長 0.75,而弧度增長 2π/50. 對於每一個新增的點,因爲半徑和角度增長的都很小,所以咱們在視覺上看到的就是一條曲線了,而不是直線。
Path createSpiralPath(Size size) {
double radius = 0, angle = 0;
Path path = Path();
for (int n = 0; n < 200; n++) {
radius += 0.75;
angle += (math.pi * 2) / 50;
var x = size.width / 2 + radius * math.cos(angle);
var y = size.height / 2 + radius * math.sin(angle);
path.lineTo(x, y);
}
return path;
}
複製代碼
一樣的動畫效果須要使用 pathMetric。 完整代碼能夠在這裏找到 :
效果:
最後展現一個行星旋轉的動畫效果,完整代碼地址:
效果:
歡迎關注「Flutter 編程開發」微信公衆號 。