6

I was searching for a way to combine repetitive and not repetitive animations in Flutter. For example, start opening animation (not repetitive) and then show some repetitive animation, like bouncing animation. Currently, I've used 2 animated controllers and 2 animatedBuilders above one widget. Here is my sample code:

   @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
        animation: _outerAnimationController, //starting animation, not repetitive
        builder: (context, _) {
          return AnimatedBuilder( 
              animation: _innerCurvedAnimation, //repetitive animation, bouncing
              builder: (context, _) {
                return CustomPaint(
                  size: Size(MediaQuery.of(context).size.width,
                      MediaQuery.of(context).size.height),
                  painter: ShowCasePainter(
                    centerPosition: Offset(
                        MediaQuery.of(context).size.width / 2,
                        MediaQuery.of(context).size.height / 2),
                    innerCircleRadius: widget.innerCircleRadius +
                        (_innerCurvedAnimation.value * PaddingSmall), //repetitive animation value, bouncing
                    outerCircleRadius: _outerAnimationTween.value, //starting animation value, not repetitive
                  ),
                );
              });
        });
  }

Is it a good practice on using multiple controllers in this way? How to influence animatedBuilder from two controllers representing different animations?

Thanks for your help!

5
  • 1
    check Listenable.merge ctor Commented Sep 14, 2021 at 8:01
  • could you give me some more expanded answer? Commented Sep 14, 2021 at 8:03
  • api.flutter.dev/flutter/foundation/Listenable/… Commented Sep 14, 2021 at 8:03
  • Please, post your comment as answer, I'll check it as correct! Commented Sep 14, 2021 at 10:34
  • 1
    feel free to write a self answer if Listenable.merge worked for you Commented Sep 14, 2021 at 10:41

1 Answer 1

21

With the help of @pskink I've finally found solution. No need in use multiple AnimatedBuilders here, Listenable.merge with multiple AnimationControllers will be enought. Merging possible because AnimationControllers extends from Listenable class. Here is correct code:

@override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
        animation: Listenable.merge(
            [_innerAnimationController, _outerAnimationController]),
        builder: (context, _) {
          return CustomPaint(
            size: Size(MediaQuery.of(context).size.width,
                MediaQuery.of(context).size.height),
            painter: ShowCasePainter(
              centerPosition: Offset(MediaQuery.of(context).size.width / 2,
                  MediaQuery.of(context).size.height / 2),
              innerCircleRadius: widget.innerCircleRadius +
                  (_innerCurvedAnimation.value * PaddingSmall),
              outerCircleRadius: _outerAnimationTween.value,
            ),
          );
        });
  }

This solution will be ok with any widget, but with CustomPainter there is a better solution. In case of Custom painter, animationControllers can be passed throught constructor into CustomPainter and inside of this class should be merged. Parent build method:

 @override
  Widget build(BuildContext context) {
    return CustomPaint(
      willChange: true,
      size: Size(MediaQuery.of(context).size.width,
          MediaQuery.of(context).size.height),
      painter: ShowCasePainter(
        centerPosition: Offset(MediaQuery.of(context).size.width / 2,
            MediaQuery.of(context).size.height / 2),
        innerCircleRadius: widget.innerCircleRadius,
        outerCircleRadius: widget.outerCircleRadius,
        innerAnimationController: _innerAnimationController,
        outerAnimationController: _outerAnimationController,
      ),
    );
  }

As you see, I passed only immutable values, instead of using values from controllers/animations. And here is CustomPainter constructor:

class ShowCasePainter extends CustomPainter {
  final Offset centerPosition;
  final double innerCircleRadius;
  final double outerCircleRadius;
  final Color backgroundColor;
  final Color ringColor;
  final Animation<double> innerAnimationController;
  final Animation<double> outerAnimationController;
  Animation<double> _innerCurvedAnimation;
  Animation<double> _outerAnimationTween;
  Animation<Color> _backgroudColorTween;
  ShowCasePainter(
      {this.innerAnimationController,
      this.outerAnimationController,
      this.centerPosition,
      this.innerCircleRadius = 32.0,
      this.outerCircleRadius = 128.0,
      this.backgroundColor,
      this.ringColor})
      : super(
            repaint: Listenable.merge(
                [innerAnimationController, outerAnimationController])) {
    _innerCurvedAnimation =
        CurvedAnimation(parent: innerAnimationController, curve: Curves.easeIn);

    _outerAnimationTween =
        Tween(begin: innerCircleRadius, end: outerCircleRadius)
            .animate(outerAnimationController);

    _backgroudColorTween = ColorTween(
            begin: Colors.transparent,
            end: backgroundColor ?? Colors.black.withOpacity(0.2))
        .animate(outerAnimationController);
  }

Now animations will work correct.

Sign up to request clarification or add additional context in comments.

3 Comments

btw you dont need AnimatedBuilder and creating a new ShowCasePainter for every animation frame - instead check CustomPainter constructor and its repaint optional parameter - that way you create just one ShowCasePainter that "repaints" itself when this Listenable triggers
I thought that if you want to listen for updates there must be some king of observer. I understand what you mean. I need to check it out. Thanks!))
Unfortunately, it doesn't work with AnimationControllers. So, I found your response: stackoverflow.com/a/58595116/10084055, which is not what I'm searching for. In addition, I found solution with setState, which is dirtier solution. Example is, add listener on merged listenables above custom painter class and call setstate on each controllers update.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.