1
class _SomeWidgetWithAnimationsState extends State<SomeWidgetWithAnimations> with TickerProviderStateMixin {
  AnimationController firstController;
  AnimationController secondController;

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
        aniamtion: /* Here I want to pass the two animation controllers above */,
        builder: (context, child) => /* Something that uses two animations to animate */,
    );
  }
}

I want to be able to listen to events from multiple Listenables where only one Listenable is required. Is there a way to route notifications from two Listenables to one? I considered creating my own implementation of the Listenable that will have some method such as addSourceListenable(Listenable source) that will subscribe to the Listenable source with a callback that will notify its own subscribers. But I think there is a more elegant way to solve this. Maybe RxDart can offer something.

2 Answers 2

1

For this use case having multiple animations flutter has an own concept called Staggered Animations. You can read more about it here:

https://flutter.dev/docs/development/ui/animations/staggered-animations#:~:text=To%20create%20a%20staggered%20animation,being%20animated%2C%20create%20a%20Tween%20.

This is a full working example provided from the article:

// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;

class StaggerAnimation extends StatelessWidget {
  StaggerAnimation({ Key key, this.controller }) :

    // Each animation defined here transforms its value during the subset
    // of the controller's duration defined by the animation's interval.
    // For example the opacity animation transforms its value during
    // the first 10% of the controller's duration.

    opacity = Tween<double>(
      begin: 0.0,
      end: 1.0,
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(
          0.0, 0.100,
          curve: Curves.ease,
        ),
      ),
    ),

    width = Tween<double>(
      begin: 50.0,
      end: 150.0,
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(
          0.125, 0.250,
          curve: Curves.ease,
        ),
      ),
    ),

    height = Tween<double>(
      begin: 50.0,
      end: 150.0
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(
          0.250, 0.375,
          curve: Curves.ease,
        ),
      ),
    ),

    padding = EdgeInsetsTween(
      begin: const EdgeInsets.only(bottom: 16.0),
      end: const EdgeInsets.only(bottom: 75.0),
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(
          0.250, 0.375,
          curve: Curves.ease,
        ),
      ),
    ),

    borderRadius = BorderRadiusTween(
      begin: BorderRadius.circular(4.0),
      end: BorderRadius.circular(75.0),
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(
          0.375, 0.500,
          curve: Curves.ease,
        ),
      ),
    ),

    color = ColorTween(
      begin: Colors.indigo[100],
      end: Colors.orange[400],
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(
          0.500, 0.750,
          curve: Curves.ease,
        ),
      ),
    ),

    super(key: key);

  final Animation<double> controller;
  final Animation<double> opacity;
  final Animation<double> width;
  final Animation<double> height;
  final Animation<EdgeInsets> padding;
  final Animation<BorderRadius> borderRadius;
  final Animation<Color> color;

  // This function is called each time the controller "ticks" a new frame.
  // When it runs, all of the animation's values will have been
  // updated to reflect the controller's current value.
  Widget _buildAnimation(BuildContext context, Widget child) {
    return Container(
      padding: padding.value,
      alignment: Alignment.bottomCenter,
      child: Opacity(
        opacity: opacity.value,
        child: Container(
          width: width.value,
          height: height.value,
          decoration: BoxDecoration(
            color: color.value,
            border: Border.all(
              color: Colors.indigo[300],
              width: 3.0,
            ),
            borderRadius: borderRadius.value,
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      builder: _buildAnimation,
      animation: controller,
    );
  }
}

class StaggerDemo extends StatefulWidget {
  @override
  _StaggerDemoState createState() => _StaggerDemoState();
}

class _StaggerDemoState extends State<StaggerDemo> with TickerProviderStateMixin {
  AnimationController _controller;

  @override
  void initState() {
    super.initState();

    _controller = AnimationController(
      duration: const Duration(milliseconds: 2000),
      vsync: this
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  Future<void> _playAnimation() async {
    try {
      await _controller.forward().orCancel;
      await _controller.reverse().orCancel;
    } on TickerCanceled {
      // the animation got canceled, probably because we were disposed
    }
  }

  @override
  Widget build(BuildContext context) {
    timeDilation = 10.0; // 1.0 is normal animation speed.
    return Scaffold(
      appBar: AppBar(
        title: const Text('Staggered Animation'),
      ),
      body: GestureDetector(
        behavior: HitTestBehavior.opaque,
        onTap: () {
          _playAnimation();
        },
        child: Center(
          child: Container(
            width: 300.0,
            height: 300.0,
            decoration: BoxDecoration(
              color: Colors.black.withOpacity(0.1),
              border: Border.all(
                color:  Colors.black.withOpacity(0.5),
              ),
            ),
            child: StaggerAnimation(
              controller: _controller.view
            ),
          ),
        ),
      ),
    );
  }
}

void main() {
  runApp(MaterialApp(home: StaggerDemo()));
}

You can nearly do everything and its chained in the logic. I recomend you just guiding threw the docs there, its well explained.

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

1 Comment

With Staggered animation, The animations run one after another, What the OP is asking is for a case when multiple animations run at the same instance.
1

Have a look at Listenable.merge(List<Listenable?> listenables) which returns a Listenable that triggers when any of the given Listenables themselves trigger. Hope i have helped you :)

https://api.flutter.dev/flutter/foundation/Listenable/Listenable.merge.html

1 Comment

Thank you this is exactly what I was looking for and I believe what the OP is looking for too.

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.