Flutter: Scratch card - dart

Here is the example of a scratch card in react-native, I want to implement in Flutter, But I am not getting any way to do this. I tried with blendMode but it is not working, and even there is no clear functionality are given in CustomPaint in the flutter. https://github.com/aleksik/react-scratchcard.
Future<Null> main() async {
runApp(MaterialApp(home: Scaffold(body: new Roller())));
}
class Roller extends StatefulWidget {
#override
_ScratchCardState createState() => new _ScratchCardState();
}
class _ScratchCardState extends State<Roller> {
ui.Image _image;
String _urlImage = 'assets/BedRoom.png';
List<Offset> _points = <Offset>[];
#override
void initState() {
// TODO: implement initState
super.initState();
super.initState();
load(_urlImage).then((j) {
_image = j;
print('image:${_image}');
});
}
Future<ui.Image> load(String asset) async {
ByteData data = await rootBundle.load(asset);
ui.Codec codec = await ui.instantiateImageCodec(
data.buffer.asUint8List(),
);
ui.FrameInfo fi = await codec.getNextFrame();
return fi.image;
}
void _onPanStart(DragStartDetails dt) {
print('drag start ');
setState(() {
});
}
Offset _localPosition;
void _onPanUpdate(DragUpdateDetails details) {
setState(() {
RenderBox object = context.findRenderObject();
_localPosition = object.globalToLocal(details.globalPosition);
_points = new List.from(_points)..add(_localPosition);
});
}
_onPanEnd(DragEndDetails dt) {
_points.add(null);
}
#override
void dispose() {
super.dispose();
}
Widget _scale(BuildContext context) {
return GestureDetector(
onPanStart: _onPanStart,
onPanUpdate: _onPanUpdate,
onPanEnd: _onPanEnd,
onDoubleTap: () {
setState(() {
_points.clear();
});
},
child: Container(
child: CustomPaint(
painter: ScratchCard(
imagePath: _image, points: _points,),
),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _scale(context),
);
}
}
class ScratchCard extends CustomPainter {
final ui.Image imagePath;
List<Offset> points;
ScratchCard({Key key, this.imagePath, this.points}) : super();
Paint _paint = Paint();
Rect rect, inputSubrect, outputSubrect;
Path path = new Path();
Paint paint1 = new Paint();
#override
void paint(Canvas canvas, Size size) {
_paint.blendMode = BlendMode.src;
if (imagePath != null)
canvas.drawImage(imagePath, Offset(10.0, 100.0), _paint);
Paint paint = new Paint()
..color = Colors.white
..strokeCap = StrokeCap.round
..strokeWidth = 30.0;
_paint.blendMode = BlendMode.clear;
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null) {
canvas.drawLine(points[i], points[i + 1], paint);
path.reset();
}
}
}
#override
bool shouldRepaint(ScratchCard oldDelegate) {
return true;
}
}

I've created a library for scratch cards in Flutter which supports both - color and image scratchable overlays. You can get it from https://pub.dev/packages/scratcher Feedback is more than welcome!
Example:
Scratcher(
brushSize: 30,
threshold: 50,
color: Colors.red,
onChange: (value) { print("Scratch progress: $value%"); },
onThreshold: () { print("Threshold reached, you won!"); },
child: Container(
height: 300,
width: 300,
child: Text("Hidden text"),
),
)

Related

Countdown in text flutter

We need to create a 5 minute countdown text object in Flutter, once the countdown reaches zero then we need to launch a new screen. here is the current code:
Container(
margin: EdgeInsets.only(left: 0.0,top: 60.0, bottom: 50.0, right:0.0),
child: Text(
'5:00', style: new TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100.0 ))
,
),
I usually don't post just sample code, but I was in the mood of coding a bit in flutter. So here's my quick suggestion on how you can implement this:
class _MyHomePageState extends State<MyHomePage> {
TickViewModel _viewModel;
int _seconds;
StreamSubscription<int> _tickSubscription;
#override
void initState() {
super.initState();
_viewModel = TickViewModel();
_tickSubscription = _viewModel._tickStream.listen((newTime) {
if (newTime == 0) {
//TODO: DO THE SCREEN CHANGE HERE;
}
setState(() {
_seconds = newTime;
});
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("My Flutter App"),
backgroundColor: Colors.red,
),
body: Text(_computeTime()));
}
String _computeTime() {
if (_seconds == null) {
return "-";
}
String minutes = "${_seconds ~/ 60}".padLeft(2, "0");
String seconds = "${_seconds % 60}".padLeft(2, "0");
return "$minutes:$seconds";
}
#override
void dispose() {
//We cleanup the stuffs here
_tickSubscription.cancel();
_viewModel.destroy();
super.dispose();
}
}
class TickViewModel {
static const int _fiveMinutes = 5 * 60;
StreamController<int> _ticker;
Stream<int> get _tickStream => _ticker.stream;
Timer _timer;
final int seconds;
int _internalSeconds;
TickViewModel({this.seconds = _fiveMinutes}) : _internalSeconds = seconds {
_ticker = StreamController(onListen: () {
//start timer only when we have a subscriber
_startTimer();
}, onCancel: () {
_timer.cancel();
});
}
void _startTimer() {
//start timer to tick every 1 second
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
_internalSeconds -= 1;
if (_internalSeconds < 0) {
_timer.cancel();
} else {
_ticker.add(_internalSeconds);
}
});
}
void destroy() {
_ticker.close();
_timer.cancel();
}
}

