Created
September 3, 2020 21:07
-
-
Save dsyrstad/14274fe5bb2c2da4572b31316ca5c1bd to your computer and use it in GitHub Desktop.
Flutter Path.addPath() bug
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:ui'; | |
import 'package:flutter/material.dart'; | |
void main() => runApp(MyApp()); | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'AddPath Bug Demo', | |
theme: ThemeData( | |
primarySwatch: Colors.green, | |
), | |
home: BugPainter(), | |
); | |
} | |
} | |
enum DrawingMethod { AddPath, AddPathWithMoveTo, AddPathWithNoAA, DirectToCanvas } | |
class BugPainter extends StatefulWidget { | |
@override | |
_BugPainterState createState() => _BugPainterState(); | |
} | |
class _BugPainterState extends State<BugPainter> with TickerProviderStateMixin { | |
Animation<double> animation; | |
AnimationController controller; | |
DrawingMethod _drawingMethod = DrawingMethod.AddPath; | |
@override | |
void initState() { | |
super.initState(); | |
controller = AnimationController( | |
vsync: this, | |
duration: Duration(seconds: 1), | |
); | |
final valueTween = Tween<double>(begin: 0, end: 1); | |
animation = valueTween.animate(controller) | |
..addListener(() { | |
setState(() {}); | |
}) | |
..addStatusListener((status) { | |
if (status == AnimationStatus.completed) { | |
controller.repeat(); | |
} else if (status == AnimationStatus.dismissed) { | |
controller.forward(); | |
} | |
}); | |
controller.forward(); | |
} | |
@override | |
void dispose() { | |
controller.dispose(); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
body: SafeArea( | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: <Widget>[ | |
Padding( | |
padding: EdgeInsets.only(bottom: 120), | |
), | |
for (final type in DrawingMethod.values) | |
RadioListTile<DrawingMethod>( | |
title: Text(type.toString()), | |
value: type, | |
groupValue: _drawingMethod, | |
onChanged: (value) { | |
setState(() { | |
_drawingMethod = value; | |
}); | |
}, | |
), | |
Expanded( | |
child: AnimatedBuilder( | |
animation: animation, | |
builder: (context, snapshot) { | |
return CustomPaint( | |
painter: ShapePainter(animation.value, _drawingMethod), | |
child: Container(), | |
); | |
}, | |
), | |
), | |
], | |
), | |
), | |
); | |
} | |
} | |
class ShapePainter extends CustomPainter { | |
final double _value; | |
final DrawingMethod _drawingMethod; | |
final List<PathContent> _paths = List(); | |
ShapePainter(this._value, this._drawingMethod) { | |
for (int i = 0; i < 15; i++) { | |
_paths.add(PathContent(_drawingMethod)); | |
} | |
} | |
@override | |
void paint(Canvas canvas, Size size) { | |
double x = size.width / 2; | |
double y = (size.height - size.width) / 2; | |
for (final path in _paths) { | |
final transform = Matrix4.identity() | |
..translate(x, y) | |
..scale(1 - _value / 10); | |
path.draw(canvas, transform); | |
} | |
} | |
@override | |
bool shouldRepaint(CustomPainter oldDelegate) { | |
return true; | |
} | |
} | |
class PathContent { | |
final Path _path = Path(); | |
final DrawingMethod _drawingMethod; | |
final List<Path> children = List(); | |
final Paint _paint = Paint() | |
..color = Colors.teal | |
// fill makes a huge difference here. If you change it to stroke, there's no problem. | |
..style = PaintingStyle.fill; | |
final Paint _paintNoAA = Paint() | |
..color = Colors.teal | |
..style = PaintingStyle.fill | |
..isAntiAlias = false; | |
PathContent(this._drawingMethod) { | |
final rect = Rect.fromLTWH(0, 0, 40, 40); | |
for (var i = 0; i < 10; i++) { | |
children.add(Path()..addRect(rect)); | |
} | |
} | |
void draw(Canvas canvas, Matrix4 transform) { | |
switch (_drawingMethod) { | |
case DrawingMethod.AddPath: | |
drawUsingAddPath(canvas, transform); | |
break; | |
case DrawingMethod.DirectToCanvas: | |
drawDirectToCanvas(canvas, transform); | |
break; | |
case DrawingMethod.AddPathWithMoveTo: | |
drawUsingAddPathWithMoveTo(canvas, transform); | |
break; | |
case DrawingMethod.AddPathWithNoAA: | |
drawUsingAddPathWithNoAntiAliasing(canvas, transform); | |
break; | |
} | |
} | |
void drawUsingAddPath(Canvas canvas, Matrix4 transform) { | |
_path.reset(); | |
for (final child in children) { | |
_path.addPath(child, Offset.zero, matrix4: transform.storage); | |
} | |
canvas.drawPath(_path, _paint); | |
} | |
void drawUsingAddPathWithMoveTo(Canvas canvas, Matrix4 transform) { | |
_path.reset(); | |
for (final child in children) { | |
_path.moveTo(0, 0); // Speeds it up somewhat | |
_path.addPath(child, Offset.zero, matrix4: transform.storage); | |
} | |
canvas.drawPath(_path, _paint); | |
} | |
void drawUsingAddPathWithNoAntiAliasing(Canvas canvas, Matrix4 transform) { | |
_path.reset(); | |
for (final child in children) { | |
_path.addPath(child, Offset.zero, matrix4: transform.storage); | |
} | |
canvas.drawPath(_path, _paintNoAA); | |
} | |
void drawDirectToCanvas(Canvas canvas, Matrix4 transform) { | |
canvas.save(); | |
canvas.transform(transform.storage); | |
for (final child in children) { | |
canvas.drawPath(child, _paint); | |
} | |
canvas.restore(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment