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');
});
Related
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.
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"),
),
)
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 .
I'm trying to draw a rectangle at the bottom only the Rect object Rect.fromLTRB is not drawing.
I do not know if I'm interpreting the Rect object in the wrong way or I'm writing the drawRect object erroneously.
Could you help me draw a rectangle in the bottom?
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(home: new HomePage()));
}
class HomePage extends StatefulWidget {
#override
HomePageState createState() => new HomePageState();
}
class HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Stack(
children: <Widget>[
new Positioned(
bottom: 0.0,
left: 0.0,
right: 0.0,
top: 0.0,
child: new CustomPaint(
painter: new Sky(),
)
),
]
)
);
}
}
class Sky extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
canvas.drawRect(
new Rect.fromLTRB(
0.0, 100.0, 0.0, 0.0
),
new Paint()..color = new Color(0xFF0099FF),
);
}
#override
bool shouldRepaint(Sky oldDelegate) {
return false;
}
}
Your left and right is the same (0.0) so it draws an empty rect. Also the coordinates start on top, so bottom should be > top; Try this
new Rect.fromLTRB(
0.0, 0.0, 20.0, 100.0
)
Follows the code in which the rectangle is in the bottom of screen:
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
void main() {
runApp(new MaterialApp(home: new HomePage()));
}
class HomePage extends StatefulWidget {
#override
HomePageState createState() => new HomePageState();
}
class HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
final ui.Size logicalSize = MediaQuery.of(context).size;
final double _width = logicalSize.width;
final double _height = logicalSize.height;
double _rectHeight = 50.0;
return new Scaffold(
body: new Stack(
children: <Widget>[
new Positioned(
bottom: 0.0,
left: 0.0,
top: _height - _rectHeight,
right: 0.0,
child: new CustomPaint(
painter: new Sky(_width, _rectHeight),
child: new Text('$_width'),
)
),
]
)
);
}
}
class Sky extends CustomPainter {
final double _width;
final double _rectHeight;
Sky(this._width, this._rectHeight);
#override
void paint(Canvas canvas, Size size) {
canvas.drawRect(
new Rect.fromLTRB(
0.0, 0.0, this._width, _rectHeight
),
new Paint()..color = new Color(0xFF0099FF),
);
}
#override
bool shouldRepaint(Sky oldDelegate) {
return false;
}
}
I need to create a custom control which allows a user to drag a pointer within a bounded rectangle. Very like this joystick control here :https://github.com/zerokol/JoystickView
I have managed to cobble something together using a CustomPainter to draw the control point and a GestureDetector to track where the user drags the pointer on the view. However, I can't get this to capture the panning input. I can't get it to capture any input at all. I don't know if what I am doing is the best approach. I could be on the totally wrong track. Here is the code.
import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(new TouchTest());
}
class TouchTest extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Touch Test',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new Scaffold(
appBar: new AppBar(
title: const Text('Test'),
),
body: new Container(
decoration: new BoxDecoration(
color: Colors.white,
border: new Border.all(
color: Colors.black,
width: 2.0,
),
),
child: new Center(
child: new TouchControl()
),
),
)
);
}
}
class TouchControl extends StatefulWidget {
final double xPos;
final double yPos;
final ValueChanged<Offset> onChanged;
const TouchControl({Key key,
this.onChanged,
this.xPos:0.0,
this.yPos:0.0}) : super(key: key);
#override
TouchControlState createState() => new TouchControlState();
}
/**
* Draws a circle at supplied position.
*
*/
class TouchControlState extends State<TouchControl> {
double xPos = 0.0;
double yPos = 0.0;
GestureDetector _gestureDetector;
TouchControl() {
_gestureDetector = new GestureDetector(
onPanStart:_handlePanStart,
onPanEnd: _handlePanEnd,
onPanUpdate: _handlePanUpdate);
}
void onChanged(Offset offset) {
setState(() {
widget.onChanged(offset);
xPos = offset.dx;
yPos = offset.dy;
});
}
#override
bool hitTestSelf(Offset position) => true;
#override
void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
if (event is PointerDownEvent ) {
// ??
}
}
void _handlePanStart(DragStartDetails details) {
onChanged(details.globalPosition);
}
void _handlePanEnd(DragEndDetails details) {
// TODO
}
void _handlePanUpdate(DragUpdateDetails details) {
onChanged(details.globalPosition);
}
#override
Widget build(BuildContext context) {
return new Center(
child: new CustomPaint(
size: new Size(xPos, yPos),
painter: new TouchControlPainter(xPos, yPos),
),
);
}
}
class TouchControlPainter extends CustomPainter {
static const markerRadius = 10.0;
final double xPos;
final double yPos;
TouchControlPainter(this.xPos, this.yPos);
#override
void paint(Canvas canvas, Size size) {
final paint = new Paint()
..color = Colors.blue[400]
..style = PaintingStyle.fill;
canvas.drawCircle(new Offset(xPos, yPos), markerRadius, paint);
}
#override
bool shouldRepaint(TouchControlPainter old) => xPos != old.xPos && yPos !=old.yPos;
}
Your code isn't using the GestureDetector anywhere.
You should use it to wrap the CustomPaint the build() function of TouchControlState.
import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(new TouchTest());
}
class TouchTest extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Touch Test',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new Scaffold(
appBar: new AppBar(
title: const Text('Test'),
),
body: new Container(
decoration: new BoxDecoration(
color: Colors.white,
border: new Border.all(
color: Colors.black,
width: 2.0,
),
),
child: new Center(
child: new TouchControl()
),
),
)
);
}
}
class TouchControl extends StatefulWidget {
final double xPos;
final double yPos;
final ValueChanged<Offset> onChanged;
const TouchControl({Key key,
this.onChanged,
this.xPos:0.0,
this.yPos:0.0}) : super(key: key);
#override
TouchControlState createState() => new TouchControlState();
}
/**
* Draws a circle at supplied position.
*
*/
class TouchControlState extends State<TouchControl> {
double xPos = 0.0;
double yPos = 0.0;
GlobalKey _painterKey = new GlobalKey();
void onChanged(Offset offset) {
final RenderBox referenceBox = context.findRenderObject();
Offset position = referenceBox.globalToLocal(offset);
if (widget.onChanged != null)
widget.onChanged(position);
setState(() {
xPos = position.dx;
yPos = position.dy;
});
}
#override
bool hitTestSelf(Offset position) => true;
#override
void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
if (event is PointerDownEvent ) {
// ??
}
}
void _handlePanStart(DragStartDetails details) {
onChanged(details.globalPosition);
}
void _handlePanEnd(DragEndDetails details) {
print('end');
// TODO
}
void _handlePanUpdate(DragUpdateDetails details) {
onChanged(details.globalPosition);
}
#override
Widget build(BuildContext context) {
return new ConstrainedBox(
constraints: new BoxConstraints.expand(),
child: new GestureDetector(
behavior: HitTestBehavior.opaque,
onPanStart:_handlePanStart,
onPanEnd: _handlePanEnd,
onPanUpdate: _handlePanUpdate,
child: new CustomPaint(
size: new Size(xPos, yPos),
painter: new TouchControlPainter(xPos, yPos),
),
),
);
}
}
class TouchControlPainter extends CustomPainter {
static const markerRadius = 10.0;
final double xPos;
final double yPos;
TouchControlPainter(this.xPos, this.yPos);
#override
void paint(Canvas canvas, Size size) {
final paint = new Paint()
..color = Colors.blue[400]
..style = PaintingStyle.fill;
canvas.drawCircle(new Offset(xPos, yPos), markerRadius, paint);
}
#override
bool shouldRepaint(TouchControlPainter old) => xPos != old.xPos && yPos !=old.yPos;
}