Skip to main content
Back to all tutorials
Tested: Flutter 3.24.3 • Dart 3.5.3
WE-TUT-003 Verified September 2026
Animations Implicit Curves AnimatedContainer

Customize AnimatedContainer Dynamics

Explore implicit animation curves, duration tuning, and state toggles with mathematical interpolation and clean Flutter state management.

1. The Core Challenge & Problem

Building smooth micro-interactions like expandable floating action buttons, active tab pill indicators, or morphing cards often leads developers to create complex AnimationControllers, SingleTickerProviderStateMixins, and custom Tween pipelines for simple property transitions.

2. Architectural Principles & Resolution

Flutter provides `AnimatedContainer` as an "implicit animation" widget. When any of its properties (`width`, `height`, `color`, `borderRadius`, `padding`) change upon calling `setState()`, the framework automatically creates an internal `Tween` between the previous and current values and interpolates them over the specified `duration` using the chosen `curve`. Key concepts: 1. **Curves**: Curves map a linear progress value $t \in [0, 1]$ to an eased output. For example, `Curves.easeInOut` decelerates into the destination, whereas `Curves.bounceOut` simulates physical gravity and elastic rebound. 2. **Durations**: UI micro-interactions are optimal between 200ms and 400ms. Durations below 150ms can cause perceptual flickering, while durations exceeding 700ms feel sluggish to users. 3. **Implicit vs Explicit**: If you don't need continuous looping, reverse playheads, or staggered timelines, `AnimatedContainer` eliminates boilerplate controller disposal and ticker management.

3. Complete Tested Flutter Code

main.dart (Flutter 3.x+ ready)
lib/main.dart Copy & Paste into a new Flutter project
import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(
    debugShowCheckedModeBanner: false,
    home: Scaffold(
      backgroundColor: Color(0xFF0B1220),
      body: Center(
        child: MorphingCardDemo(),
      ),
    ),
  ));
}

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

  @override
  State<MorphingCardDemo> createState() => _MorphingCardDemoState();
}

class _MorphingCardDemoState extends State<MorphingCardDemo> {
  bool _isExpanded = false;

  void _toggle() {
    setState(() {
      _isExpanded = !_isExpanded;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        // Interactive Implicit AnimatedContainer
        AnimatedContainer(
          duration: const Duration(milliseconds: 400),
          curve: Curves.easeInOutCubic,
          width: _isExpanded ? 280 : 160,
          height: _isExpanded ? 180 : 160,
          decoration: BoxDecoration(
            color: _isExpanded ? const Color(0xFF2563EB) : const Color(0xFF1E293B),
            borderRadius: BorderRadius.circular(_isExpanded ? 24 : 12),
            border: Border.all(
              color: _isExpanded ? const Color(0xFF60A5FA) : const Color(0xFF334155),
              width: 2,
            ),
            boxShadow: [
              BoxShadow(
                color: _isExpanded ? const Color(0x662563EB) : const Color(0x33000000),
                blurRadius: _isExpanded ? 24 : 8,
                offset: const Offset(0, 8),
              ),
            ],
          ),
          child: Center(
            child: Icon(
              _isExpanded ? Icons.fullscreen_exit : Icons.fullscreen,
              color: Colors.white,
              size: _isExpanded ? 40 : 28,
            ),
          ),
        ),
        const SizedBox(height: 24),
        ElevatedButton.icon(
          onPressed: _toggle,
          icon: const Icon(Icons.play_arrow),
          label: Text(_isExpanded ? 'Collapse' : 'Expand Card'),
          style: ElevatedButton.styleFrom(
            backgroundColor: const Color(0xFF2DD4BF),
            foregroundColor: const Color(0xFF0F172A),
          ),
        ),
      ],
    );
  }
}

4. Expected Visual & Behavioral Result

Clicking 'Expand Card' causes the container to fluidly morph from a dark square (160x160 with 12px radius) into a luminous blue rectangle (280x180 with 24px radius and vivid drop shadow) over 400 milliseconds with cubic easing.

5. Common Pitfalls & Traps

Pitfall #1: Specifying both color on AnimatedContainer and color in decoration: BoxDecoration

Remedy: Flutter throws an assertion error if color is defined in both places. Always specify color inside the BoxDecoration when using rounded corners or borders.

Pitfall #2: Using Curves.bounceOut with properties that cannot tolerate negative interpolation values (such as zero or negative radius)

Remedy: Bounce curves can momentarily extrapolate values beyond 1.0 or before 0.0. Ensure minimum radii and dimensions have adequate margins.

Pitfall #3: Instantiating new Duration instances with unstable runtime variables on every frame

Remedy: Use const Duration(...) wherever possible to preserve widget rebuild performance.

Official Flutter References & Standards