Bird
Raised Fist0
Fluttermobile~20 mins

AnimationController in Flutter - Mini App: Build & Ship

Choose your learning style10 modes available
Build: Simple Animation Screen
This screen shows a square that grows and shrinks continuously using AnimationController.
Target UI
┌─────────────────────────────┐
│ Simple Animation Screen      │
│                             │
│          ■■■■■■■■           │
│          ■      ■           │
│          ■      ■           │
│          ■■■■■■■■           │
│                             │
│  [Start Animation] [Stop]   │
└─────────────────────────────┘
Add a square container that changes size smoothly from small to large repeatedly.
Use AnimationController to control the animation.
Add two buttons: 'Start Animation' to begin the animation and 'Stop' to stop it.
The animation should loop back and forth (grow then shrink).
Starter Code
Flutter
import 'package:flutter/material.dart';

class SimpleAnimationScreen extends StatefulWidget {
  @override
  State<SimpleAnimationScreen> createState() => _SimpleAnimationScreenState();
}

class _SimpleAnimationScreenState extends State<SimpleAnimationScreen> with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 2),
    );
    // TODO: Add animation behavior here
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Simple Animation Screen')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // TODO: Add animated square here
            const SizedBox(height: 40),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: () {
                    // TODO: Start animation
                  },
                  child: const Text('Start Animation'),
                ),
                const SizedBox(width: 20),
                ElevatedButton(
                  onPressed: () {
                    // TODO: Stop animation
                  },
                  child: const Text('Stop'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
Task 1
Task 2
Task 3
Task 4
Solution
Flutter
import 'package:flutter/material.dart';

class SimpleAnimationScreen extends StatefulWidget {
  @override
  State<SimpleAnimationScreen> createState() => _SimpleAnimationScreenState();
}

class _SimpleAnimationScreenState extends State<SimpleAnimationScreen> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _sizeAnimation;

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

    _sizeAnimation = Tween<double>(begin: 100, end: 200).animate(_controller);

    _controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _controller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        _controller.forward();
      }
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Simple Animation Screen')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            AnimatedBuilder(
              animation: _sizeAnimation,
              builder: (context, child) {
                return Container(
                  width: _sizeAnimation.value,
                  height: _sizeAnimation.value,
                  color: Colors.blue,
                );
              },
            ),
            const SizedBox(height: 40),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: () {
                    _controller.forward();
                  },
                  child: const Text('Start Animation'),
                ),
                const SizedBox(width: 20),
                ElevatedButton(
                  onPressed: () {
                    _controller.stop();
                  },
                  child: const Text('Stop'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

We use AnimationController to control the animation timing. The controller runs for 2 seconds. We create a Tween that changes the size from 100 to 200 pixels. The AnimatedBuilder rebuilds the square container whenever the animation value changes, updating its width and height.

We listen to the animation status to make it reverse direction when it finishes growing, and forward again when it finishes shrinking. This creates a smooth back-and-forth effect.

The 'Start Animation' button calls _controller.forward() to begin the animation. The 'Stop' button calls _controller.stop() to pause it.

This example shows how to use AnimationController simply and clearly to animate a widget's size.

Final Result
Completed Screen
┌─────────────────────────────┐
│ Simple Animation Screen      │
│                             │
│          ■■■■■■■■■■■■■■     │
│          ■            ■     │
│          ■            ■     │
│          ■■■■■■■■■■■■■■     │
│                             │
│  [Start Animation] [Stop]   │
└─────────────────────────────┘
When user taps 'Start Animation', the blue square smoothly grows from 100x100 to 200x200 pixels and then shrinks back repeatedly.
When user taps 'Stop', the animation pauses and the square stays at its current size.
Stretch Goal
Add a slider to control the animation speed dynamically.
💡 Hint
Use the slider's value to update the AnimationController's duration and restart the animation.

Practice

(1/5)
1. What is the primary role of an AnimationController in Flutter animations?
easy
A. To handle user input events
B. To display images on the screen
C. To control the timing and progress of an animation
D. To manage network requests

Solution

  1. Step 1: Understand AnimationController purpose

    The AnimationController is designed to manage animation timing and progress in Flutter.
  2. Step 2: Compare with other options

    Displaying images, handling user input events, and managing network requests relate to other Flutter features, not animation control.
  3. Final Answer:

    To control the timing and progress of an animation -> Option C
  4. Quick Check:

    AnimationController controls animation timing = A [OK]
Hint: AnimationController manages animation progress and timing [OK]
Common Mistakes:
  • Confusing AnimationController with UI widgets
  • Thinking it handles user input
  • Assuming it manages network or data
2. Which of the following is the correct way to create an AnimationController inside a StatefulWidget with a 2-second duration?
easy
A. AnimationController(duration: Duration(seconds: 2))
B. AnimationController(duration: 2, vsync: this)
C. AnimationController(vsync: this, duration: 2)
D. AnimationController(vsync: this, duration: Duration(seconds: 2))

Solution

  1. Step 1: Check correct parameter types

    The duration parameter requires a Duration object, and vsync must be provided.
  2. Step 2: Validate each option

    AnimationController(vsync: this, duration: Duration(seconds: 2)) correctly uses Duration(seconds: 2) and vsync: this. Constructors passing an integer for duration misuse the type. The constructor with only duration misses vsync.
  3. Final Answer:

    AnimationController(vsync: this, duration: Duration(seconds: 2)) -> Option D
  4. Quick Check:

    Duration needs Duration object and vsync required = A [OK]
Hint: Duration must be a Duration object; vsync is mandatory [OK]
Common Mistakes:
  • Passing integer instead of Duration
  • Omitting vsync parameter
  • Swapping parameter order incorrectly
3. Given this code snippet inside a StatefulWidget:
late AnimationController controller;

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

@override
void dispose() {
  controller.dispose();
  super.dispose();
}
What happens when this widget is removed from the screen?
medium
A. The animation stops and resources are freed properly
B. The animation continues running in the background
C. The app crashes due to missing dispose call
D. The animation restarts automatically

Solution

  1. Step 1: Analyze dispose method

    The dispose() method calls controller.dispose(), which frees animation resources.
  2. Step 2: Understand animation lifecycle

    Calling dispose() stops the animation and prevents memory leaks when the widget is removed.
  3. Final Answer:

    The animation stops and resources are freed properly -> Option A
  4. Quick Check:

    Dispose frees resources and stops animation = B [OK]
Hint: Always dispose AnimationController to stop animation and free memory [OK]
Common Mistakes:
  • Forgetting to call dispose causing leaks
  • Assuming animation runs after widget removal
  • Expecting animation to restart automatically
4. Identify the error in this Flutter code snippet:
class MyWidgetState extends State<MyWidget> with SingleTickerProviderStateMixin {
  late AnimationController controller;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(duration: Duration(seconds: 3));
    controller.forward();
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
}
medium
A. SingleTickerProviderStateMixin should not be mixed in
B. Missing vsync parameter in AnimationController constructor
C. dispose() method is missing
D. AnimationController duration should be an integer

Solution

  1. Step 1: Check AnimationController constructor

    The constructor requires a vsync parameter to optimize animation performance.
  2. Step 2: Identify missing parameter

    The code omits vsync: this, causing an error.
  3. Final Answer:

    Missing vsync parameter in AnimationController constructor -> Option B
  4. Quick Check:

    vsync missing in AnimationController = D [OK]
Hint: AnimationController always needs vsync: this in StatefulWidget [OK]
Common Mistakes:
  • Omitting vsync parameter
  • Passing wrong type for duration
  • Forgetting to mix in SingleTickerProviderStateMixin
5. You want to create a repeating animation that moves a widget back and forth continuously using AnimationController. Which code snippet correctly sets up the controller to repeat the animation forever and reverse direction each time?
hard
A. controller = AnimationController(vsync: this, duration: Duration(seconds: 2))..repeat(reverse: true);
B. controller = AnimationController(vsync: this, duration: Duration(seconds: 2))..forward();
C. controller = AnimationController(vsync: this, duration: Duration(seconds: 2))..repeat();
D. controller = AnimationController(vsync: this, duration: Duration(seconds: 2))..reverse();

Solution

  1. Step 1: Understand repeat method parameters

    The repeat() method can take reverse: true to make animation go back and forth.
  2. Step 2: Evaluate each option

    controller = AnimationController(vsync: this, duration: Duration(seconds: 2))..repeat(reverse: true); uses repeat(reverse: true) correctly. controller = AnimationController(vsync: this, duration: Duration(seconds: 2))..forward(); only plays forward once. controller = AnimationController(vsync: this, duration: Duration(seconds: 2))..repeat(); repeats but does not reverse. controller = AnimationController(vsync: this, duration: Duration(seconds: 2))..reverse(); calls reverse once, not repeating.
  3. Final Answer:

    controller = AnimationController(vsync: this, duration: Duration(seconds: 2))..repeat(reverse: true); -> Option A
  4. Quick Check:

    repeat with reverse true = C [OK]
Hint: Use repeat(reverse: true) for back-and-forth animation [OK]
Common Mistakes:
  • Using forward() instead of repeat()
  • Forgetting reverse: true for back-and-forth
  • Calling reverse() once instead of repeating
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard