I have created a SamplePainter that inherits from CustomPainter as shown below, and in it, I am trying to create cartoonish screen tones using PictureRecorder.
When I try to use that screen tone to draw a shape using the paintImage function, I get an exception ("Object has been disposed.").
If I try to use canvas.drawImage instead, I get the same exception.
What is the problem?
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
void main() {
runApp(const MyApp());
}
// https://www.flutter-study.dev/widgets/button-widget
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter Demo'),
),
body: SizedBox(
width: 400,
height: 400,
child: CustomPaint(
painter: _SamplePainter(),
),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.green,
onPressed: () {},
child: const Icon(Icons.add),
),
),
);
}
}
class _SamplePainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) async {
final paint = Paint()..color = Colors.blue;
final boundsRect = const Offset(50, 50) & const Size(100, 100);
canvas.drawRect(boundsRect, paint);
var aPattern = await getPattern();
canvas.drawImage(aPattern, Offset.zero, paint);
// paintImage(
// canvas: canvas,
// rect: boundsRect,
// image: aPattern,
// repeat: ImageRepeat.repeat,
// );
}
Future<ui.Image> getPattern() async {
var pictureRecorder = ui.PictureRecorder();
Canvas patternCanvas = Canvas(pictureRecorder);
List<Offset> points = const [Offset(0, 0), Offset(1, 1)];
final patternPaint = Paint()
..color = Colors.black
..strokeWidth = 1
..style = PaintingStyle.stroke
..strokeJoin = StrokeJoin.round
..isAntiAlias = false;
patternCanvas.drawPoints(ui.PointMode.points, points, patternPaint);
final aPatternPicture = pictureRecorder.endRecording();
return aPatternPicture.toImage(2, 2);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
Try like this, the main problem you have, is that paint, should be sync method, not async
void main() {
runApp(const MyApp());
}
// https://www.flutter-study.dev/widgets/button-widget
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter Demo'),
),
body: SizedBox(
width: 400,
height: 400,
child: FutureBuilder(
future: getPattern()
builder: (BuildContext context, AsyncSnapshot<ui.Image>
snapshot) {
return CustomPaint(
painter: _SamplePainter(snapshot.data),
);
},
),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.green,
onPressed: () {},
child: const Icon(Icons.add),
),
),
);
}
}
Future<ui.Image> getPattern() async {
var pictureRecorder = ui.PictureRecorder();
Canvas patternCanvas = Canvas(pictureRecorder);
List<Offset> points = const [Offset(0, 0), Offset(1, 1)];
final patternPaint = Paint()
..color = Colors.black
..strokeWidth = 1
..style = PaintingStyle.stroke
..strokeJoin = StrokeJoin.round
..isAntiAlias = false;
patternCanvas.drawPoints(ui.PointMode.points, points, patternPaint);
final aPatternPicture = pictureRecorder.endRecording();
return aPatternPicture.toImage(2, 2);
}
class _SamplePainter extends CustomPainter {
final ui.Image? aPattern;
_SamplePainter(this.aPattern);
#override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = Colors.blue;
final boundsRect = const Offset(50, 50) & const Size(100, 100);
canvas.drawRect(boundsRect, paint);
if (aPattern != null) {
canvas.drawImage(aPattern!, Offset.zero, paint);
}
// paintImage(
// canvas: canvas,
// rect: boundsRect,
// image: aPattern,
// repeat: ImageRepeat.repeat,
// );
}
#override
bool shouldRepaint(_SamplePainter oldDelegate) {
return aPattern != oldDelegate.aPattern;
}
}
Related
I want to draw the widget like this:
For detail, I will use coordinates. The angle (0, 0), (0, 1) and (1, 1) is easy, the cross line from (0,6, 0) to (1, 1) is easy too, but the border in (0,6, 0) is so hard for me. Any ideal to draw this border?
Here is your Container and Clip Code... and also use Shape Maker to design such layout and you will get clip code
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
backgroundColor: Colors.grey,
appBar: AppBar(
title: const Text('Welcome to Flutter'),
),
body: ClipPath(
clipper: CustomClipPathTopContainer(),
child: Container(
height: 300,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30)
)
)
),
),
),
);
}
}
class CustomClipPathTopContainer extends CustomClipper<Path> {
#override
Path getClip(Size size) {
double w = size.width;
double h = size.height;
Paint paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth=10.0
..color = Colors.black;
Path path0 = Path();
path0.moveTo(0,size.height);
path0.lineTo(0,0);
path0.quadraticBezierTo(size.width*0.4875583,size.height*-0.0214000,size.width*0.5673083,size.height*0.0330714);
path0.quadraticBezierTo(size.width*0.6709917,size.height*0.1021143,size.width,size.height);
path0.lineTo(0,size.height);
path0.close();
return path0;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
Output
Is it possible to somehow do in flutter what is shown in the GIF?
And so that the starting point always displays a number (in what position this point is at the moment), in the range from 0-100.
EDIT: The animation is now similar to the one in GIF
import 'dart:ui';
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({super.key});
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
late final AnimationController _controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 3)
);
late final Size size = const Size(400,400);
late Path path = Path()
..moveTo(3, size.height-4.5)
..quadraticBezierTo(
size.width, size.height,
size.width-4.5, 3,
);
late PathAnimation pathAnimation = PathAnimation(path: path);
#override
void initState() {
super.initState();
_controller.forward();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Material App',
home: Scaffold(
appBar: AppBar(
title: const Text('Material App Bar'),
),
body: Center(
child: ClipRect(
child: AnimatedBuilder(
animation: _controller,
child: Container(
alignment: Alignment.center,
height: size.height,
width: size.width,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: Colors.black,
width: 3,
)
),
),
builder: (context, child) {
return CustomPaint(
foregroundPainter: PathPainter(
animation: _controller.value,
pathAnimation: pathAnimation,
),
child: child
);
}
),
),
),
),
);
}
}
class PathAnimation {
final Path path;
Path currentPath = Path();
late final List<PathMetric> pathSegments;
late final double totalLength;
double currentLength = 0;
int currentPathIndex = 0;
PathAnimation({
required this.path
}) {
pathSegments = path.computeMetrics().toList();
totalLength = pathSegments.fold(0, (value, element) => value + element.length);
}
Path getCurrentPath(double animation) {
while (animation > (currentLength + pathSegments[currentPathIndex].length) / totalLength) {
double segmentLength = pathSegments[currentPathIndex].length;
currentPath.addPath(
pathSegments[currentPathIndex].extractPath(0, segmentLength),
Offset.zero
);
currentPathIndex++;
currentLength += segmentLength;
}
if (currentPathIndex >= pathSegments.length) return currentPath;
double missingPartLength = animation - currentLength / totalLength;
double newSegmentLength = pathSegments[currentPathIndex].length;
return
currentPath..addPath(
pathSegments[currentPathIndex].extractPath(0, missingPartLength * newSegmentLength),
Offset.zero
);
}
}
class PathPainter extends CustomPainter{
double animation;
PathAnimation pathAnimation;
PathPainter({
required this.animation,
required this.pathAnimation,
});
#override
void paint(Canvas canvas, Size size) {
canvas.drawPath(
pathAnimation.getCurrentPath(animation),
Paint()
..style = PaintingStyle.stroke
..strokeWidth = 3
..color = Colors.red
);
}
#override
bool shouldRepaint(PathPainter oldDelegate) {
return animation != oldDelegate.animation;
}
}
I have a CustomPaint which paints an oval.
I want to cut out a hole at a specific position which I couldn't figure out yet how that works.
I tried:
canvas.drawPath(
Path.combine(PathOperation.difference, ovalPath, holePath),
ovalPaint,
);
but that doesn't cut the hole, but gives me the following result:
But this is what I want to achieve:
This oval is just an example, the "real" custom paint is gonna get more complex and I need more than just one cutout. So just painting several lines is not an alternative. I want to first define the path and then apply a cutout (or even inverted clipping) to get the hole.
Is that possible?
Here is a full working example of what I have:
import 'package:flutter/material.dart';
import 'dart:math';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: const Scaffold(
body: Center(
child: OvalCustomPaint(),
),
),
);
}
}
class OvalCustomPaint extends StatelessWidget {
const OvalCustomPaint({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: LayoutBuilder(
builder: (context, constraints) {
return Center(
child: CustomPaint(
painter: _Painter(),
child: SizedBox(
width: constraints.maxWidth,
height: constraints.maxHeight,
),
),
);
},
),
);
}
}
class _Painter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
canvas.translate(size.width / 2, size.height / 2);
const curveRadius = 50.0;
const legLength = 150.0;
canvas.rotate(pi/2);
final ovalPaint = Paint()
..color = Colors.blue
..strokeWidth = 2.5
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round;
const fixPoint = Offset.zero;
//* OVAL LINE
final ovalPath = Path()..moveTo(fixPoint.dx, fixPoint.dy);
ovalPath.relativeArcToPoint(
const Offset(curveRadius * 2, 0),
radius: const Radius.circular(curveRadius),
);
ovalPath.relativeLineTo(0, legLength);
ovalPath.relativeArcToPoint(
const Offset(-curveRadius * 2, 0),
radius: const Radius.circular(curveRadius),
);
ovalPath.relativeLineTo(0, -legLength);
//* CLP HOLE
final holePath = Path();
holePath.addArc(Rect.fromCircle(center: fixPoint, radius: 13), 0, 2 * pi);
canvas.drawPath(
Path.combine(PathOperation.difference, ovalPath, holePath),
ovalPaint,
);
}
#override
bool shouldRepaint(_Painter oldDelegate) => false;
}
Okay I found a solution for it.
I created a rect path with the size of the CustomPainter area and built the difference with the cutout hole path.
That created a cutout template:
final rectWithCutout = Path.combine(
PathOperation.difference,
Path()
..addRect(
Rect.fromCenter(
center: Offset.zero,
width: size.width,
height: size.height,
),
),
holePath);
Which I could clip the canvas with: canvas.clipPath(rectWithCutout);
The final result:
This is the working full code example again:
import 'dart:math';
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: const Scaffold(
body: Center(
child: OvalCustomPaint(),
),
),
);
}
}
class OvalCustomPaint extends StatelessWidget {
const OvalCustomPaint({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: LayoutBuilder(
builder: (context, constraints) {
return Center(
child: CustomPaint(
painter: _Painter(),
child: SizedBox(
width: constraints.maxWidth,
height: constraints.maxHeight,
),
),
);
},
),
);
}
}
class _Painter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
canvas.translate(size.width / 2, size.height / 2);
const curveRadius = 40.0;
const legLength = 130.0;
canvas.rotate(pi / 2);
final ovalPaint = Paint()
..color = Colors.blue
..strokeWidth = 2.5
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round;
const fixPoint = Offset.zero;
//* OVAL LINE
final ovalPath = Path()..moveTo(fixPoint.dx, fixPoint.dy);
ovalPath.relativeArcToPoint(
const Offset(curveRadius * 2, 0),
radius: const Radius.circular(curveRadius),
);
ovalPath.relativeLineTo(0, legLength);
ovalPath.relativeArcToPoint(
const Offset(-curveRadius * 2, 0),
radius: const Radius.circular(curveRadius),
);
ovalPath.relativeLineTo(0, -legLength);
//* CLIP HOLE
final holePath = Path();
holePath.addArc(Rect.fromCircle(center: fixPoint, radius: 13), 0, 2 * pi);
final rectWithCutout = Path.combine(
PathOperation.difference,
Path()
..addRect(
Rect.fromCenter(
center: Offset.zero,
width: size.width,
height: size.height,
),
),
holePath);
canvas.clipPath(rectWithCutout);
canvas.drawPath(
ovalPath,
ovalPaint,
);
}
#override
bool shouldRepaint(_Painter oldDelegate) => false;
}
You can control the length of the line.
ovalPath.relativeArcToPoint(
const Offset(-curveRadius * 2, 0),
radius: const Radius.circular(curveRadius),
);
ovalPath.relativeLineTo(0, -legLength + (13 * 2)); //here based on radius
canvas.drawPath(
ovalPath,
ovalPaint,
);
I built a custom timer widget and am calling it through the main.dart file. My timer widget essentially takes an argument totalDuration and using that starts running the timer. In the main.dart file I created a variable called counter and am passing it as the value to totalDuration. Till here it works fine. Now when I create a button, which on being clicked increments the counter variable and calls the setState method, my counter varibale is being incremented but widget is not being rebuilt. Why is that so and how could I go about solving this problem? For reference I have attached the codes from my both my main and timer file here.
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_app_test_counter/timer.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 60;
void _incrementCounter() {
setState(() {
print(_counter);
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Expanded(
child: Timer(
totalDuration: _counter,
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
timer.dart
import 'dart:ui';
import 'package:flutter/material.dart';
class Timer extends StatefulWidget {
final int totalDuration;
const Timer({Key key, this.totalDuration}) : super(key: key);
#override
_Timer createState() => _Timer();
}
class _Timer extends State<Timer> with TickerProviderStateMixin {
double _progress = 0.0;
bool _reversed = true;
bool _stopped = false;
Duration duration;
Animation<double> animation;
AnimationController controller;
String get _timeRemaining {
if (controller.lastElapsedDuration != null) {
duration = _reversed
? controller.duration - controller.lastElapsedDuration
: controller.lastElapsedDuration + Duration(seconds: 1);
}
return '${(duration.inHours).toString().padLeft(2, '0')}:${(duration.inMinutes % 60).toString().padLeft(2, '0')}:${(duration.inSeconds % 60).toString().padLeft(2, '0')}';
}
#override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: Duration(seconds: widget.totalDuration),
);
animation = Tween(begin: 1.0, end: 0.0).animate(controller)
..addListener(() {
setState(() {
_progress = animation.value;
});
});
controller.forward();
}
#override
void dispose() {
controller.dispose();
_stopped = !_stopped;
super.dispose();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => _reversed = !_reversed,
child: Scaffold(
body: CustomPaint(
painter:
ShapePainter(progress: _progress, timeRemaining: _timeRemaining),
child: Container(),
),
),
);
}
}
class ShapePainter extends CustomPainter {
double progress;
String timeRemaining;
ShapePainter({this.progress, this.timeRemaining});
#override
void paint(Canvas canvas, Size size) {
final rectBounds = Rect.fromLTRB(0, 0, size.width, size.height);
final rectPaint = Paint()
..strokeWidth = 1
..style = PaintingStyle.fill
..color = Colors.orange;
canvas.drawRRect(
RRect.fromRectAndRadius(rectBounds, Radius.circular(10)),
rectPaint,
);
var paintProgressBar = Paint()
..color = Colors.white
..strokeWidth = 6
..strokeCap = StrokeCap.round;
Offset progressStartingPoint = Offset(42, size.height - 60);
Offset progressEndingPoint = Offset(size.width - 42, size.height - 60);
canvas.drawLine(
progressStartingPoint, progressEndingPoint, paintProgressBar);
var paintDoneBar = Paint()
..color = Colors.deepOrange
..strokeWidth = 6
..strokeCap = StrokeCap.round;
Offset doneStartingPoint = Offset(42, size.height - 60);
Offset doneEndingPoint =
Offset(((size.width - 84) * (1.0 - progress) + 42), size.height - 60);
canvas.drawLine(doneStartingPoint, doneEndingPoint, paintDoneBar);
final timerTextStyle = TextStyle(
color: Colors.indigo,
fontSize: 30,
);
final timerTextSpan = TextSpan(
text: timeRemaining,
style: timerTextStyle,
);
final timerTextPainter = TextPainter(
text: timerTextSpan,
textDirection: TextDirection.ltr,
);
timerTextPainter.layout(
minWidth: 0,
maxWidth: size.width,
);
final timerOffset = Offset(size.width / 2, size.height / 2 - 40);
timerTextPainter.paint(canvas, timerOffset);
final textStyle = TextStyle(
color: Colors.black,
fontSize: 30,
);
final textSpan = TextSpan(
text: 'time left',
style: textStyle,
);
final textPainter = TextPainter(
text: textSpan,
textDirection: TextDirection.ltr,
);
textPainter.layout(
minWidth: 0,
maxWidth: size.width,
);
final offset = Offset((size.width - 20) / 2, (size.height - 20) / 2);
textPainter.paint(canvas, offset);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
One quick dirty fix is to add a key to the Timer widget
Timer(
key: UniqueKey(),
totalDuration: _counter,
),
Other way is to use didUpdateWidget function in your child Timer() widget.
In that function, you can make the changes.
#override
void didUpdateWidget(Timer oldWidget) {
print("didUpdateWidget called");
super.didUpdateWidget(oldWidget);
}
https://api.flutter.dev/flutter/widgets/State/didUpdateWidget.html
I am trying to create an image using Canvas and Picture Recorder.
I am able to output an image with two circles correctly using..
c.drawCircle(offset, 20.0, paint);
c.drawCircle(offset2, 20.0, paint);
But using following code returns a blank image
c.drawImage(imagetoDraw, offset3, paint);
This is how I am importing imagetoDraw asset
getImageFromAsset(){
rootBundle.load("images/1.jpg").then( (bd) {
Uint8List lst = new Uint8List.view(bd.buffer);
ui.instantiateImageCodec(lst).then( (codec) {
codec.getNextFrame().then(
(frameInfo) {
imagetoDraw = frameInfo.image;
print ("bkImage instantiated: $imagetoDraw");
}
);
});
});
}
What am I missing?
complete code:
import 'package:flutter/material.dart';
//import 'package:path_provider/path_provider.dart';
import 'dart:ui' as ui;
import 'dart:typed_data';
import 'dart:async';
//import 'dart:io';
import 'package:flutter/services.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Image _image;
ui.Image imagetoDraw;
#override
void initState() {
super.initState();
_image = new Image.network(
'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_120x44dp.png',
);
getImageFromAsset();
}
getImageFromAsset(){
rootBundle.load("images/1.jpg").then( (bd) {
Uint8List lst = new Uint8List.view(bd.buffer);
ui.instantiateImageCodec(lst).then( (codec) {
codec.getNextFrame().then(
(frameInfo) {
imagetoDraw = frameInfo.image;
print ("bkImage instantiated: $imagetoDraw");
}
);
});
});
}
_generateImage() {
_generate().then((val) => setState(() {
_image = val;
}));
}
Future<Image> _generate() async {
ui.PictureRecorder recorder = new ui.PictureRecorder();
Canvas c = new Canvas(recorder);
var rect = new Rect.fromLTWH(0.0, 0.0, 100.0, 100.0);
c.clipRect(rect);
final paint = new Paint();
paint.strokeWidth = 2.0;
paint.color = const Color(0xFF333333);
paint.style = PaintingStyle.fill;
final offset = new Offset(10.0, 10.0);
final offset2 = new Offset(90.0, 90.0);
final offset3 = new Offset(30.0, 30.0);
c.drawCircle(offset, 20.0, paint);
c.drawCircle(offset2, 20.0, paint);
c.drawImage(imagetoDraw, offset3, paint);
var picture = recorder.endRecording();
final pngBytes = await picture
.toImage(100, 100)
.toByteData(format: ui.ImageByteFormat.png);
var image = Image.memory(pngBytes.buffer.asUint8List());
return image;
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_image,
],
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _generateImage,
tooltip: 'Generate',
child: new Icon(Icons.add),
),
);
}
}
This seems to be a GPU-related issue, as mentioned in the comments flutter run --enable-software-rendering can be a workaround. I've tried your locally and it works well without issues.