skills/flutter/references/features-implicit-animations.md

7.5 KiB

name description
features-implicit-animations Flutter implicit animations - AnimatedContainer, AnimatedOpacity, AnimatedSize, and other implicit animation widgets

Implicit Animations

Implicit animations automatically animate property changes. They're simpler to use than explicit animations but less flexible.

AnimatedContainer

Automatically animates changes to Container properties.

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

  @override
  State<AnimatedContainerExample> createState() => _AnimatedContainerExampleState();
}

class _AnimatedContainerExampleState extends State<AnimatedContainerExample> {
  bool _expanded = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          _expanded = !_expanded;
        });
      },
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 300),
        curve: Curves.easeInOut,
        width: _expanded ? 200 : 100,
        height: _expanded ? 200 : 100,
        decoration: BoxDecoration(
          color: _expanded ? Colors.blue : Colors.red,
          borderRadius: BorderRadius.circular(_expanded ? 20 : 10),
        ),
        child: Center(
          child: Text(_expanded ? 'Expanded' : 'Collapsed'),
        ),
      ),
    );
  }
}

AnimatedOpacity

Animates opacity changes.

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

  @override
  State<AnimatedOpacityExample> createState() => _AnimatedOpacityExampleState();
}

class _AnimatedOpacityExampleState extends State<AnimatedOpacityExample> {
  bool _visible = true;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        AnimatedOpacity(
          opacity: _visible ? 1.0 : 0.0,
          duration: const Duration(milliseconds: 300),
          child: const FlutterLogo(size: 100),
        ),
        ElevatedButton(
          onPressed: () {
            setState(() {
              _visible = !_visible;
            });
          },
          child: Text(_visible ? 'Hide' : 'Show'),
        ),
      ],
    );
  }
}

AnimatedSize

Animates size changes.

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

  @override
  State<AnimatedSizeExample> createState() => _AnimatedSizeExampleState();
}

class _AnimatedSizeExampleState extends State<AnimatedSizeExample> {
  bool _expanded = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        AnimatedSize(
          duration: const Duration(milliseconds: 300),
          curve: Curves.easeInOut,
          child: Container(
            width: _expanded ? 200 : 100,
            height: _expanded ? 200 : 100,
            color: Colors.blue,
            child: _expanded
                ? const Center(child: Text('Expanded'))
                : const Center(child: Text('Small')),
          ),
        ),
        ElevatedButton(
          onPressed: () {
            setState(() {
              _expanded = !_expanded;
            });
          },
          child: Text(_expanded ? 'Collapse' : 'Expand'),
        ),
      ],
    );
  }
}

AnimatedCrossFade

Cross-fades between two children.

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

  @override
  State<AnimatedCrossFadeExample> createState() => _AnimatedCrossFadeExampleState();
}

class _AnimatedCrossFadeExampleState extends State<AnimatedCrossFadeExample> {
  bool _showFirst = true;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        AnimatedCrossFade(
          duration: const Duration(milliseconds: 300),
          firstChild: Container(
            width: 200,
            height: 200,
            color: Colors.red,
            child: const Center(child: Text('First')),
          ),
          secondChild: Container(
            width: 200,
            height: 200,
            color: Colors.blue,
            child: const Center(child: Text('Second')),
          ),
          crossFadeState: _showFirst
              ? CrossFadeState.showFirst
              : CrossFadeState.showSecond,
        ),
        ElevatedButton(
          onPressed: () {
            setState(() {
              _showFirst = !_showFirst;
            });
          },
          child: const Text('Toggle'),
        ),
      ],
    );
  }
}

AnimatedSwitcher

Switches between children with a transition.

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

  @override
  State<AnimatedSwitcherExample> createState() => _AnimatedSwitcherExampleState();
}

class _AnimatedSwitcherExampleState extends State<AnimatedSwitcherExample> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        AnimatedSwitcher(
          duration: const Duration(milliseconds: 300),
          transitionBuilder: (child, animation) {
            return ScaleTransition(
              scale: animation,
              child: child,
            );
          },
          child: Text(
            '$_counter',
            key: ValueKey(_counter),
            style: const TextStyle(fontSize: 48),
          ),
        ),
        ElevatedButton(
          onPressed: () {
            setState(() {
              _counter++;
            });
          },
          child: const Text('Increment'),
        ),
      ],
    );
  }
}

AnimatedPositioned

Animates position changes within a Stack.

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

  @override
  State<AnimatedPositionedExample> createState() => _AnimatedPositionedExampleState();
}

class _AnimatedPositionedExampleState extends State<AnimatedPositionedExample> {
  bool _moved = false;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        AnimatedPositioned(
          duration: const Duration(milliseconds: 300),
          left: _moved ? 200 : 0,
          top: _moved ? 200 : 0,
          child: Container(
            width: 100,
            height: 100,
            color: Colors.blue,
          ),
        ),
        Positioned(
          bottom: 20,
          left: 20,
          child: ElevatedButton(
            onPressed: () {
              setState(() {
                _moved = !_moved;
              });
            },
            child: const Text('Move'),
          ),
        ),
      ],
    );
  }
}

Animation Curves

Use curves to control animation timing.

AnimatedContainer(
  duration: const Duration(milliseconds: 300),
  curve: Curves.easeInOut, // Common curves: linear, easeIn, easeOut, easeInOut, bounce, elastic
  // ... properties
)

Key Points

  • Implicit animations automatically animate property changes
  • Use AnimatedContainer for animating container properties
  • Use AnimatedOpacity for fade in/out effects
  • Use AnimatedSize for size changes
  • Use AnimatedCrossFade to transition between two widgets
  • Use AnimatedSwitcher to switch between children
  • Use AnimatedPositioned for position animations in Stack
  • Specify duration and curve for timing control
  • Implicit animations are simpler but less flexible than explicit animations