はじめに
image_pickerを使えば簡単に画像を取ってこれるのですが、
それをCustomPaintを使って表示する方法でとても手こずってしまったので備忘録を兼ねて記して置こうと思います。
CustomPaintを使えると、画像の上に描画もできるので画像の編集とかにも使えそうですね!
使うライブラリ
ソースコード全体
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.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,
),
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> {
final picker = ImagePicker();
bool _imgTaken = false;
ui.Image image;
Future<void> _takePicture() async {
final imageFile = await picker.getImage(
source: ImageSource.camera,
);
if (imageFile == null) {
return;
}
final imageByte = await imageFile.readAsBytes();
image = await decodeImageFromList(imageByte);
setState(() {
_imgTaken = true;
});
}
Future<void> _getImageFromGallery() async {
final imageFile = await picker.getImage(
source: ImageSource.gallery,
);
if (imageFile == null) {
return;
}
final imageByte = await imageFile.readAsBytes();
image = await decodeImageFromList(imageByte);
setState(() {
_imgTaken = true;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
padding: EdgeInsets.all(10),
child: Column(
children: [
_imgTaken
? FittedBox(
child: SizedBox(
width: image.width.toDouble(),
height: image.height.toDouble(),
child: CustomPaint(
painter: OriginalPainter(image),
),
),
)
: Container(
width: 300,
height: 500,
alignment: Alignment.center,
decoration: BoxDecoration(
border: Border.all(width: 1, color: Colors.grey),
),
child: Text(
'画像が選択されていません',
textAlign: TextAlign.center,
),
),
Row(
children: [
Expanded(
child: FlatButton.icon(
icon: Icon(Icons.photo_camera),
label: Text('カメラ'),
textColor: Theme.of(context).primaryColor,
onPressed: _takePicture,
),
),
Expanded(
child: FlatButton.icon(
icon: Icon(Icons.photo_library),
label: Text('ギャラリー'),
textColor: Theme.of(context).primaryColor,
onPressed: _getImageFromGallery,
),
),
],
),
],
),
),
);
}
}
class OriginalPainter extends CustomPainter {
final ui.Image image;
OriginalPainter(this.image);
@override
void paint(ui.Canvas canvas, Size size) {
final paint = Paint();
if (image != null) {
canvas.drawImage(image, Offset(0, 0), paint);
}
paint.color = Colors.red;
canvas.drawCircle(Offset(size.width / 2, size.height / 2), 50, paint);
}
@override
bool shouldRepaint(covariant OriginalPainter oldDelegate) => false;
}
image_pickerで画像を取ってくる
final picker = ImagePicker();
bool _imgTaken = false;
ui.Image image;
Future<void> _takePicture() async {
final imageFile = await picker.getImage(
source: ImageSource.camera,
);
if (imageFile == null) {
return;
}
final imageByte = await imageFile.readAsBytes();
image = await decodeImageFromList(imageByte);
setState(() {
_imgTaken = true;
});
}
Future<void> _getImageFromGallery() async {
final imageFile = await picker.getImage(
source: ImageSource.gallery,
);
if (imageFile == null) {
return;
}
final imageByte = await imageFile.readAsBytes();
image = await decodeImageFromList(imageByte);
setState(() {
_imgTaken = true;
});
}
image_pickerって非常に便利ですよね。ソースを指定すればカメラ、ギャラリーどちらからでも画像を取ってこれます。
ただし、image_pickerで取ってくる画像の型はFile
になります。
そのため、CustomPaint
で使うui.Image
に変換しないといけません。
final Uint8List imageByte = await imageFile.readAsBytes();
image = await decodeImageFromList(imageByte);
一度、Uint8List
に変えてからデコードする形でui.Image
に変換します。
また、画像が選択されたかどうかを伝えるためにsetState()
で_imgTaken
をtrueにしています。
UIでの描画部分
_imgTaken
? FittedBox(
child: SizedBox(
width: image.width.toDouble(),
height: image.height.toDouble(),
child: CustomPaint(
painter: OriginalPainter(image),
),
),
)
: Container(
width: 300,
height: 500,
alignment: Alignment.center,
decoration: BoxDecoration(
border: Border.all(width: 1, color: Colors.grey),
),
child: Text(
'画像が選択されていません',
textAlign: TextAlign.center,
),
),
表示部分では_imgTaken
で条件分岐させています。
画像表示部分であるCustomPaint自体はサイズを持たないので、Container
なり、SizedBox
なりでサイズを与えてあげる必要があります。ui.Image
であれば、すぐにwidthとheightを取ってこれるので楽ですね。
CustomPainter
class OriginalPainter extends CustomPainter {
final ui.Image image;
OriginalPainter(this.image);
@override
void paint(ui.Canvas canvas, Size size) {
final paint = Paint();
if (image != null) {
canvas.drawImage(image, Offset(0, 0), paint);
}
paint.color = Colors.red;
canvas.drawCircle(Offset(size.width / 2, size.height / 2), 50, paint);
}
@override
bool shouldRepaint(covariant OriginalPainter oldDelegate) => false;
}
画像の描画はこのようになります。意外とシンプルです。
image_pickerで取ってきて変換したui.Image
をcanvas.drawImage
で表示することができます。
内部のOffset
で画像の表示位置を変更することができます。(0,0)で指定すると画面左上が始点になります。
さらに今回はcanvas.drawCircle
で画像の中心に円も描いております。
また、shouldRepaint
は画像の再描画が必要な場合はtrueにする必要があります。
お絵描きアプリなんかだと、再描画が必要になりそうですね。
デモ
おわりに
Flutterで色々できることが増えてくると非常に面白いですよね!
https://www.youtube.com/watch?v=yyHhloFMNNA&t
こんな感じでCustomPaintを使ったお絵描きアプリみたいなデモもあるので、今後はそれにも挑戦してみたいと思います!