Tom
Tom

Reputation: 4059

Triggering widget animation from outside widget

I have a custom widget that has normal / animated state. Sometimes I want to be it animated, and sometimes static.

I have made a simple test project to demonstrate my problem: test page contains my custom widget (ScoreBoard) and 2 buttons to start / stop animating scoreboard. My problem, that ScoreBoard is not animated, even if I start animation.

Here is my code:

TestPage:

class TestPage extends StatefulWidget {
  @override
  _TestPageState createState() => _TestPageState();
}

class _TestPageState extends State<TestPage> {
  bool _animate;

  @override
  void initState() {
    _animate = false;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: <Widget>[
          ScoreBoard(
            text: "Hello, world!",
            animate: _animate,
          ),
          FlatButton(
            child: Text("START animation"),
            onPressed: () {
              setState(() {
                _animate = true;
              });
            },
          ),
          FlatButton(
            child: Text("STOP animation"),
            onPressed: () {
              setState(() {
                _animate = false;
              });
            },
          ),
        ],
      ),
    );
  }
}

ScoreBoard widget:

class ScoreBoard extends StatefulWidget {
  final String text;
  final bool animate;

  const ScoreBoard({Key key, this.text, this.animate}) : super(key: key);

  @override
  _ScoreBoardState createState() => _ScoreBoardState();
}

class _ScoreBoardState extends State<ScoreBoard>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = new AnimationController(
      duration: const Duration(seconds: 1),
      vsync: this,
    )..forward();
  }

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

  @override
  Widget build(BuildContext context) {
    return widget.animate
        ? ScaleTransition(
            child:
                Text(widget.text, style: Theme.of(context).textTheme.display1),
            scale: new CurvedAnimation(
              parent: _controller,
              curve: Curves.easeIn,
            ),
          )
        : Container(
            child:
                Text(widget.text, style: Theme.of(context).textTheme.display1),
          );
  }
}

Would you be so kind to help me? Thanks in advance!

Upvotes: 0

Views: 1511

Answers (1)

M. Krol
M. Krol

Reputation: 165

Answer

If you initialize an AnimationController widget and do not specify arguments for lowerBound and upperBound (which is the case here), your animation is going to start by default with lowerBound 0.0.

AnimationController({double value, Duration duration, String debugLabel, double lowerBound: 0.0, double upperBound: 1.0, AnimationBehavior animationBehavior: AnimationBehavior.normal, @required TickerProvider vsync }) Creates an animation controller. [...]

https://docs.flutter.io/flutter/animation/AnimationController-class.html

If you initialize the state of your widget ScoreBoard, the method forward gets called only once during the whole lifetime of your app. The method forward makes that your animation increases from the lowerBound (0.0) to the upperBound (1.0) within 1 second.

Starts running this animation forwards (towards the end).

https://docs.flutter.io/flutter/animation/AnimationController/forward.html

In our case, there is no way back once the method forward got called. We are only able to stop the animation.

Press Ctrl + F5 for a full restart of the app to see the animation. To make it even clearer, change the duration of your animation to 10 seconds.

Btw. since Dart 2 you do not need to use the new keyword.

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 10),
      vsync: this,
    )..forward();
  }

Example

To see what happens you could add this to your build method:

  @override
  Widget build(BuildContext context) {
    // Execute 'reverse' if the animation is completed
    if (_controller.isCompleted)
      _controller.reverse();
    else if (_controller.isDismissed)
      _controller.forward();
    // ...

... and do not call the method forward in the initState method:

  @override
  void initState() {
    super.initState();
    _controller = new AnimationController(
      duration: const Duration(seconds: 10),
      vsync: this,
    );
  }

Upvotes: 1

Related Questions