Flutter resize circle_wave widget - flutter
Here we have a very awesome implemented widget to have this circular wave
now in this code i can't resize width and height of this widget. before posting problem i try to resolve that, but i can't
GitHub Link: demo_circle_wave
import 'dart:math' as math;
import 'dart:ui';
import 'package:flutter/material.dart';
import 'common.dart';
class DemoCircleWave extends StatefulWidget {
const DemoCircleWave({
Key key,
}) : super(key: key);
#override
_DemoCircleWaveState createState() => _DemoCircleWaveState();
}
class _DemoCircleWaveState extends State<DemoCircleWave>
with TickerProviderStateMixin {
static const colors = <Color>[
Color(0xFFFF2964),
Color(0xFF32FF3A),
Color(0xFF4255FF)
];
AnimationController controller;
AnimationController addPointController;
Animation<double> addPointAnimation;
int _counter = 0;
#override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
upperBound: 2,
duration: const Duration(seconds: 10),
)..repeat();
addPointController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
addPointAnimation =
addPointController.drive(CurveTween(curve: Curves.ease));
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
void _incrementCounter() {
setState(() {
_counter++;
addPointController.forward(from: 0);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Circle wave')),
backgroundColor: Colors.black,
body: Stack(
children: [
for (int i = 0; i < 3; i++)
Positioned.fill(
child: TweenAnimationBuilder(
tween: Tween<double>(begin: 0, end: 1),
duration: const Duration(milliseconds: 500),
curve: Curves.easeIn,
builder: (_, double opacity, __) {
return CustomPaint(
painter: CircleWavePainter(
controller,
addPointAnimation,
i,
colors[i].withOpacity(opacity),
_counter,
),
);
},
),
),
CounterText(counter: _counter),
],
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
class CircleWavePainter extends CustomPainter {
CircleWavePainter(
this.animation,
this.addAnimation,
this.index,
this.color,
this.count,
) : super(repaint: animation);
final Animation<double> animation;
final Animation<double> addAnimation;
final int index;
final Color color;
final int count;
static const halfPi = math.pi / 2;
static const twoPi = math.pi * 2;
final n = 7;
#override
void paint(Canvas canvas, Size size) {
final t = animation.value;
final halfWidth = size.width / 2;
final halfHeight = size.height / 2;
final q = index * halfPi;
List<Offset> computeOffsets(int length) {
final offsets = <Offset>[];
for (var i = 0; i < length; i++) {
final th = i * twoPi / length;
double os = map(math.cos(th - twoPi * t), -1, 1, 0, 1);
os = 0.125 * math.pow(os, 2.75);
final r = 165 * (1 + os * math.cos(n * th + 1.5 * twoPi * t + q));
offsets.add(Offset(
r * math.sin(th) + halfWidth, -r * math.cos(th) + halfHeight));
}
return offsets;
}
final offsets = computeOffsets(count);
if (count > 1 && addAnimation.value < 1) {
final t = addAnimation.value;
final oldOffsets = computeOffsets(count - 1);
for (var i = 0; i < count - 1; i++) {
offsets[i] = Offset.lerp(oldOffsets[i], offsets[i], t);
}
offsets[count - 1] = Offset.lerp(
oldOffsets[count - 2],
offsets[count - 1],
t,
);
}
final path = Path()..addPolygon(offsets, true);
canvas.drawPath(
path,
Paint()
..blendMode = BlendMode.lighten
..color = color
..strokeWidth = 8
..style = PaintingStyle.stroke,
);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
Related
How to animate a line with multiple offset points using CustomPainter in Flutter?
I want to create a line that animates to multiple offset points until the full line is painted out, using CustomPainter in Flutter. I have almost achieved this effect, by using an animation object to tween to each new point, and an index to track the line progress. Then, in CustomPainter, I paint 2 lines. One to line animates to the new position, and a second which draws the existing path based off the index. However, there is a small UI error as the GIF shows, where the corners 'fill out' after a new point is added. Note, a I tried using a TweenSequence borrowing the idea mentioned in this recent video but couldn't get it to work. FlutterForward, Youtube Video - around 14:40 import 'package:flutter/material.dart'; class LinePainterAnimation extends StatefulWidget { const LinePainterAnimation({Key? key}) : super(key: key); #override State<LinePainterAnimation> createState() => _LinePainterAnimationState(); } class _LinePainterAnimationState extends State<LinePainterAnimation> with SingleTickerProviderStateMixin { late final AnimationController _controller; final List<Offset> _offsets = [ const Offset(50, 300), const Offset(150, 100), const Offset(300, 300), const Offset(200, 300), ]; int _index = 0; Offset _begin = const Offset(0, 0); Offset _end = const Offset(0, 0); #override void initState() { _begin = _offsets[0]; _end = _offsets[1]; _controller = AnimationController( duration: const Duration(seconds: 1), vsync: this) ..addStatusListener((status) { if (status == AnimationStatus.completed) { _index++; if (_index < _offsets.length - 1) { _begin = _offsets[_index]; _end = _offsets[_index + 1]; _controller.reset(); _controller.forward(); setState(() {}); } } }); super.initState(); } #override Widget build(BuildContext context) { Animation<Offset> animation = Tween<Offset>(begin: _begin, end: _end).animate(_controller); return Scaffold( body: AnimatedBuilder( animation: _controller, builder: (context, child) => CustomPaint( painter: LinePainter( startOffset: _begin, endOffset: animation.value, offsets: _offsets, index: _index, ), child: Container(), ), ), floatingActionButton: FloatingActionButton( onPressed: () { _controller.reset(); _controller.forward(); _begin = _offsets[0]; _end = _offsets[1]; _index = 0; setState(() {}); }, child: const Text('Play'), ), ); } } class LinePainter extends CustomPainter { final Offset startOffset; final Offset endOffset; final List<Offset> offsets; final int index; LinePainter({ required this.startOffset, required this.endOffset, required this.offsets, required this.index, }); #override void paint(Canvas canvas, Size size) { var paint = Paint() ..color = Colors.red ..strokeWidth = 20 ..strokeCap = StrokeCap.butt ..style = PaintingStyle.stroke; var pathExisting = Path(); pathExisting.moveTo(offsets[0].dx, offsets[0].dy); for (int i = 0; i < index + 1; i++) { pathExisting.lineTo(offsets[i].dx, offsets[i].dy); } var pathNew = Path(); pathNew.moveTo(startOffset.dx, startOffset.dy); pathNew.lineTo(endOffset.dx, endOffset.dy); canvas.drawPath(pathNew, paint); canvas.drawPath(pathExisting, paint); } #override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; } }
OK - so I finally came up with a solution after a bit more research, which is perfect for this use case and animating complex paths - [PathMetrics][1] So to get this working the basic steps are 1) define any path 2) calculate & extract this path using PathMetrics 3) then animate this path over any duration, based on the 0.0 to 1.0 value produced by the animation controller, and voilĂ it works like magic! Note, the references I found to get this working: [Moving along a curved path in flutter][2] & [Medium article][3] Updated code pasted below if this is helpful to anyone. [![Solution][4]] import 'dart:ui'; import 'package:flutter/material.dart'; class LinePainterAnimation extends StatefulWidget { const LinePainterAnimation({Key? key}) : super(key: key); #override State<LinePainterAnimation> createState() => _LinePainterAnimationState(); } class _LinePainterAnimationState extends State<LinePainterAnimation> with SingleTickerProviderStateMixin { late AnimationController _controller; #override void initState() { super.initState(); _controller = AnimationController(duration: const Duration(seconds: 1), vsync: this); _controller.forward(); } #override void dispose() { _controller.dispose(); super.dispose(); } #override Widget build(BuildContext context) { return Scaffold( body: AnimatedBuilder( animation: _controller, builder: (context, child) => CustomPaint( painter: LinePainter(_controller.value), child: Container(), ), ), floatingActionButton: FloatingActionButton( onPressed: () { _controller.reset(); _controller.forward(); }, child: const Text('Play'), ), ); } } class LinePainter extends CustomPainter { final double percent; LinePainter(this.percent); #override void paint(Canvas canvas, Size size) { var paint = Paint() ..color = Colors.red ..strokeWidth = 20 ..strokeCap = StrokeCap.butt ..style = PaintingStyle.stroke; var path = getPath(); PathMetrics pathMetrics = path.computeMetrics(); PathMetric pathMetric = pathMetrics.elementAt(0); Path extracted = pathMetric.extractPath(0.0, pathMetric.length * percent); canvas.drawPath(extracted, paint); } #override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; } } Path getPath() { return Path() ..lineTo(50, 300) ..lineTo(150, 100) ..lineTo(300, 300) ..lineTo(200, 300); } [1]: https://api.flutter.dev/flutter/dart-ui/PathMetrics-class.html [2]: https://stackoverflow.com/questions/60203515/moving-along-a-curved-path-in-flutter [3]: https://medium.com/flutter-community/playing-with-paths-in-flutter-97198ba046c8 [4]: https://i.stack.imgur.com/ayoHn.gif
Move a circular widget horizontally while changing the size Flutter
I just started flutter development and wanted to create an animation where it looks like a a circle is rotating in 3 dimension and when in center its full size but when going left or right the radius of the circle decreases. What i have achieved so far is that i am able to move my widget horizontally till the edge of screen by using AnimatedWidget and also achieve resizing of widget by using ScaleTransition and AnimationController but i am facing some issue with resizing with calculations and time. Below is the code for what i have done so far. Would really help if someone could help me figure this out class MyStatefulWidget extends StatefulWidget { const MyStatefulWidget({super.key}); #override State<MyStatefulWidget> createState() => _MyStatefulWidgetState(); } class _MyStatefulWidgetState extends State<MyStatefulWidget> with TickerProviderStateMixin { Timer? timer; late AnimationController _controller; late Animation<double> _animation; double fromLeft = 0, fromTop = 0; double toLeft = 0, toTop = 0; bool initCalled = false; double childHeight = 0; bool moveFromToEnd = true; #override void didChangeDependencies() { super.didChangeDependencies(); if (!initCalled) { var screenWidth = MediaQuery.of(context).size.width; var screenHeight = MediaQuery.of(context).size.height; childHeight = MediaQuery.of(context).size.width/4; var halfChildWidth = (childHeight); fromLeft = (screenWidth - halfChildWidth) / 2; fromTop = (screenHeight - childHeight) / 2; toLeft = (screenWidth - halfChildWidth) / 2; toTop = (screenHeight - childHeight) / 2; int time = 3000; print('Screen width: $screenWidth'); print('Child width: $childHeight'); double distance = screenWidth - (fromLeft + halfChildWidth); double speed = (distance * time); double test = (distance/time)*2; print('time: $time distance: $distance speed: $speed test: $test'); _controller = AnimationController( duration: Duration(milliseconds: time), vsync: this, lowerBound: 0.25, value: 2, upperBound: 1) ..repeat(reverse: true); _animation = CurvedAnimation( parent: _controller, curve: Curves.linear, ); timer = Timer.periodic(Duration(milliseconds: 1), (Timer t) { setState(() { if (moveFromToEnd) { fromLeft = fromLeft + test; toLeft = toLeft - test; if (fromLeft >= screenWidth - childHeight) { moveFromToEnd = false; } } if (!moveFromToEnd) { fromLeft = fromLeft - test; toLeft = toLeft + test; if (fromLeft <= 0) { moveFromToEnd = true; } } }); }); initCalled = true; } } #override void dispose() { _controller.dispose(); timer?.cancel(); super.dispose(); } #override Widget build(BuildContext context) { return Scaffold( body: Center( child: Stack( children: [ AnimatedPositioned( left: fromLeft, top: fromTop, duration: const Duration(milliseconds: 1), child: SizedBox( height: childHeight, width: childHeight, child: ScaleTransition( scale: _animation, child: Image.network( btcUrl, fit: BoxFit.fitWidth, ), ), ), ), ], ), ), ); } }
Flutter - AnimatedScale not animating when widget update
I have a rating widget with 5 stars. I can give a rating by dragging. I want to animate the star with the star rating so that the scale gets bigger and the star without the star rating gets smaller again. Therefore, I made sure that the star with the star rating is created using the AnimatedScale. However, as you can see in the attached gif, the size of the star changes but it is not animated. I gave the Duration value of the AnimatedScale property to 2 seconds, but it is being changed as soon as it is graded. How can the stars grow and shrink smoothly? Here is my code import 'package:flutter/material.dart'; typedef void RatingChangeCallback(double rating); class SmoothStarRating extends StatelessWidget { final int starCount; final double rating; final RatingChangeCallback onRatingChanged; final Color? color; final Color? borderColor; final double size; final double spacing; SmoothStarRating({ this.starCount = 5, this.spacing = 0.0, this.rating = 0.0, required this.onRatingChanged, this.color, this.borderColor, this.size = 25, }); Widget getIcon(int starIndex) { if (starIndex >= rating) { return Icon(Icons.star_border, color: borderColor, size: size); } else if (starIndex > rating - 0.5 && starIndex < rating) { return AnimatedScale( duration: Duration(seconds: 2), scale: 1.1, child: Icon(Icons.star_half, color: color, size: size), ); } else { return AnimatedScale( duration: Duration(seconds: 2), scale: 1.1, child: Icon(Icons.star, color: color, size: size), ); } } Widget buildStar(BuildContext context, int starIndex) { return GestureDetector( onTap: () { onRatingChanged(starIndex + 1.0); }, onHorizontalDragUpdate: (dragDetails) { RenderBox box = context.findRenderObject() as RenderBox; var _pos = box.globalToLocal(dragDetails.globalPosition); var newRating = _pos.dx / size; if (newRating > starCount) newRating = starCount.toDouble(); if (newRating < 0) newRating = 0.0; onRatingChanged(newRating); }, child: getIcon(starIndex), ); } #override Widget build(BuildContext context) { return Wrap( alignment: WrapAlignment.start, spacing: spacing, children: List.generate(starCount, (starIndex) => buildStar(context, starIndex)), ); } } + ADDITIONAL I tried implementing it using animation_controller, but the results are the same. import 'package:flutter/material.dart'; typedef void RatingChangeCallback(double rating); class SmoothStarRating extends StatefulWidget { final int starCount; final double rating; final RatingChangeCallback onRatingChanged; final Color? color; final Color? borderColor; final double size; final double spacing; SmoothStarRating({ this.starCount = 5, this.spacing = 0.0, this.rating = 0.0, required this.onRatingChanged, this.color, this.borderColor, this.size = 25, }); #override State<SmoothStarRating> createState() => _SmoothStarRatingState(); } class _SmoothStarRatingState extends State<SmoothStarRating> with TickerProviderStateMixin { late final AnimationController _controller = AnimationController( duration: const Duration(milliseconds: 500), vsync: this, )..forward(); late final Animation<double> _animation = Tween(begin: 1.0, end: 1.1).animate(_controller); #override void dispose() { _controller.dispose(); super.dispose(); } Widget getIcon(int starIndex) { if (starIndex >= widget.rating) { return Icon(Icons.star_border, color: widget.borderColor, size: widget.size); } else if (starIndex > widget.rating - 0.5 && starIndex < widget.rating) { return Icon(Icons.star_half, color: widget.color, size: widget.size); } else { return ScaleTransition( scale: _animation, child: Icon(Icons.star, color: widget.color, size: widget.size), ); } } Widget buildStar(BuildContext context, int starIndex) { return GestureDetector( onTap: () { widget.onRatingChanged(starIndex + 1.0); }, onHorizontalDragUpdate: (dragDetails) { RenderBox box = context.findRenderObject() as RenderBox; var _pos = box.globalToLocal(dragDetails.globalPosition); var newRating = _pos.dx / widget.size; if (newRating > widget.starCount) newRating = widget.starCount.toDouble(); if (newRating < 0) newRating = 0.0; widget.onRatingChanged(newRating); }, child: getIcon(starIndex), ); } #override Widget build(BuildContext context) { return Wrap( alignment: WrapAlignment.start, spacing: widget.spacing, children: List.generate(widget.starCount, (starIndex) => buildStar(context, starIndex)), ); } }
Animation affect other animations in Flutter CustomPainter
So, i can't achieve two independents animation in customPainter. The animation of the invaders is affecting the animation of the spaceship.The animation of the invaders consist of a TweenSequence, that produces an effect of move and stop just like space invaders original game. But the movement of the spaceShip should be continuos. The problem is that the space ship also is having the movement pattern of the invaders, eventhough the animation has a different Tween and a different controller. The curious thing is that if I comment all the lines that draw the invaders( so the animation value is not being used) the spaceship has a continuos movement(the movement expected) Just to clarify: The animation controller and animation of the spaceship are: controllerAsync and animationAsync of SpaceInvadersMainView.dart Here is the code: SpaceInvadersMainView.dart import 'package:croco/SpaceInvaders/InvadersPositionManager.dart'; import '../SpaceInvaders/InvadersConstruction.dart'; import 'package:flutter/material.dart'; import '../SpaceInvaders/InvadersAnimationManager.dart'; class SpaceInvadersMainView extends StatelessWidget { const SpaceInvadersMainView({Key? key}) : super(key: key); #override Widget build(BuildContext context) { return MaterialApp( title: 'Space Invaders', theme: ThemeData( scaffoldBackgroundColor: Colors.black, fontFamily: 'Segoe UI', primarySwatch: Colors.lightBlue, ), home: SpaceCanvas(), ); } } class SpaceCanvas extends StatefulWidget { SpaceCanvas({Key? key}) : super(key: key); #override State<SpaceCanvas> createState() => _SpaceCanvasState(); } class _SpaceCanvasState extends State<SpaceCanvas> with TickerProviderStateMixin { late AnimationController controller; double animationStateValue = 0; String keyLabel = ""; late AnimationController controllerAsync; late double animationStateValueAsync; late var animation = TweenSequence<double>(InvadersAnimationManager.getTweenRightMovement(-600, 500, 24)).animate(controller); late var animationAsync = Tween<double>(begin: -700, end: 700).animate(controllerAsync); #override void initState() { super.initState(); controllerAsync = AnimationController( vsync: this, duration: const Duration(milliseconds: 10000) ); animationAsync .addListener(() { setState(() { print(animationAsync.value); }); }); controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 10000 ) ); animation .addListener(() { setState(() { }); }); controller.forward(); controllerAsync.forward(); } #override void dispose() { controller.dispose(); super.dispose(); } #override Widget build(BuildContext context) { return RawKeyboardListener( autofocus: true, focusNode: FocusNode(), onKey: (event) { keyLabel = event.logicalKey.keyLabel; }, child: Scaffold( body: Center( child: Column( children: <Widget>[ Align( alignment: Alignment.topCenter, child: Container( padding: const EdgeInsets.only(top: 20), child: const Text( 'Welcome to space Invaders!', style: TextStyle( fontWeight: FontWeight.w300, fontSize: 25, color: Colors.white70 ) ), ), ), Stack( children: <Widget>[ CustomPaint( painter : InvadersPaint(animation, animationAsync) ), ], ) ]) ) ), ); } } class InvadersPaint extends CustomPainter { Paint basicSquarePaint = Paint(); Path originPath = Path(); late Animation animation; late double animationStateValue; late Animation animationAsync; InvadersPaint(this.animation, this.animationAsync) : super(repaint: animation); #override void paint(Canvas canvas, Size size) { Path testPath = Path(); Paint paint = Paint(); paint.color = Colors.greenAccent; testPath = InvadersConstruction.drawInvader(animationAsync.value, 670, "spaceShip"); canvas.drawPath(testPath, paint); InvadersPositionManager.placeInvadersOnLine(canvas, animation.value + 6 + 2, 100, "squid", 58, Colors.purpleAccent); InvadersPositionManager.placeInvadersOnLine(canvas, animation.value + 2, 144, "crab", 58, Colors.lightBlueAccent); InvadersPositionManager.placeInvadersOnLine(canvas, animation.value + 2, 188, "crab", 58, Colors.lightBlueAccent); InvadersPositionManager.placeInvadersOnLine(canvas, animation.value, 232, "octopus", 58, Colors.yellowAccent); InvadersPositionManager.placeInvadersOnLine(canvas, animation.value, 276, "octopus", 58, Colors.yellowAccent); } #override bool shouldRepaint(CustomPainter oldDelegate) { return false; } } InvadersContruction.dart import 'package:flutter/material.dart'; class InvadersConstruction { static Path drawInvader(double originX, double originY, String typeOfInvader) { var transitionPath = Path(); var pathList = <List<int>>[]; const List<List<int>> octopusMatrix = [ [1,1,0,0,0,0,0,0,0,0,1,1], [0,0,1,1,0,1,1,0,1,1,0,0], [0,0,0,1,1,0,0,1,1,0,0,0], [1,1,1,1,1,1,1,1,1,1,1,1], [1,1,1,0,0,1,1,0,0,1,1,1], [1,1,1,1,1,1,1,1,1,1,1,1], [0,1,1,1,1,1,1,1,1,1,1,0], [0,0,0,0,1,1,1,1,0,0,0,0], ]; const List<List<int>> crabMatrix = [ [0,0,0,1,1,0,1,1,0,0,0], [1,0,1,0,0,0,0,0,1,0,1], [1,0,1,1,1,1,1,1,1,0,1], [1,1,1,1,1,1,1,1,1,1,1], [0,1,1,0,1,1,1,0,1,1,0], [0,0,1,1,1,1,1,1,1,0,0], [0,0,0,1,0,0,0,1,0,0,0], [0,0,1,0,0,0,0,0,1,0,0] ]; const List<List<int>> squidMatrix = [ [1,0,1,0,0,1,0,1], [0,1,0,1,1,0,1,0], [0,0,1,0,0,1,0,0], [1,1,1,1,1,1,1,1], [1,1,0,1,1,0,1,1], [0,1,1,1,1,1,1,0], [0,0,1,1,1,1,0,0], [0,0,0,1,1,0,0,0] ]; const List<List<int>> spaceShipMatrix = [ [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], [0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0], [0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0], [0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0], ]; void drawSquare( int row, int isItSquare, int sqrPosition ) { double y; double x; Path secondaryPath = Path(); y = originY + row * -4; if (sqrPosition == 0) { x = originX; } else { x = sqrPosition * 4 + originX; } if (isItSquare == 1) { secondaryPath.moveTo(x, y); secondaryPath.relativeLineTo(4, 0); secondaryPath.relativeLineTo(0, -4); secondaryPath.relativeLineTo(-4, 0); secondaryPath.close(); transitionPath = Path.combine(PathOperation.union, transitionPath, secondaryPath); transitionPath.fillType = PathFillType.evenOdd; } } int counterRow = -1; int counterCol = -1; if(typeOfInvader == "octopus") { pathList = octopusMatrix; } else if(typeOfInvader == "crab") { pathList = crabMatrix; } else if(typeOfInvader == "squid") { pathList = squidMatrix; } else if(typeOfInvader == "spaceShip") { pathList = spaceShipMatrix; } for ( var row in pathList) { counterRow++; // print("This is the counter of the row: $counterRow"); for (var sqr in row) { counterCol++; // print("This is the counter of the square position: $counterCol"); drawSquare(counterRow, sqr, counterCol); if (counterCol == row.length -1) { counterCol = -1; } } } return transitionPath; } } InvadersAnimationManager.dart import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'dart:html'; class InvadersAnimationManager { //This method maps the right direction movement(-x -> x) of invaders in a List of Tween Sequence Items, that it's //going to be procesed by the TweenSequence constructor '../Views/SpaceInvadersMainView.dart' line: 37 static List<TweenSequenceItem<double>> getTweenRightMovement (double originX, double finalX, double pixelsToRight) { double steps = 16; var tweenSequence = <TweenSequenceItem<double>>[]; for(int i = 1; i <= steps; i++ ) { var subCounter = i - 1; var tweenItem = TweenSequenceItem<double>( tween: Tween<double>(begin: originX + pixelsToRight * subCounter, end: originX + pixelsToRight * i), weight: 100/ steps ); tweenSequence.add(tweenItem); } return tweenSequence; } } InvadersPositionManager.dart import 'package:flutter/material.dart'; import 'InvadersConstruction.dart'; class InvadersPositionManager { static void placeInvadersOnLine(Canvas canvas, double originX, double originY, String invader, double inBetweenPixels, Color color) { int _rowElements = 12; Path _internalInvader; Paint _paint = Paint(); double _invaderLength = 48; _paint.color = color; for(int i = 1; i <= 12 + 1; i++) { if(i == 1) { _internalInvader = InvadersConstruction.drawInvader(originX, originY, invader); } else { _internalInvader = InvadersConstruction.drawInvader(originX + _invaderLength + i * inBetweenPixels, originY, invader); canvas.drawPath(_internalInvader, _paint); } } } }
How to animate line draw in custom painter in Flutter?
I want to animate the line drawing in custom painter canvas. So far what I can do is create two circles at two points and then create a line between those two points. But I don't know how to animate the line as if it is going from one point to the other. I have tried something but I can't make it work. Please check the code and suggest me if you have any idea. import 'package:flutter/material.dart'; import 'dart:math' as math; class ProgressMonitorAnimation extends StatefulWidget { #override State<StatefulWidget> createState() => _ProgressMonitorAnimationState(); } class _ProgressMonitorAnimationState extends State<ProgressMonitorAnimation> with TickerProviderStateMixin { double _progress = 0.0; Animation<double> animation; #override void initState() { var controller = AnimationController(duration: Duration(milliseconds: 3000), vsync: this); animation = Tween(begin: 1.0, end: 0.0).animate(controller)..addListener(() { setState(() { _progress = animation.value; }); }); controller.forward(); super.initState(); } #override Widget build(BuildContext context) { return Transform( alignment: Alignment.center, transform: Matrix4.rotationX(math.pi), child: CustomPaint( foregroundPainter: ProgressPainter(_progress), ), ); } } class ProgressPainter extends CustomPainter { double _progress; ProgressPainter(this._progress); #override void paint(Canvas canvas, Size size) { final Paint circlePainter = Paint()..color = Colors.green; final Paint linePainter = Paint()..color = Colors.black..strokeWidth = 4..strokeCap = StrokeCap.round; canvas.drawCircle(Offset(0.0, 30.0 * 3), 10.0, circlePainter); canvas.drawCircle(Offset(15.0 * 2, 80.0 * 3), 10.0, circlePainter); canvas.drawLine(Offset(0.0 / (_progress * 10), 30.0 * 3), Offset((30.0 * 3) + (15.0) / (_progress * 15) * 2, (80.0 * 3) / (_progress * 15)), linePainter); } #override bool shouldRepaint(CustomPainter oldDelegate) => true; }
You can do as follows using flutter Custom Painter. import 'dart:async'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; class AnimatedPathPainter extends CustomPainter { final Animation<double> _animation; AnimatedPathPainter(this._animation) : super(repaint: _animation); Path _createAnyPath(Size size) { return Path() // ..moveTo(size.height / 4, size.height / 4) // ..lineTo(size.height, size.width / 2) // ..lineTo(size.height / 2, size.width) ..quadraticBezierTo(size.height / 2, 100, size.width, size.height); } #override void paint(Canvas canvas, Size size) { final animationPercent = this._animation.value; print("Painting + ${animationPercent} - ${size}"); final path = createAnimatedPath(_createAnyPath(size), animationPercent); final Paint paint = Paint(); paint.color = Colors.black; paint.style = PaintingStyle.stroke; paint.strokeWidth = 4.0; canvas.drawPath(path, paint); } #override bool shouldRepaint(CustomPainter oldDelegate) => true; } Path createAnimatedPath( Path originalPath, double animationPercent, ) { // ComputeMetrics can only be iterated once! final totalLength = originalPath .computeMetrics() .fold(0.0, (double prev, PathMetric metric) => prev + metric.length); final currentLength = totalLength * animationPercent; return extractPathUntilLength(originalPath, currentLength); } Path extractPathUntilLength( Path originalPath, double length, ) { var currentLength = 0.0; final path = new Path(); var metricsIterator = originalPath.computeMetrics().iterator; while (metricsIterator.moveNext()) { var metric = metricsIterator.current; var nextLength = currentLength + metric.length; final isLastSegment = nextLength > length; if (isLastSegment) { final remainingLength = length - currentLength; final pathSegment = metric.extractPath(0.0, remainingLength); path.addPath(pathSegment, Offset.zero); break; } else { // There might be a more efficient way of extracting an entire path final pathSegment = metric.extractPath(0.0, metric.length); path.addPath(pathSegment, Offset.zero); } currentLength = nextLength; } return path; } class AnimatedPathDemo extends StatefulWidget { #override _AnimatedPathDemoState createState() => _AnimatedPathDemoState(); } class _AnimatedPathDemoState extends State<AnimatedPathDemo> with SingleTickerProviderStateMixin { AnimationController _controller; Completer<GoogleMapController> _controllerMap = Completer(); static final CameraPosition _initialPosition = CameraPosition( // target: LatLng(12.947437, 77.685345), target: LatLng(7.8731, 80.7718), zoom: 8, ); void _startAnimation() { _controller.stop(); _controller.reset(); _controller.repeat( period: Duration(seconds: 2), ); } #override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar(title: const Text('Animated Paint')), body: Stack( children: <Widget>[ GoogleMap( // rotateGesturesEnabled: false, mapType: MapType.normal, compassEnabled: false, initialCameraPosition: _initialPosition, // polylines: _polylines, // markers: _markers, onMapCreated: (GoogleMapController controller) { // controller.setMapStyle(Utils.mapStyles); _controllerMap.complete(controller); }, ), SizedBox( height: 300, width: 300, child: new CustomPaint( painter: new AnimatedPathPainter(_controller), ), ), ], ), floatingActionButton: new FloatingActionButton( onPressed: _startAnimation, child: new Icon(Icons.play_arrow), ), ); } #override void initState() { super.initState(); _controller = new AnimationController( vsync: this, ); } #override void dispose() { _controller.dispose(); super.dispose(); } }