在 Flutter 中想要繪圖的話,可以繼承 CustomPainter
,它有兩個方法必須實作,paint
與 shouldRepaint
,前者用來定義如何繪圖,後者決定何時必須重繪。
這邊從簡單的繪圖開始,如果想在使用者每次觸控螢幕時的位置增加一個圓,那麼每次的觸控必須記錄使用者點過的每個位置,這些位置要傳給你的 CustomPainter
實作,例如:
class CirclesPainter extends CustomPainter {
final List<Offset> points;
CirclesPainter({this.points});
@override
void paint(Canvas canvas, Size size) {
var paint = Paint()
..color = Colors.deepOrangeAccent;
var radius = (size.width + size.height) / 20;
points.forEach((point) {
canvas.drawCircle(point, radius, paint);
});
}
@override
bool shouldRepaint(CirclesPainter other) => points.length != other.points.length;
}
建立 CirclesPainter
時必須指定 points
,表示使用者點過的每個點,而在 paint
方法部份,canvas
是代表畫布的物件,size
參數是畫布的尺寸,在方法中,Paint
實例就像是畫筆,在範例中只指定了顏色,而在繪製圓時,使用 size
來計算出圓的半徑,並透過 canvas
的 drawCircle
來畫圓。
shouldRepaint
表示何時該重繪圓,在這邊指定了 points
的長度與新的 CirclesPainter
中的 points
不同就重繪。
接著來定義一個 Widget
,可以處理使用者的觸控事件,並記錄每次觸控的位置:
class Circles extends StatefulWidget {
Circles({Key key}) : super(key: key);
@override
_CirclesState createState() => _CirclesState();
}
class _CirclesState extends State<Circles> {
final List<Offset> points = [];
@override
Widget build(BuildContext context) {
return GestureDetector(
child: CustomPaint(
size: Size.infinite,
painter: CirclesPainter(points: List.from(points)),
),
onPanDown: (details) {
setState(() {
points.add(details.localPosition);
});
},
);
}
}
在這邊可以看到使用了 GestureDetector
的 onPanDown
,透過其 details
可以取得觸控的點,而其 child
為 CustomPaint
,它需要搭配一個 CustomPainter
實例來進行繪圖。
CustomPaint
可以有子 Widget
,如果指定了 child
,那麼會 size
會是子 Widget
的尺寸,若沒有指定 child
,就會使用 size
的設定值,而 size
的預設值是 Size.zero
。
這邊的範例沒有指定 child
,如果沒有指定 size
的話,那麼你的畫布尺寸就是 Size.zero
,也就畫不出東西來了,這邊將 size
設為 Size.infinite
,並不是就擁有了無限尺寸的畫布,別忘了〈OVERFLOW 是啥?〉中談過的,CustomPaint
最後實際尺寸,會受到父元件的約束,簡單來說,這邊 size
設為 Size.infinite
目的,只是希望儘可能取得最大可用的繪圖尺寸。
接著只要將以上的元件組裝一下就可以了:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
/// This Widget is the main application widget.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Openhome.cc')),
body: Circles(),
),
);
}
}
來看一下執行效果: