Animation is slow in flutter - flutter

I am animating line in canvas in flutter.i am using AnimationController to control the animation. When i animate a single line, it is get animated without any lag or performance issue.But when i animate more than 10 lines it gets struck and lagging when rendering the line.In each frame redraw is getting called in order to animate the line .How to overcome this issue.
Code Snippet
class CrossPainter extends CustomPainter {
Paint _paint;
double _fraction;
CrossPainter(this._fraction) {
_paint = Paint()
..color = Colors.blue
..strokeWidth = 10.0
..strokeCap = StrokeCap.round;
}
#override
void paint(Canvas canvas, Size size) {
canvas.clipRect(Rect.fromLTRB(0, 0, _fraction * size.width , size.height));
canvas.drawLine(Offset(0.0, 0.0), Offset(size.width , size.height ), _paint);
canvas.drawLine(Offset(size.width, 0.0), Offset(size.width - size.width, size.height ), _paint);
}
#override
bool shouldRepaint(CrossPainter oldDelegate) {
return oldDelegate._fraction != _fraction;
}
}
typedef FadeBuilder = Widget Function(BuildContext, double);
class _AnimationWrapper extends StatefulWidget {
const _AnimationWrapper({this.builder});
final FadeBuilder builder;
#override
_AnimationWrapperState createState() => _AnimationWrapperState();
}
class _AnimationWrapperState extends State<_AnimationWrapper> with SingleTickerProviderStateMixin {
double opacity = 0.0;
double _fraction = 0.0;
Animation<double> animation;
AnimationController controller;
#override
void initState() {
super.initState();
controller = AnimationController(duration: Duration(milliseconds: 3000), vsync: this);
animation = Tween(begin: 0.0, end: 1.0).animate(controller)
..addListener(() {
setState(() {
_fraction = animation.value;
});
}
);
controller.forward();
}
#override void didUpdateWidget(_AnimationWrapper oldWidget) {
// TODO: implement didUpdateWidget
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) {
return CustomPaint(painter: CrossPainter(_fraction));
}
}
Thanks
Ashwin

If I understand your problem correct - these lags caused by debug mode. There are always small problems with animation in debug.
Try to build release apk and launch it

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

Heart Beat line for splash screen

i want to add heart beat horizontal line for splash screen, it will start animating from one side and go all the way to other if the initial data is still not loaded it will continue animation
i tried below code but its only pulse kinda animation
import 'dart:math';
import 'package:flutter/material.dart';
class SpritePainter extends CustomPainter {
final Animation<double> _animation;
SpritePainter(this._animation) : super(repaint: _animation);
void circle(Canvas canvas, Rect rect, double value) {
double opacity = (1.0 - (value / 4.0)).clamp(0.0, 1.0);
Color color = Color.fromRGBO(0, 117, 194, opacity);
double size = rect.width / 2;
double area = size * size;
double radius = sqrt(area * value / 4);
final Paint paint = Paint()..color = color;
canvas.drawCircle(rect.center, radius, paint);
}
#override
void paint(Canvas canvas, Size size) {
Rect rect = Rect.fromLTRB(0.0, 0.0, size.width, size.height);
for (int wave = 3; wave >= 0; wave--) {
circle(canvas, rect, wave + _animation.value);
}
}
#override
bool shouldRepaint(SpritePainter oldDelegate) {
return true;
}
}
class SpriteDemo extends StatefulWidget {
#override
SpriteDemoState createState() => SpriteDemoState();
}
class SpriteDemoState extends State<SpriteDemo>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
);
//_startAnimation();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
void _startAnimation() {
_controller
..stop()
..reset()
..repeat(period: const Duration(seconds: 1));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Pulse')),
body: CustomPaint(
painter: SpritePainter(_controller),
child: SizedBox(
width: 200.0,
height: 200.0,
),
),
floatingActionButton: FloatingActionButton(
onPressed: _startAnimation,
child: new Icon(Icons.play_arrow),
),
);
}
}
void main() {
runApp(
MaterialApp(
home: SpriteDemo(),
),
);
}
I want it to start(animate) from center left and go all the way to right just like in image

How to find a Flutter widget has been rendered or not after animation completed

