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;
}
}
Related
When the bottom child is tapped (the height changes when its tapped), the parent color (blue) is shown while animating.
Is there anyway to prevent the animation?
Should I be thinking about this in a different way?
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Container(color: Colors.blue, child: CustomScrollView(slivers: <Widget>[
SliverList(
delegate: SliverChildListDelegate([
TapBox(color: Colors.red),
TapBox(color: Colors.white),
TapBox(color: Colors.red),
]),
)
],))
);
}
}
class TapBox extends StatefulWidget {
final Color color;
TapBox({ this.color });
#override
State<StatefulWidget> createState() => _TapBoxState();
}
class _TapBoxState extends State<TapBox> {
double height = 500;
onTap() {
setState(() {
if (height == 500) {
height = 250;
} else {
height = 500;
}
});
}
#override
Widget build(BuildContext context) {
return Material(child: InkWell(onTap: onTap, child: LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
return Container(width: constraints.maxWidth, height: height, color: widget.color);
})));
}
}
In iOS, I wrote a somewhat complex custom UIViewController that handles transitioning between unique child controllers; most notably, a special header view at the top of each one. I'm still trying to really wrap my head around end to end architecture in Flutter, and would like some suggestions on how to accomplish this. There are two types of headers - Arc and Profile, and each one goes from an expanded to a collapsed state as the user scrolls. Additionally, navigation between any combination of type and state can have a transition defined.
Here is how it looks when used in a TabBar for example. Transitions are handled gracefully wether nested in Tab/NavigationControllers or not.
This is what I threw together, I hope it helps (click for video):
Note:
It would be better to reduce the amount of animation controllers, ideally to a single controller that controls both the header extent and the arc curvature
There is no animation for the content below the header, but I'm sure you could add that as well.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Anim playground',
theme: ThemeData(
brightness: Brightness.dark,
),
home: AnimatedPageTest(),
);
}
}
class AnimatedPageTest extends StatefulWidget {
#override
_AnimatedPageTestState createState() => _AnimatedPageTestState();
}
class _AnimatedPageTestState extends State<AnimatedPageTest> {
bool _arc = true;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(child: AnimatedPage(
appearance: _arc ? HeaderAppearance.arc : HeaderAppearance.profile,
backgroundImage: _arc ? 'assets/earth.jpg' : 'assets/moon.jpg',
children: List.generate(30, (index) => ListTile(title: Text('index'),)),
),),
persistentFooterButtons: <Widget>[
FlatButton(
child: Text('Switch'),
onPressed: () {
setState(() {
_arc = !_arc;
});
},
)
],
);
}
}
enum HeaderAppearance { arc, profile }
double _getTargetMaxExtent(HeaderAppearance appearance) {
if (appearance == HeaderAppearance.arc) {
return 150.0;
} else {
return 75.0;
}
}
double _getTargetArcAnimationValue(HeaderAppearance appearance) {
if (appearance == HeaderAppearance.arc) {
return 1.0;
} else {
return 0.0;
}
}
class AnimatedPage extends StatefulWidget {
AnimatedPage({Key key, this.appearance, this.backgroundImage, this.children}) : super(key: key);
final HeaderAppearance appearance;
final String backgroundImage;
final List<Widget> children;
#override
_AnimatedPageState createState() => _AnimatedPageState();
}
class _AnimatedPageState extends State<AnimatedPage> with SingleTickerProviderStateMixin {
AnimationController _maxExtentAnimation;
#override
void initState() {
super.initState();
_maxExtentAnimation = AnimationController.unbounded(vsync: this, value: _getTargetMaxExtent(widget.appearance));
}
#override
void didUpdateWidget(AnimatedPage oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.appearance != oldWidget.appearance) {
_maxExtentAnimation.animateTo(
_getTargetMaxExtent(widget.appearance),
duration: Duration(milliseconds: 600),
curve: Curves.easeInOut,
);
}
}
#override
void dispose() {
_maxExtentAnimation.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _maxExtentAnimation,
builder: (context, child) {
return CustomScrollView(
slivers: <Widget>[
SliverPersistentHeader(
pinned: true,
delegate: AnimatedHeaderDelegate(
appearance: widget.appearance,
backgroundImage: widget.backgroundImage,
minExtent: 50.0,
maxExtent: _maxExtentAnimation.value,
),
),
child,
],
);
},
child: SliverList(delegate: SliverChildListDelegate(widget.children)),
);
}
}
class AnimatedHeaderDelegate extends SliverPersistentHeaderDelegate {
AnimatedHeaderDelegate({this.appearance, this.backgroundImage, this.minExtent, this.maxExtent});
final HeaderAppearance appearance;
final String backgroundImage;
#override
final double minExtent;
#override
final double maxExtent;
#override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
final shrinkRelative = shrinkOffset / (maxExtent - minExtent);
return AnimatedHeader(
appearance: appearance,
backgroundImage: backgroundImage,
curvatureMultiplier: 1.0 - shrinkRelative,
);
}
#override
bool shouldRebuild(AnimatedHeaderDelegate oldDelegate) {
return appearance != oldDelegate.appearance ||
minExtent != oldDelegate.minExtent ||
maxExtent != oldDelegate.maxExtent;
}
}
class AnimatedHeader extends StatefulWidget {
AnimatedHeader({Key key, this.appearance, this.backgroundImage, this.curvatureMultiplier}) : super(key: key);
final HeaderAppearance appearance;
final String backgroundImage;
final double curvatureMultiplier;
#override
_AnimatedHeaderState createState() => _AnimatedHeaderState();
}
class _AnimatedHeaderState extends State<AnimatedHeader> with TickerProviderStateMixin {
AnimationController _arcAnimation;
#override
void initState() {
super.initState();
_arcAnimation = AnimationController(
vsync: this,
value: _getTargetArcAnimationValue(widget.appearance),
duration: Duration(milliseconds: 600),
);
}
#override
void didUpdateWidget(AnimatedHeader oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.appearance != oldWidget.appearance) {
_arcAnimation.animateTo(_getTargetArcAnimationValue(widget.appearance));
}
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: CurvedAnimation(parent: _arcAnimation, curve: Curves.linear),
builder: (context, child) {
return ClipPath(
clipper: ArcClipper(
curvature: _arcAnimation.value * widget.curvatureMultiplier,
),
clipBehavior: Clip.antiAlias,
child: child,
);
},
child: Stack(
fit: StackFit.expand,
children: <Widget>[
AnimatedSwitcher(
duration: Duration(milliseconds: 600),
child: Container(
key: ValueKey(widget.backgroundImage),
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(widget.backgroundImage),
fit: BoxFit.cover,
),
),
),
),
Center(
child: Text(
'TITLE',
style: TextStyle(fontSize: 30.0, fontWeight: FontWeight.w500),
),
),
],
),
);
}
}
class ArcClipper extends CustomClipper<Path> {
ArcClipper({this.curvature});
final double curvature;
#override
Path getClip(Size size) {
if (curvature == 0.0) {
return Path()..addRect(Offset.zero & size);
} else {
return Path()
..moveTo(0.0, 0.0)
..lineTo(size.width, 0.0)
..lineTo(size.width, size.height)
..quadraticBezierTo(size.width / 2, size.height - size.height * 0.4 * curvature, 0.0, size.height)
..close();
}
}
#override
bool shouldReclip(ArcClipper oldClipper) {
return curvature != oldClipper.curvature;
}
}
I am using imagecarousel package for displaying images from the network. I want to keep onPressed function for images in the slide.
new ImageCarousel(
<ImageProvider>[
new NetworkImage('http://www.hilversum.ferraridealers.com/siteasset/ferraridealer/54f07ac8c35b6/961/420/selected/0/0/0/54f07ac8c35b6.jpg'),
new NetworkImage('http://auto.ferrari.com/en_EN/wp-content/uploads/sites/5/2017/08/ferrari-portofino-reveal-2017-featured-new.jpg'),
new NetworkImage('http://www.hilversum.ferraridealers.com/siteasset/ferraridealer/54f07ac8c35b6/961/420/selected/0/0/0/54f07ac8c35b6.jpg'),
],
interval: new Duration(seconds: 1),
)
After making some modifications to Image Carousel, I was able to implement click event (other events also possible). Here is the sample code.
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class ImageCarousel extends StatefulWidget {
final List<ImageProvider> imageProviders;
final double height;
final TargetPlatform platform;
final Duration interval;
final TabController tabController;
final BoxFit fit;
// Images will shrink according to the value of [height]
// If you prefer to use the Material or Cupertino style activity indicator set the [platform] parameter
// Set [interval] to let the carousel loop through each photo automatically
// Pinch to zoom will be turned on by default
ImageCarousel(this.imageProviders,
{this.height = 250.0, this.platform, this.interval, this.tabController, this.fit = BoxFit.cover});
#override
State createState() => new _ImageCarouselState();
}
TabController _tabController;
class _ImageCarouselState extends State<ImageCarousel> with SingleTickerProviderStateMixin {
#override
void initState() {
super.initState();
_tabController = widget.tabController ?? new TabController(vsync: this, length: widget.imageProviders.length);
if (widget.interval != null) {
new Timer.periodic(widget.interval, (_) {
_tabController.animateTo(_tabController.index == _tabController.length - 1 ? 0 : ++_tabController.index);
});
}
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new SizedBox(
height: widget.height,
child: new TabBarView(
controller: _tabController,
children: widget.imageProviders.map((ImageProvider provider) {
return new CarouselImageWidget(widget, provider, widget.fit, widget.height);
}).toList(),
),
);
}
}
class CarouselImageWidget extends StatefulWidget {
final ImageCarousel carousel;
final ImageProvider imageProvider;
final BoxFit fit;
final double height;
CarouselImageWidget(this.carousel, this.imageProvider, this.fit, this.height);
#override
State createState() => new _CarouselImageState();
}
class _CarouselImageState extends State<CarouselImageWidget> {
bool _loading = true;
Widget _getIndicator(TargetPlatform platform) {
if (platform == TargetPlatform.iOS) {
return new CupertinoActivityIndicator();
} else {
return new Container(
height: 40.0,
width: 40.0,
child: new CircularProgressIndicator(),
);
}
}
#override
void initState() {
super.initState();
widget.imageProvider.resolve(new ImageConfiguration()).addListener((i, b) {
if (mounted) {
setState(() {
_loading = false;
});
}
});
}
#override
Widget build(BuildContext context) {
return new Container(
height: widget.height,
child: _loading
? _getIndicator(widget.carousel.platform == null ? defaultTargetPlatform : widget.carousel.platform)
: new GestureDetector(
child: new Image(
image: widget.imageProvider,
fit: widget.fit,
),
onTap: () {
int index = int.parse(_tabController.index.toString());
switch(index){
//Implement you case here
case 0:
case 1:
case 2:
default:
print(_tabController.index.toString());
}
},
),
);
}
}
void main(){
runApp(new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("Demo"),
),
body: new ImageCarousel(
<ImageProvider>[
new NetworkImage(
'http://wallpaper-gallery.net/images/images/images-2.jpg'),
new NetworkImage(
'http://wallpaper-gallery.net/images/images/images-10.jpg'),
new NetworkImage(
'http://wallpaper-gallery.net/images/images/images-4.jpg'),
],
interval: new Duration(seconds: 5),
)
),
));
}
Hope it helps..!!
I'm starting out on Flutter and trying to make an animation which rotates while fading in and out continuously. So far rotation works, but I'm having difficulties with the fading effect. The widget will gradually become transparent but right after one rotation, it jumps back into opaque before turning transparent again. I'm trying to fix this but I can't seem to find out how. Using .forward() and .reverse() doesn't work, but it's possible I may have implemented the opaque animation incorrectly.
class AnimatedLoader extends AnimatedWidget {
static final _opacityTween = new Tween<double>(begin: 1.0, end: 0.3);
AnimatedLoader({
Key key,
this.alignment: FractionalOffset.center,
Animation<double> turns,
Animation<double> animation,
this.child,
}) : super(key: key, listenable: turns);
Animation<double> get turns => listenable;
final FractionalOffset alignment;
final Widget child;
#override
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
final double turnsValue = turns.value;
final Matrix4 transform = new Matrix4.rotationZ(turnsValue * math.PI * 2.0);
return new Transform(
alignment: alignment,
transform: transform,
child: new Opacity(
opacity: _opacityTween.evaluate(animation),
child: child,
)
);
}
}
class AppLoader extends StatefulWidget {
AppLoaderState createState() => new AppLoaderState();
}
class AppLoaderState extends State<AppLoader> with TickerProviderStateMixin {
AnimationController _controller;
AnimationController _controllerOp;
Animation<double> animation;
#override initState(){
super.initState();
_controller = new AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
)..repeat();
_controllerOp = new AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
animation = new Tween(begin: 0.0, end: 300.0).animate(_controllerOp);
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controllerOp.reverse();
} else if (status == AnimationStatus.dismissed) {
_controllerOp.forward();
}
});
_controllerOp.forward();
}
#override
Widget build(BuildContext context) {
return new Center (
child: new AnimatedLoader(
turns: _controller,
alignment: FractionalOffset.center,
animation: _controllerOp,
child: new Container(
margin: new EdgeInsets.symmetric(vertical: 10.0),
height: 150.0,
width: 150.0,
child: new FlutterLogo(),
)
),
);
}
Sorry for the big chunk of code, I'm unsure which part I could've made a mistake in.
I think you're on the right track, but you should only use one AnimationController per AnimatedWidget. I fixed some bugs in your code.
import 'package:flutter/material.dart';
import 'dart:math' as math;
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
Widget build(BuildContext context) {
return new Scaffold(
body: new AppLoader(),
);
}
}
class PulsateCurve extends Curve {
#override
double transform(double t) {
if (t == 0 || t == 1)
return 0.3;
return math.sin(t * math.PI) * 0.35 + 0.65;
}
}
class AnimatedLoader extends AnimatedWidget {
static final _opacityTween = new CurveTween(curve: new PulsateCurve());
AnimatedLoader({
Key key,
this.alignment: FractionalOffset.center,
Animation<double> animation,
this.child,
}) : super(key: key, listenable: animation);
final FractionalOffset alignment;
final Widget child;
#override
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
final Matrix4 transform = new Matrix4.rotationZ(animation.value * math.PI * 2.0);
return new Transform(
alignment: alignment,
transform: transform,
child: new Opacity(
opacity: _opacityTween.evaluate(animation),
child: child,
)
);
}
}
class AppLoader extends StatefulWidget {
AppLoaderState createState() => new AppLoaderState();
}
class AppLoaderState extends State<AppLoader> with TickerProviderStateMixin {
AnimationController _controller;
#override initState() {
super.initState();
_controller = new AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
)..repeat();
}
#override
Widget build(BuildContext context) {
return new Center (
child: new AnimatedLoader(
animation: _controller,
alignment: FractionalOffset.center,
child: new Container(
margin: new EdgeInsets.symmetric(vertical: 10.0),
height: 150.0,
width: 150.0,
child: new FlutterLogo(),
)
),
);
}
}
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;
}