Flutter: show only special part of an image

This is how I show a picture:
return Scaffold(
body: Center(
child: Image.asset('man_face.jpg'),
),
);
And this the result: https://imgur.com/a/CPrQgvS
I want to show only special part of the picture. For example a rectangle with x: 250 and y: 360 and width: 200 and height: 150.
Which it should be something like this: https://imgur.com/a/p41y3nx
How can I do that?
you might want to look into this library. brendan-duncan/image. a great tool to manipulate images in flutter.
Here is a code I came up with
it accept an image url and a rect and display only the rect part of the image
import 'dart:async';
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
class PartImagePainter extends StatefulWidget {
String imageUrl;
Rect rect;
PartImagePainter(this.imageUrl, this.rect);
#override
_PartImagePainterState createState() => _PartImagePainterState();
}
class _PartImagePainterState extends State<PartImagePainter> {
Future<ui.Image> getImage(String path) async {
Completer<ImageInfo> completer = Completer();
var img = new NetworkImage(path);
img
.resolve(ImageConfiguration())
.addListener(ImageStreamListener((ImageInfo info, bool _) {
completer.complete(info);
}));
ImageInfo imageInfo = await completer.future;
return imageInfo.image;
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getImage(widget.imageUrl),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// If the Future is complete, display the preview.
return paintImage(snapshot.data);
} else {
// Otherwise, display a loading indicator.
return Center(child: CircularProgressIndicator());
}
});
}
paintImage(image) {
return CustomPaint(
painter: ImagePainter(image, widget.rect),
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: widget.rect.height,
),
);
}
}
class ImagePainter extends CustomPainter {
ui.Image resImage;
Rect rectCrop;
ImagePainter(this.resImage, this.rectCrop);
#override
void paint(Canvas canvas, Size size) {
if (resImage == null) {
return;
}
final Rect rect = Offset.zero & size;
final Size imageSize =
Size(resImage.width.toDouble(), resImage.height.toDouble());
FittedSizes sizes = applyBoxFit(BoxFit.fitWidth, imageSize, size);
Rect inputSubRect = rectCrop;
final Rect outputSubRect =
Alignment.center.inscribe(sizes.destination, rect);
final paint = Paint()
..color = Colors.white
..style = PaintingStyle.fill
..strokeWidth = 4;
canvas.drawRect(rect, paint);
canvas.drawImageRect(resImage, inputSubRect, outputSubRect, Paint());
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
try using Alignment property and set fit to none.
Image.network(
"your image.png",
// move on the X axis to right 10% of the image and 0% on the Y Axis
alignment: const Alignment(0.1,0),
// set fit to none
fit: BoxFit.none,
// use scale to zoom out of the image
scale: 2,
)
One way suggested by the official doc of Flutter, is to:
To display a subpart of an image, consider using a CustomPainter and Canvas.drawImageRect.
ref: https://api.flutter.dev/flutter/painting/DecorationImage/alignment.html
Thus here is my full code. Use PartImage to show what you want.
class PartImage extends StatefulWidget {
const PartImage({
Key key,
#required this.imageProvider,
#required this.transform,
}) : assert(imageProvider != null),
super(key: key);
final ImageProvider imageProvider;
final Matrix4 transform;
#override
_PartImageState createState() => _PartImageState();
}
class _PartImageState extends State<PartImage> {
ImageStream _imageStream;
ImageInfo _imageInfo;
#override
void didChangeDependencies() {
super.didChangeDependencies();
_getImage();
}
#override
void didUpdateWidget(PartImage oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.imageProvider != oldWidget.imageProvider) _getImage();
}
void _getImage() {
final oldImageStream = _imageStream;
_imageStream = widget.imageProvider.resolve(createLocalImageConfiguration(context));
if (_imageStream.key != oldImageStream?.key) {
final listener = ImageStreamListener(_updateImage);
oldImageStream?.removeListener(listener);
_imageStream.addListener(listener);
}
}
void _updateImage(ImageInfo imageInfo, bool synchronousCall) {
setState(() {
_imageInfo = imageInfo;
});
}
#override
void dispose() {
_imageStream.removeListener(ImageStreamListener(_updateImage));
super.dispose();
}
#override
Widget build(BuildContext context) {
return RawPartImage(
image: _imageInfo?.image, // this is a dart:ui Image object
scale: _imageInfo?.scale ?? 1.0,
transform: widget.transform,
);
}
}
/// ref: [RawImage]
class RawPartImage extends StatelessWidget {
final ui.Image image;
final double scale;
final Matrix4 transform;
const RawPartImage({Key key, this.image, this.scale, this.transform}) : super(key: key);
#override
Widget build(BuildContext context) {
return CustomPaint(
painter: _RawPartImagePainter(
image: image,
scale: scale,
transform: transform,
),
);
}
}
class _RawPartImagePainter extends CustomPainter {
final ui.Image image;
final double scale;
final Matrix4 transform;
final painter = Paint();
_RawPartImagePainter({this.image, this.scale, this.transform});
#override
void paint(Canvas canvas, Size size) {
if (image == null) {
return;
}
final transformInv = Matrix4.inverted(transform);
final dst = Offset.zero & size;
final src = Rect.fromPoints(
transformOffset(transformInv, dst.topLeft),
transformOffset(transformInv, dst.bottomRight),
);
// print('src=$src dst=$dst');
canvas.drawImageRect(image, src, dst, painter);
}
#override
bool shouldRepaint(covariant _RawPartImagePainter oldDelegate) {
return oldDelegate.image != image || //
oldDelegate.scale != scale ||
oldDelegate.transform != transform;
}
}
Offset transformOffset(Matrix4 transform, Offset offset) {
Vector4 vecOut = transform * Vector4(offset.dx, offset.dy, 0, 1);
return Offset(vecOut.x, vecOut.y);
}
By the way, if you are interested in knowing what happens behind drawImageRect:
Have a search https://github.com/flutter/engine/search?q=drawImageRect
This seems the C++ code that drawImageRect (i.e. _drawImageRect actually calls: https://github.com/flutter/engine/blob/6bc70e4a114ff4c01b60c77bae754bace5683f6d/lib/ui/painting/canvas.cc#L330
It calls canvas_->drawImageRect. What is canvas_? From the header file we see it is of type SkCanvas* canvas_;.
Then we go into the world of Skia (not Flutter or Dart anymore). https://skia.org/user/api/skcanvas_overview for an overview of SkCanvas. And https://api.skia.org/classSkCanvas.html#a680ab85c3c7b5eab23b853b97f914334 for the actual SkCanvas.drawImageRect documentation.

how to display animations one after the other in flutter

I have mapped an array to a class and I am displaying the animation accordingly but the animation works for the entire thing at once. I want to have a delay between the subsequent animations.
The screenshot of the game is here. The stars should appear with animation one after the other The code that i am using is :
for (var i = 0; i < 4; i++) {
myScore > (10*i) ? stars.add("true") : stars.add("false");
}
Widget _buildItem(int index, String text) {
return new MyButton(
key: new ValueKey<int>(index),
text: text,
keys: keys++,
onPress: () {
}
);
}
class MyButton extends StatefulWidget {
MyButton(
{Key key,
this.text,
this.keys,
this.onPress})
: super(key: key);
final String text;
final VoidCallback onPress;
int keys;
#override
_MyButtonState createState() => new _MyButtonState();
}
class _MyButtonState extends State<MyButton> with TickerProviderStateMixin {
AnimationController controller;
Animation<double> animation;
String _displayText;
initState() {
super.initState();
print("_MyButtonState.initState: ${widget.text}");
_displayText = widget.text;
controller = new AnimationController(
duration: new Duration(milliseconds: 600), vsync: this);
animation = new CurvedAnimation(parent: controller, curve: Curves.easeIn)
..addStatusListener((state) {
// print("$state:${animation.value}");
if (state == AnimationStatus.dismissed) {
print('dismissed');
if (widget.text != null) {
setState(() => _displayText = widget.text);
controller.forward();
}
}
});
}
#override
void didUpdateWidget(MyButton oldWidget) {
super.didUpdateWidget(oldWidget);
// sleep(const Duration(microseconds: 10)); //I tried adding a delay here but instead it delays the entire thing.
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
Size media = MediaQuery.of(context).size;
double ht = media.height;
double wd = media.width;
widget.keys++;
print("_MyButtonState.build");
return new Shake(
animation: animation,
child: new GestureDetector(
child: new Container(
child: new FlatButton(
onPressed: () => widget.onPress(),
color: Colors.transparent,
child: new Icon(
_displayText == "true" ? Icons.star : Icons.star_border,
key: new Key("${widget.keys}"),
size: ht > wd ? ht * 0.05 : wd * 0.05,
color: Colors.black,
)
),)
) );
}
}
Use Staggered Animations in Flutter for series of operations , rather than all at once .Follow the link Staggered Animations to build Staggered Animations using Flutter .

The method nextPage() and previousPage() in PageController doesn't work sometimes

Here is my code:
I response key event in Android MainActivity, and use BasicMessageChannel to post key message:
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "scroll";
private static final String KEY_LEFT = "keyLeft";
private static final String KEY_RIGHT = "keyRight";
private BasicMessageChannel messageChannel;
private FlutterView flutterView;
private String[] getArgsFromIntent(Intent intent) {
// Before adding more entries to this list, consider that arbitrary
// Android applications can generate intents with extra data and that
// there are many security-sensitive args in the binary.
ArrayList<String> args = new ArrayList<String>();
if (intent.getBooleanExtra("trace-startup", false)) {
args.add("--trace-startup");
}
if (intent.getBooleanExtra("start-paused", false)) {
args.add("--start-paused");
}
if (intent.getBooleanExtra("enable-dart-profiling", false)) {
args.add("--enable-dart-profiling");
}
if (!args.isEmpty()) {
String[] argsArray = new String[args.size()];
return args.toArray(argsArray);
}
return null;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
flutterView = new FlutterView(this);
String[] args = getArgsFromIntent(getIntent());
FlutterMain.ensureInitializationComplete(getApplicationContext(), args);
flutterView.runFromBundle(FlutterMain.findAppBundlePath(getApplicationContext()), null);
messageChannel = new BasicMessageChannel<>(flutterView, CHANNEL, StringCodec.INSTANCE);
}
#Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
Log.d(TAG, "dispatchKeyEvent: ACTION_DOWN keyCode = " + event.getKeyCode());
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_DPAD_LEFT:
messageChannel.send(KEY_LEFT);
return true;
case KeyEvent.KEYCODE_DPAD_RIGHT:
messageChannel.send(KEY_RIGHT);
return true;
default:
break;
}
}
return super.dispatchKeyEvent(event);
}
#Override
protected void onDestroy() {
if (flutterView != null) {
flutterView.destroy();
}
super.onDestroy();
}
#Override
protected void onPause() {
super.onPause();
flutterView.onPause();
}
#Override
protected void onPostResume() {
super.onPostResume();
flutterView.onPostResume();
}
}
When Flutter receives the message, I call nextPage() and previousPage() to scroll PageView, it doesn't work. But I find if I call nextPage() and previousPage() in onTap() method of the GestureDetector, it works:
class _Page {
_Page({
this.imagePath,
});
final String imagePath;
}
final List<_Page> _allPages = <_Page>[
new _Page(imagePath: 'images/1.jpg'),
new _Page(imagePath: 'images/2.jpg'),
new _Page(imagePath: 'images/3.jpg'),
new _Page(imagePath: 'images/4.jpg'),
new _Page(imagePath: 'images/5.jpg'),
new _Page(imagePath: 'images/6.jpg'),
];
class ScrollablePageDemo extends StatefulWidget {
#override
_ScrollablePageDemoState createState() => new _ScrollablePageDemoState();
}
class _ScrollablePageDemoState extends State<ScrollablePageDemo>
with SingleTickerProviderStateMixin {
PageController _controller;
static const String _channel = 'scroll';
static const String _emptyMessage = '';
static const String KEY_LEFT = "keyLeft";
static const String KEY_RIGHT = "keyRight";
static const BasicMessageChannel<String> platform =
const BasicMessageChannel<String>(_channel, const StringCodec());
#override
void initState() {
super.initState();
_controller = new PageController(
initialPage: 0, keepPage: true, viewportFraction: 1.0);
platform.setMessageHandler(changePage);
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
Future<String> changePage(String message) async {
setState(() {
print(message);
if (message == KEY_RIGHT) {
//here doesn't work
_controller.nextPage(duration: kTabScrollDuration, curve: Curves.ease);
print("tab right: page = " + _controller.page.toString());
} else if (message == KEY_LEFT) {
//here doesn't work
_controller.previousPage(duration: kTabScrollDuration, curve: Curves.ease);
print("tab left: page = " + _controller.page.toString());
}
});
return _emptyMessage;
}
PageView buildPageView() {
return new PageView(
controller: _controller,
children: _allPages.map((_Page page) {
return new Container(
key: new ObjectKey(page.imagePath),
padding: const EdgeInsets.all(12.0),
child: new Card(
child: new Image.asset(page.imagePath, fit: BoxFit.fill)));
}).toList(),
);
}
#override
Widget build(BuildContext context) {
return new GestureDetector(
onTap: () {
print('Listen PageView');
//here works
_controller.nextPage(duration: kTabScrollDuration, curve: Curves.ease);
},
child: new Scaffold(
appBar: new AppBar(
title: new Text('PageView'),
),
body: buildPageView()));
}
}
void main() {
runApp(new MaterialApp(
title: 'Flutter Study',
home: new ScrollablePageDemo(),
));
}
The problem is with setState method inside changePage. The setState invokes build method whenever the state set. Thus the page gets built again whenever setState called. You can just remove the setState from changePage method.
example:
Future<String> changePage(String message) async {
print(message);
if (message == KEY_RIGHT) {
_controller.nextPage(duration: kTabScrollDuration, curve: Curves.ease);
print("tab right: page = " + _controller.page.toString());
} else if (message == KEY_LEFT) {
_controller.previousPage(duration: kTabScrollDuration, curve: Curves.ease);
print("tab left: page = " + _controller.page.toString());
}
return _emptyMessage;
}
Hope that helped!

How to touch paint a canvas?

Flutter newbie here. I'm currently trying to build a simple touch-drawing app with Flutter, but cannot work out how to trigger a canvas re-draw.
What I have is this:
I have a CustomPaint widget which contains a GestureDetector child. The CustomPaint's painter is getting a message whenever a touch event occurs, and stores the touch coordinates to draw a path on re-paint. Problem is, the paint method is never called.
This is the code I have so far:
import 'package:flutter/material.dart';
class WriteScreen extends StatefulWidget {
#override
_WriteScreenState createState() => new _WriteScreenState();
}
class KanjiPainter extends CustomPainter {
Color strokeColor;
var strokes = new List<List<Offset>>();
KanjiPainter( this.strokeColor );
void startStroke(Offset position) {
print("startStroke");
strokes.add([position]);
}
void appendStroke(Offset position) {
print("appendStroke");
var stroke = strokes.last;
stroke.add(position);
}
void endStroke() {
}
#override
void paint(Canvas canvas, Size size) {
print("paint!");
var rect = Offset.zero & size;
Paint fillPaint = new Paint();
fillPaint.color = Colors.yellow[100];
fillPaint.style = PaintingStyle.fill;
canvas.drawRect(
rect,
fillPaint
);
Paint strokePaint = new Paint();
strokePaint.color = Colors.black;
strokePaint.style = PaintingStyle.stroke;
for (var stroke in strokes) {
Path strokePath = new Path();
// Iterator strokeIt = stroke.iterator..moveNext();
// Offset start = strokeIt.current;
// strokePath.moveTo(start.dx, start.dy);
// while (strokeIt.moveNext()) {
// Offset off = strokeIt.current;
// strokePath.addP
// }
strokePath.addPolygon(stroke, false);
canvas.drawPath(strokePath, strokePaint);
}
}
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
class _WriteScreenState extends State<WriteScreen> {
GestureDetector touch;
CustomPaint canvas;
KanjiPainter kanjiPainter;
void panStart(DragStartDetails details) {
print(details.globalPosition);
kanjiPainter.startStroke(details.globalPosition);
}
void panUpdate(DragUpdateDetails details) {
print(details.globalPosition);
kanjiPainter.appendStroke(details.globalPosition);
}
void panEnd(DragEndDetails details) {
kanjiPainter.endStroke();
}
#override
Widget build(BuildContext context) {
touch = new GestureDetector(
onPanStart: panStart,
onPanUpdate: panUpdate,
onPanEnd: panEnd,
);
kanjiPainter = new KanjiPainter( const Color.fromRGBO(255, 255, 255, 1.0) );
canvas = new CustomPaint(
painter: kanjiPainter,
child: touch,
// child: new Text("Custom Painter"),
// size: const Size.square(100.0),
);
Container container = new Container(
padding: new EdgeInsets.all(20.0),
child: new ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: new Card(
elevation: 10.0,
child: canvas,
)
)
);
return new Scaffold(
appBar: new AppBar(
title: new Text("Draw!")
),
backgroundColor: const Color.fromRGBO(200, 200, 200, 1.0),
body: container,
);
}
}
According to CustomPainter docs you must notify paint widget whenever repainting is needed
The most efficient way to trigger a repaint is to either extend this class and supply a repaint argument to the constructor of the CustomPainter, where that object notifies its listeners when it is time to repaint, or to extend Listenable (e.g. via ChangeNotifier) and implement CustomPainter, so that the object itself provides the notifications directly. In either case, the CustomPaint widget or RenderCustomPaint render object will listen to the Listenable and repaint whenever the animation ticks, avoiding both the build and layout phases of the pipeline.
E.g. KanjiPainter should extend ChangeNotifier and implement CustomPainter. And when you change strokes, invoke notifyListeners
And also build function always creates new KanjiPainter, this will remove all old data. You can init painter in initState once.
Working example:
class WriteScreen extends StatefulWidget {
#override
_WriteScreenState createState() => _WriteScreenState();
}
class KanjiPainter extends ChangeNotifier implements CustomPainter {
Color strokeColor;
var strokes = <List<Offset>>[];
KanjiPainter(this.strokeColor);
bool hitTest(Offset position) => true;
void startStroke(Offset position) {
print("startStroke");
strokes.add([position]);
notifyListeners();
}
void appendStroke(Offset position) {
print("appendStroke");
var stroke = strokes.last;
stroke.add(position);
notifyListeners();
}
void endStroke() {
notifyListeners();
}
#override
void paint(Canvas canvas, Size size) {
print("paint!");
var rect = Offset.zero & size;
Paint fillPaint = Paint();
fillPaint.color = Colors.yellow[100]!;
fillPaint.style = PaintingStyle.fill;
canvas.drawRect(rect, fillPaint);
Paint strokePaint = new Paint();
strokePaint.color = Colors.black;
strokePaint.style = PaintingStyle.stroke;
for (var stroke in strokes) {
Path strokePath = new Path();
// Iterator strokeIt = stroke.iterator..moveNext();
// Offset start = strokeIt.current;
// strokePath.moveTo(start.dx, start.dy);
// while (strokeIt.moveNext()) {
// Offset off = strokeIt.current;
// strokePath.addP
// }
strokePath.addPolygon(stroke, false);
canvas.drawPath(strokePath, strokePaint);
}
}
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
#override
// TODO: implement semanticsBuilder
SemanticsBuilderCallback? get semanticsBuilder => null;
#override
bool shouldRebuildSemantics(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRebuildSemantics
return true;
}
}
class _WriteScreenState extends State<WriteScreen> {
late GestureDetector touch;
late CustomPaint canvas;
late KanjiPainter kanjiPainter;
void panStart(DragStartDetails details) {
print(details.globalPosition);
kanjiPainter.startStroke(details.globalPosition);
}
void panUpdate(DragUpdateDetails details) {
print(details.globalPosition);
kanjiPainter.appendStroke(details.globalPosition);
}
void panEnd(DragEndDetails details) {
kanjiPainter.endStroke();
}
#override
void initState() {
super.initState();
kanjiPainter = new KanjiPainter(const Color.fromRGBO(255, 255, 255, 1.0));
}
#override
Widget build(BuildContext context) {
touch = new GestureDetector(
onPanStart: panStart,
onPanUpdate: panUpdate,
onPanEnd: panEnd,
);
canvas = new CustomPaint(
painter: kanjiPainter,
child: touch,
// child: new Text("Custom Painter"),
// size: const Size.square(100.0),
);
Container container = new Container(
padding: new EdgeInsets.all(20.0),
child: new ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: new Card(
elevation: 10.0,
child: canvas,
)));
return new Scaffold(
appBar: new AppBar(title: new Text("Draw!")),
backgroundColor: const Color.fromRGBO(200, 200, 200, 1.0),
body: container,
);
}
}
this library does it well.
example :
myCanvas.drawRect(Rect.fromLTRB(x * 0.88, y / 2, 25, 25), paint,
onTapDown: (t) {
print('tap');
});

Resources