I have a scenario to find whether a widget has been rendered or not. I have tested with WidgetsBinding and SchedulerBinding for a custom painter, they got triggered after the widget gets rendered irrespective of animation. If the widget's animation is in progress before animation gets completed the event got triggered. In the below gif image, you can find the line is getting animated before that event triggers. So, is there any way to find out after the animation got completed? Kindly suggest me a way to achieve my requirement. I have attached a sample reference code and gif image.
import 'package:flutter/material.dart';
void main() {
return runApp(Line());
}
class Line extends StatefulWidget {
#override
State<StatefulWidget> createState() => _LineState();
}
class _LineState extends State<Line> with SingleTickerProviderStateMixin {
double _progress = 0.0;
Animation<double>? animation;
late DateTime startTime;
#override
void initState() {
startTime = DateTime.now();
WidgetsBinding.instance?.addPostFrameCallback((_) {
final DateTime endTime = DateTime.now();
final Duration duration = endTime.difference(startTime);
print(
'Duration to render the custom painter: ${duration.inMilliseconds}');
});
var controller = AnimationController(
duration: Duration(milliseconds: 5000), 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 Container(
color: Colors.white,
child: CustomPaint(painter: LinePainter(_progress)));
}
}
class LinePainter extends CustomPainter {
Paint? _paint;
double _progress;
LinePainter(this._progress) {
_paint = Paint()
..color = Colors.teal
..strokeWidth = 8.0;
}
#override
void paint(Canvas canvas, Size size) {
canvas.drawLine(
Offset(0.0, 0.0),
Offset(size.width - size.width * _progress,
size.height - size.height * _progress),
_paint!);
}
#override
bool shouldRepaint(LinePainter oldDelegate) {
return oldDelegate._progress != _progress;
}
}
Thanks in advance.
As I can see you have already added a listener to your animation
animation = Tween(begin: 1.0, end: 0.0).animate(controller)
..addListener(() {
setState(() {
_progress = animation!.value;
});
});
Every time the value change on the animation, this listener is called, so the easy way to obtain your status is to check there the animation controller status
https://api.flutter.dev/flutter/animation/AnimationStatus.html
in your listener, you can just add a check and act according.
animation = Tween(begin: 1.0, end: 0.0).animate(controller)
..addListener(() {
if(controller.status == AnimationStatus.completed) {
// ADD YOUR LOGIC HERE
}
setState(() {
_progress = animation!.value;
});
});

My rectangle Animation does not enlarge from the center but from the first vertex expanding to the right

I am trying to create a square animation from the centrum of the screen that opens up from the center(starting really small and enlarge itself growing from the centrum of the square, unfortunately what is happening with my code is that giving the animation as parameter to the Rectangle in the CustomPaint as Size(transitionTween.value, transitionTween.value) the animation is not growing from the center out but starts from the left/up vertex first, enlarging the rectangle to the right instead from the center. How can I obtain the effect to make my animation starting and enlarging from the center
import 'package:flutter/material.dart';
class PointToCircle extends StatefulWidget {
#override
_PointToCircleState createState() => _PointToCircleState();
}
class _PointToCircleState extends State<PointToCircle>
with TickerProviderStateMixin {
AnimationController _controller;
Animation<double> transitionTween;
// Animation<BorderRadius> borderRadius;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
transitionTween = Tween<double>(
begin: 1.0,
end: 200.0,
).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeIn,
),
);
_controller.forward();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
return Scaffold(
body: new Center(
child: CustomPaint(
painter: OpenPainter(transitionTween),
)));
},
);
}
}
class OpenPainter extends CustomPainter {
Animation<double> transitionTween;
OpenPainter(this.transitionTween);
#override
void paint(Canvas canvas, Size size) {
var paint1 = Paint()
..color = Colors.blue
..style = PaintingStyle.stroke;
canvas.drawRect(
Offset(-50, -100) & Size(transitionTween.value, transitionTween.value),
paint1);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
Found the solution is to add a size:Size(transition.value,transition.value); in the Custom paint, leaving also the dynamic tween inside the Size of the rectangle
but I do not understand the reason behind now is working

Weird performance issue in drawing path using flutter

I'm trying to draw some pointing triangles on CustomPaint and I'm facing performance issues when I change the shape of the triangle. If you execute the current code the refresh rate is fast.
If you change the part Offset(x,y+8), with Offset(x,y+4), the triangle shape changes slightly but the performance drops and the refresh rate is poor.
Does anyone knows why that happens and how can I resolve this issue?
import 'dart:math' as Math;
import 'package:flutter/material.dart';
void main() =>
runApp(
MaterialApp(
title: 'Demo',
home: Scaffold(
body: Shapes()
),
)
);
class Shapes extends StatefulWidget {
#override
_ShapesState createState() => new _ShapesState();
}
class _ShapesState extends State<Shapes> with SingleTickerProviderStateMixin{
Animation<double> animation;
AnimationController animationController;
#override
void initState() {
super.initState();
animationController = AnimationController(
vsync: this,
duration: Duration(seconds: 5),
)..addListener(() => setState(() {}));
animation = Tween<double>(
begin: 50.0,
end: 120.0,
).animate(animationController);
animationController.forward();
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
animationController.repeat();
}
});
}
#override
Widget build(BuildContext context) {
return CustomPaint(painter: ShapesPainter());
}
}
class ShapesPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
final paint = Paint();
paint.strokeWidth = 2;
for (var i = 0; i < 1000; i++) {
Math.Random rnd = new Math.Random();
double x = 5 + rnd.nextInt(300 - 5).toDouble();
double y = 5 + rnd.nextInt(1200 - 5).toDouble();
final Path path = Path();
path.addPolygon([
Offset(x+6,y+8),
Offset(x,y+-8),
Offset(x-6,y+8),
Offset(x,y+8),
], true);
paint.color = Color(0xFF3f6cbf);
paint.style = PaintingStyle.stroke;
canvas.drawPath(path, paint);
paint.color = Color.fromARGB(255, 255, 0, 0);
paint.style = PaintingStyle.fill;
canvas.drawPath(path, paint);
}
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}