Edit-: A relevant question also would be How to dispose multiple controllers dynamically?
I have a custom painter in which each object needs to be animated individually. And after certain time data gets added to the list and I paint it and animate it.
Here what it looks like,
The is the model class for animation
class Wave {
AnimationController animationController;
Animation<double> animation;
bool isDisposed;
Wave({
required this.animationController,
required this.animation,
this.isDisposed = false,
});
}
@override
void paint(Canvas canvas, Size size) {
for (var i = 0; i < waveData.length; i++) {
addAnimatedWaves(i); // <------- a callback to add animation model to list
animatedWaves[i].animationController.forward();
canvas.drawLine(...);
if (animatedWaves[i].animationController.status ==
AnimationStatus.dismissed &&
!animatedWaves[i].isDisposed) { //<------ trying to dispose but it won't get executed
animatedWaves[i].animationController.dispose();
animatedWaves[i].animation.removeListener(() {});
animatedWaves[i].isDisposed = true;
}
}
}
This is the above mentioned call back,
void addAnimatedWaves(int i) {
if (_waves.isEmpty) {
var controller = AnimationController(
vsync: this, duration: const Duration(milliseconds: 500));
_waves.add(Wave(
animationController: controller,
animation: Tween(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: controller, curve: Curves.easeIn),
),
));
} else {
if (_waves.length > i) {
var controller = AnimationController(
vsync: this, duration: const Duration(milliseconds: 500));
_waves.add(Wave(
animationController: controller,
animation: Tween(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: controller, curve: Curves.easeIn),
),
));
}
_waves[i].animation.addListener(() {
if (mounted) setState(() {});
});
}
}
disposing
@override
void dispose() {
for (var wave in _waves) { // it always calls every dispose method because every
// controller is active
if (!wave.isDisposed) {
wave.animationController.dispose();
wave.animation.removeListener(() {});
}
}
super.dispose();
}
what I want is to immediately dispose a controller when the animation is completed. And optimise the for loop at the end so that it doesn't have length of whole list.
So can anyone suggest what should do or Is there any better method to animate list of objects which needs to individually animated?
Ok so I have found this solution if anyone have better solution then I'm happy to change the accepted answer.
First, @pskink suggested that we should not use controllers inside paints so removed them and added inside addAnimatedWave()
@override
void paint(Canvas canvas, Size size) {
if (animatedWaves.isEmpty) {
addAnimatedWaves(i);//---->for first time
} else {
if (animatedWaves.isNotEmpty && i >= animatedWaves.length - 1) {
addAnimatedWaves(i);
}
}
canvas.drawLine(...);
}
the above condition checks whether the index contains any value or not. So if it contains then it is not required and it skips it.
void _addAnimatedWave(int i) {
if (_waves.isEmpty) {
_addWave(); // <--- it just creates new controller and adds to _waves
//you can refer it from question.
} else {
if (_waves.length > i) {
_addWave();
}
_waves[i].animationController.forward();
_waves[i].animation.addListener(() {
if (mounted) setState(() {});
});
_waves[i].animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_waves[i].animationController.dispose();
_waves[i].animation.removeListener(() {});
_waves[i].isDisposed = true;
}
});
}
}
Here we are adding a listener to every AnimationController
but it will active till an animation completes for the controller and we are immediately disposing it.
so for my case a single controller will be active
for 300ms
.
Before it was adding 1000+
controller every 2-3 seconds
now it only adds a controller for every wave bar.
Now for the remaining controller which are still active, while widget is removed from widget tree.
@override
void dispose() {
for (var i = _waves.length - 1; i >= 0; i--) {
if (_waves[i].isDisposed) {
break;
}
_waves[i].animationController.dispose();
_waves[i].animation.removeListener(() {});
}
super.dispose();
}
For my case disposing a controller was linear
so if we reverse
the list and reach to the first
controller which was disposed, after that every controller would be disposed so we don't require to go through whole list so we can just break
that loop.
And with this optimisation, I notice only max 3
controllers were required to be disposed with for-loop.