今天来学习一下flutter中的绘制,从如何使用Canvas draw/paint了解到,在Flutter中使用绘制方式自定义Widget,需要以下三个步骤:
-
1.继承CustomPainter并重写paint方法和shouldRepaint方法
-
2.在写paint方法中绘制内容
-
3.使用CustomPaint来构建Widget
先通过写一个简单的画板来学习一下paint的使用
first:声明一个画纸的class,如上所说继承CustomPainter并重写paint方法和shouldRepaint方法
class Paper extends CustomPainter {
Paper({
@required this.lines,
this.positions,
}) {
_paint = Paint()
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round;
}
Paint _paint;
final List<List<TolyCircle>> lines;
final List<TolyCircle> positions;
@override
void paint(Canvas canvas, Size size) {
// TODO: implement paint
for (int i = 0; i < lines.length; i++) {
drawLine(canvas, lines[i]);
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
void drawLine(Canvas canvas, List<TolyCircle> positions) {
//注意这里是positions.length - 1
for (var i = 0; i < positions.length - 1; i++) {
if (positions[i] != null && positions[i + 1] != null) {
canvas.drawLine(positions[i].pos, positions[i + 1].pos,
_paint..strokeWidth = positions[i].radius);
_paint..color = positions[i].color ?? Colors.red;
}
}
}
}
这里注意一下drawLine中for循环 i < positions.length - 1;而不是i < positions.length ;
second:那画纸上画的是什么呢?点?线?都可以,线就是一些点的 集合,所以我们在声明一个点的class,最重要的就是要有一个Offset属性(点的位置)
class TolyDrawable {
Offset pos;
Color color;
TolyDrawable(this.pos,this.color);
}
class TolyCircle extends TolyDrawable {
double radius;
TolyCircle(Color color, Offset pos, {this.radius = 4.0}) : super(pos,color);
}
third:把我们的画纸放手机屏幕上,就开始划拉吧~
因为我们手指每滑动一下都要更新页面,所以组件为有状态组件,_lines为状态量,在移动时将点加入当前所画的线
class _AnimationPageState extends State<AnimationPage> {
var _positions = <TolyCircle>[];
var _lines = <List<TolyCircle>>[];
Offset _oldPos;
@override
Widget build(BuildContext context) {
//按下时表示新添加一条线,并记录上一点位置
void _panDown(DragDownDetails details) {
var x = details.globalPosition.dx;
var y = details.globalPosition.dy;
// _positions.add(TolyCircle(Colors.red, Offset(x, y), radius: 4.0));
print('pan down ${_positions.toString()}');
_lines.add(_positions);
_oldPos = Offset(x, y);
}
///移动中,将点添加到点集中
void _panUpdate(DragUpdateDetails details) {
var x = details.globalPosition.dx;
var y = details.globalPosition.dy;
var curPos = Offset(x, y);
if ((curPos - _oldPos).distance > 3) {
var tolyCirle = TolyCircle(Colors.blue, curPos, radius: 4.0);
_positions.add(tolyCirle);
_oldPos = curPos;
setState(() {});
}
}
/// 抬起后,将旧线拷贝到线集中
void _panEnd(DragEndDetails details) {
var oldLine = <TolyCircle>[];
for (var i = 0; i < _positions.length; i++) {
oldLine.add(_positions[i]);
}
_lines.add(oldLine);
_positions.clear();
print(_lines.toString());
}
return GestureDetector(
child: Scaffold(
body: CustomPaint(
painter: Paper(lines: _lines),
),
),
onPanDown: (DragDownDetails details) {
_panDown(details);
},
onPanUpdate: _panUpdate,
onPanEnd: _panEnd,
onDoubleTap: () {
_lines.clear();
setState(() {});
},
);
}
}
这里着重注意一下_panDown()手指按下的时候要开始画一条新的线,所以调用_lines.add(_positions);时 _positions其实是一个空数组,这样就保证了每次手指按下然后抬起画一条线,和下一次重复画线的动作,画的线是分开的 ,这里描述的可能不太容易懂,可以自己试试,如果_panDown的时候只是_positions.add(TolyCircle(Colors.red, Offset(x, y), radius: 4.0));是什么效果,每次画的线就变成了收尾相连的了。。。
下面是效果图:
测试感觉着还是不太流畅,不太顺滑,需要再研究一下怎么优化
文末加上源码地址:https://github.com/AnleSu/flutter_paintPaper 以后有空会继续更新paint相关的demo