AnimatedBuilder not working when trying to avoid setState call inside add listener
The issue is that _scale
, which you are using as the value for the animation, is not being updated due to a lack of setState
. The only place _scale
is being updated is in the stateful widget's build method. The only time build
is called again after the first time is when setState
is called, leading to _scale
not being updated like you wanted.
AnimatedBuilder
is essentially the same as you would do by calling setState
, but it isolates the rebuild to just the builder
of itself rather than the enclosing stateful widget as a whole, which is why it's considered better for performance.
To solve this, simply move _scale = 1 - _controller.value;
into the builder
of AnimatedBuilder
.
@override
Widget build(BuildContext context) {
//_scale = 1 - _controller.value; NOT HERE
return GestureDetector(
onTap: _onTap,
onTapDown: _onTapDown,
onTapUp: _onTapUp,
onTapCancel: _onTapCancel,
child: AnimatedBuilder(
animation: _controller,
child: _animatedButtonUI,
builder: (context, child) {
_scale = 1 - _controller.value; //HERE
return Transform.scale(
scale: _scale,
child: child,
);
},
)
);
}
MAA
Updated on December 22, 2022Comments
-
MAA over 1 year
I had copy pasted code for an animated button that calls setState as it adds a listener to the animation controller.
This article and SO answer:
(Article) Flutter Laggy Animations: How Not To setState
(SO answer) Flutter animation not working
stated that calling setState is not recommended in this case because it will rebuild the entire UI each time the button is pressed. Makes sense!
Now when I do use the AnimatedBuilder stops working. What am I doing wrong?
The Code:
class _AnimatedButtonState extends State<AnimatedButton> with SingleTickerProviderStateMixin { final style = GoogleFonts.volkhov( color: Colors.black, fontSize: 15, ); double _scale; AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: Duration(milliseconds: 100), lowerBound: 0.0, upperBound: 0.1); // _controller.addListener(() { // setState(() {}); // }); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { _scale = 1 - _controller.value; return GestureDetector( onTap: _onTap, onTapDown: _onTapDown, onTapUp: _onTapUp, onTapCancel: _onTapCancel, child: AnimatedBuilder( // HERE HERE animation: _controller, child: _animatedButtonUI, builder: (context, child) { return Transform.scale( scale: _scale, child: child, ); }, ) ); } _onTapUp(TapUpDetails details) => _controller.reverse(); _onTapDown(TapDownDetails details) => _controller.forward(); _onTapCancel() => _controller.reverse(); Widget get _animatedButtonUI => Container( height: 100, width: 250, decoration: BoxDecoration( borderRadius: BorderRadius.circular(100), gradient: LinearGradient( begin: Alignment.bottomLeft, end: Alignment.bottomRight, colors: [Colors.green, Colors.green[400], Colors.green[500]], ), ), child: Center( child: Text( 'Save', style: TextStyle( fontSize: 25, fontWeight: FontWeight.bold, color: Colors.black, ), ), ), );
Side-note: The reason I found this tip, was because I was trying to make my button more responsive to click/touch. Quick clicks and touches on the phone, did not trigger the animation when I used the setState. Instead it took longer than a casual touch by the user. If you can help me with that as well, I'd appreciate it a lot.
-
Christopher Moore almost 4 yearsIn this case,
setState
does not seem like it would be very expensive as the whole widget is just the button which is the only thing to be animated. UsingAnimatedBuilder
provides little to no benefit. -
MAA almost 4 yearsI thought so as well. It's just the first I came across AnimatedBuilder so I wanted to use it.
-
Christopher Moore almost 4 yearsIt works well in other cases where the stateful widget contains more, as in the article you posted. See my answer to solve your problem.
-