Animations & Motion Design

Implicit Animations: AnimatedContainer & AnimatedPadding

15 min Lesson 2 of 13

Implicit Animations: AnimatedContainer & AnimatedPadding

Flutter provides a powerful category of widgets called implicit animation widgets. These widgets automatically animate changes to their properties whenever those properties are updated inside setState(). You do not manage AnimationController, Tween, or Ticker objects at all — Flutter handles the interpolation for you behind the scenes.

The two most commonly used implicit animation widgets are AnimatedContainer and AnimatedPadding. Both accept a duration parameter that controls how long the transition takes, and an optional curve parameter that shapes the easing of the animation.

Note: Implicit animations are ideal when a widget owns the animation — that is, when you simply want a property to transition smoothly from one value to another in response to a state change. For more complex, repeating, or sequenced animations you will use explicit animation widgets with AnimationController.

How Implicit Animations Work

Every implicit animation widget follows the same pattern:

  • You declare the widget with some initial property values.
  • When a state change causes one or more properties to receive new values, the widget detects the difference.
  • Over the specified duration, the widget smoothly interpolates between the old value and the new value.
  • The animation respects the chosen Curve, which controls the speed profile of the transition (e.g., ease-in, bounce, elastic).

AnimatedContainer

AnimatedContainer is the animated version of Container. It can animate virtually any property that Container supports: width, height, color, border-radius, alignment, padding, margin, and decoration. This makes it one of the most versatile implicit animation widgets in Flutter.

AnimatedContainer — Animating Size and Color

class AnimatedBox extends StatefulWidget {
  const AnimatedBox({super.key});

  @override
  State<AnimatedBox> createState() => _AnimatedBoxState();
}

class _AnimatedBoxState extends State<AnimatedBox> {
  bool _expanded = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          _expanded = !_expanded;
        });
      },
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 400),
        curve: Curves.easeInOut,
        width: _expanded ? 280.0 : 120.0,
        height: _expanded ? 280.0 : 120.0,
        decoration: BoxDecoration(
          color: _expanded ? Colors.deepPurple : Colors.teal,
          borderRadius: BorderRadius.circular(_expanded ? 40.0 : 8.0),
        ),
        alignment: Alignment.center,
        child: Text(
          _expanded ? 'Tap to shrink' : 'Tap to expand',
          style: const TextStyle(color: Colors.white, fontSize: 14),
          textAlign: TextAlign.center,
        ),
      ),
    );
  }
}

When the user taps the box, setState flips _expanded. AnimatedContainer automatically interpolates the width, height, color, and borderRadius over 400 milliseconds using the easeInOut curve. No animation boilerplate required.

Tip: You can animate decoration and color simultaneously, but do not set both color and a BoxDecoration with a color at the same time. Flutter will throw an assertion error. Put the color inside the BoxDecoration when you also need border-radius or shadows.

Animating Multiple Properties at Once

One of the strengths of AnimatedContainer is that every property you change in a single setState call is animated simultaneously. You can animate width, height, color, border-radius, padding, and alignment all in one transition.

AnimatedContainer — Card Highlight Effect

class HighlightCard extends StatefulWidget {
  final String label;
  const HighlightCard({super.key, required this.label});

  @override
  State<HighlightCard> createState() => _HighlightCardState();
}

class _HighlightCardState extends State<HighlightCard> {
  bool _selected = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => setState(() => _selected = !_selected),
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 300),
        curve: Curves.fastOutSlowIn,
        margin: EdgeInsets.all(_selected ? 4.0 : 12.0),
        padding: EdgeInsets.symmetric(
          vertical: _selected ? 24.0 : 16.0,
          horizontal: _selected ? 32.0 : 16.0,
        ),
        decoration: BoxDecoration(
          color: _selected ? Colors.indigo : Colors.white,
          borderRadius: BorderRadius.circular(_selected ? 20.0 : 4.0),
          boxShadow: [
            BoxShadow(
              color: Colors.black.withOpacity(_selected ? 0.25 : 0.08),
              blurRadius: _selected ? 16.0 : 4.0,
              offset: const Offset(0, 4),
            ),
          ],
        ),
        child: Text(
          widget.label,
          style: TextStyle(
            color: _selected ? Colors.white : Colors.black87,
            fontWeight:
                _selected ? FontWeight.bold : FontWeight.normal,
          ),
        ),
      ),
    );
  }
}

AnimatedPadding

AnimatedPadding is a focused implicit animation widget that animates only the padding around its child. It is lighter than AnimatedContainer when you exclusively need to transition padding values — for example, sliding content inward or outward in response to user interaction or a keyboard appearing.

AnimatedPadding — Keyboard-Aware Content Shift

class FocusableForm extends StatefulWidget {
  const FocusableForm({super.key});

  @override
  State<FocusableForm> createState() => _FocusableFormState();
}

class _FocusableFormState extends State<FocusableForm> {
  bool _focused = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        AnimatedPadding(
          duration: const Duration(milliseconds: 350),
          curve: Curves.easeOut,
          padding: EdgeInsets.only(top: _focused ? 8.0 : 60.0),
          child: const FlutterLogo(size: 80),
        ),
        Focus(
          onFocusChange: (hasFocus) {
            setState(() => _focused = hasFocus);
          },
          child: const TextField(
            decoration: InputDecoration(
              labelText: 'Username',
              border: OutlineInputBorder(),
            ),
          ),
        ),
      ],
    );
  }
}

When the text field gains focus, the logo smoothly slides upward by transitioning from top: 60.0 to top: 8.0 — simulating the kind of content shift commonly seen in mobile login screens.

Choosing the Right Curve

The curve parameter controls how the animation progresses over time. Flutter ships many built-in curves in the Curves class:

  • Curves.linear — constant speed, mechanical feel.
  • Curves.easeIn — starts slow, ends fast.
  • Curves.easeOut — starts fast, decelerates naturally.
  • Curves.easeInOut — slow start and end, fast middle. Most natural for UI transitions.
  • Curves.fastOutSlowIn — Material Design standard motion curve.
  • Curves.bounceOut — overshoots and bounces at the end.
  • Curves.elasticOut — elastic spring overshoot effect.
Tip: For most UI transitions, Curves.easeInOut or Curves.fastOutSlowIn looks the most polished and follows Material Design motion guidelines. Reserve bounce and elastic curves for playful, game-like interactions.

Summary

Implicit animation widgets are Flutter's easiest entry point into motion design. AnimatedContainer lets you animate size, color, border-radius, padding, margin, and decoration in one widget simply by updating state. AnimatedPadding provides a lightweight alternative when only spacing needs to animate. Both accept a duration and a curve, giving you full control over the feel of the transition without any animation controller code. As a rule, reach for implicit widgets first, and graduate to explicit animations only when you need fine-grained control over sequencing or repeating.

Key Takeaway: Any time a Flutter widget's name begins with Animated and its corresponding non-animated counterpart exists (e.g., ContainerAnimatedContainer, OpacityAnimatedOpacity), it follows the implicit animation pattern: change the property value inside setState() and Flutter animates the transition automatically.