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.

user11655900
  • 420
  • 7
  • 15

2 Answers2

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.

Marcel Dz
  • 2,321
  • 4
  • 14
  • 49
  • 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. – Mahesh Jamdade Dec 07 '21 at 16:34
0

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