Reputation: 93
I tried to draw image using Canvas and CustomPainter but it's not working. This is for an Android app using Flutter Framework.
I just used command flutter build apk
in Windows cmd to build the app.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(title: Text('Title!')),
body: Center(
child: AspectRatio(
aspectRatio: 1.0,
child: CustomPaint(
painter: ImageEditor(),
),
),
),
);
}
}
class ImageEditor extends CustomPainter {
@override
Future paint(Canvas canvas, Size size) async {
canvas.save();
ByteData bd = await rootBundle.load("assets/sampleImagees.jpg");
final Uint8List bytes = Uint8List.view(bd.buffer);
final ui.Codec codec = await ui.instantiateImageCodec(bytes);
final ui.Image image = (await codec.getNextFrame()).image;
canvas.drawImage(image, Offset(0.0, 0.0), Paint());
canvas.save();
canvas.restore();
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
There isn't any error but nothing happend in the app. It's just a white screen. What's wrong in my code?
White screen of the builded app
Upvotes: 8
Views: 10070
Reputation: 35
As @Muldec said, the image needs to be available before drawing it on the canvas. In that case the FuturBuilder is appropriate.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
late Future<ui.Image> _image;
@override
void initState() {
_image = _loadImage('path/image.jpg');
}
Future<ui.Image> _loadImage(String imagePath) async {
ByteData bd = await rootBundle.load(imagePath);
final Uint8List bytes = Uint8List.view(bd.buffer);
final ui.Codec codec = await ui.instantiateImageCodec(bytes);
final ui.Image image = (await codec.getNextFrame()).image;
return image;
}
@override
Widget build(BuildContext context) {
return FutureBuilder<ui.Image>(
future: _image,
builder: (BuildContext context, AsyncSnapshot<ui.Image> snapshot) {
Widget centerChild;
if (snapshot.hasData) {
centerChild =
CustomPaint(painter: ImageEditor(image: snapshot.data!));
} else {
centerChild = const CircularProgressIndicator();
}
return Scaffold(
appBar: AppBar(title: const Text('Title!')),
body: Center(child: centerChild));
});
}
}
class ImageEditor extends CustomPainter {
final ui.Image image;
ImageEditor({required this.image});
@override
Future paint(Canvas canvas, Size size) async {
canvas.save();
canvas.drawImage(image, Offset(0.0, 0.0), Paint());
canvas.save();
canvas.restore();
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
Upvotes: 2
Reputation: 4921
Your image needs to be available before the canvas is drawn. By moving the loading code outside of the painter, the painter now works as intended :
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
ui.Image _image;
@override
void initState() {
_loadImage();
}
_loadImage() async {
ByteData bd = await rootBundle.load("assets/sampleImagees.jpg");
final Uint8List bytes = Uint8List.view(bd.buffer);
final ui.Codec codec = await ui.instantiateImageCodec(bytes);
final ui.Image image = (await codec.getNextFrame()).image;
setState(() => _image = image);
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(title: Text('Title!')),
body: Center(
child: AspectRatio(
aspectRatio: 1.0,
child: CustomPaint(
painter: ImageEditor(_image),
)
),
),
);
}
}
class ImageEditor extends CustomPainter {
ui.Image image;
ImageEditor(this.image) : super();
@override
Future paint(Canvas canvas, Size size) async {
if (image != null) {
canvas.drawImage(image, Offset(0.0, 0.0), Paint());
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return image != (oldDelegate as ImageEditor).image;
}
}
Note that you can easily draw an image using Image.asset
instead of a CustomPainter
:
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(title: Text('Title!')),
body: Center(
child: AspectRatio(
aspectRatio: 1.0,
child: Image.asset('assets/sampleImagees.jpg'),
),
),
);
}
Upvotes: 11