Flutter-Animationsgrundlagen

Dieser Abschnitt führt in die Implementierung von Animationen in Flutter ein, einschließlich impliziter und expliziter Animationen.


Implizite Animation

Implizite Animationen sind Animations-Widgets, bei denen Flutter die Übergangseffekte automatisch übernimmt.

Beispiel: AnimatedContainer

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

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

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

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // Klicken, um die Größe umzuschalten
        GestureDetector(
          onTap: () => setState(() => _isLarge = !_isLarge),
          child: AnimatedContainer(
            duration: const Duration(milliseconds: 500),  // Animationsdauer
            curve: Curves.easeInOut,  // Animationskurve
            width: _isLarge ? 200 : 100,
            height: _isLarge ? 200 : 100,
            color: _isLarge ? Colors.blue : Colors.red,
            child: Center(
              child: Text(
                _isLarge ? 'large' : 'small',
                style: const TextStyle(color: Colors.white, fontSize: 24),
              ),
            ),
          ),
        ),
        const SizedBox(height: 20),
        ElevatedButton(
          onPressed: () => setState(() => _isLarge = !_isLarge),
          child: const Text('Switch'),
        ),
      ],
    );
  }
}

Häufige implizite Animations-Widgets

WidgetBeschreibung
AnimatedContainerAnimationen, wenn sich Eigenschaften wie Containergröße, Farbe usw. ändern
AnimatedOpacityDeckkraft-Übergangsanimation
AnimatedPaddingPadding-Übergangsanimation
AnimatedAlignAnimation bei Ausrichtungsänderung
AnimatedSwitcherAnimation beim Wechseln untergeordneter Widgets
AnimatedDefaultTextStyleAnimation bei Textstiländerung

Explizite Animation

Explizite Animationen verwenden AnimationController, um die Animation vollständig zu steuern.

Beispiel: Rotationsanimation

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

  @override
  State<RotationAnimation> createState() => _RotationAnimationState();
}

class _RotationAnimationState extends State<RotationAnimation>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

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

    // Rotationsanimation erstellen (0 bis 1 entspricht 0 bis 2π)
    _animation = Tween<double>(begin: 0, end: 6.28).animate(
      CurvedAnimation(parent: _controller, curve: Curves.linear),
    );

    // Animation starten
    _controller.repeat();
  }

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

  @override
  Widget build(BuildContext context) {
    return Center(
      child: AnimatedBuilder(
        animation: _animation,
        builder: (context, child) {
          return Transform.rotate(
            angle: _animation.value,  // Rotationswinkel
            child: const Icon(Icons.refresh, size: 100),
          );
        },
      ),
    );
  }
}

Gestaffelte Animation

Beispiel: Gestaffelte Animation

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

  @override
  State<StaggeredAnimation> createState() => _StaggeredAnimationState();
}

class _StaggeredAnimationState extends State<StaggeredAnimation>
    with TickerProviderStateMixin {
  late AnimationController _controller;

  // Einzelne Animationen
  late Animation<double> _fadeAnimation;
  late Animation<double> _scaleAnimation;
  late Animation<Offset> _slideAnimation;

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

    // Einblendanimation (0-0.5)
    _fadeAnimation = Tween<double>(begin: 0, end: 1).animate(
      CurvedAnimation(
        parent: _controller,
        curve: const Interval(0, 0.5, curve: Curves.easeOut),
      ),
    );

    // Skalierungsanimation (0.3-0.7)
    _scaleAnimation = Tween<double>(begin: 0.5, end: 1).animate(
      CurvedAnimation(
        parent: _controller,
        curve: const Interval(0.3, 0.7, curve: Curves.elasticOut),
      ),
    );

    // Einschubanimation (0.5-1.0)
    _slideAnimation = Tween<Offset>(
      begin: const Offset(0, 0.5),
      end: Offset.zero,
    ).animate(
      CurvedAnimation(
        parent: _controller,
        curve: const Interval(0.5, 1, curve: Curves.easeOutCubic),
      ),
    );

    _controller.forward();
  }

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

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return FadeTransition(
          opacity: _fadeAnimation,
          child: ScaleTransition(
            scale: _scaleAnimation,
            child: SlideTransition(
              position: _slideAnimation,
              child: const Card(
                child: Padding(
                  padding: EdgeInsets.all(24),
                  child: Text('Welcome'),
                ),
              ),
            ),
          ),
        );
      },
    );
  }
}

Hero-Animation

Hero-Animation wird verwendet, um Übergangseffekte für gemeinsam genutzte Elemente beim Wechsel zwischen Seiten zu erzeugen.

Beispiel: Hero-Animation

// Startseite - Bild mit Hero
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home')),
      body: Center(
        child: GestureDetector(
          onTap: () => Navigator.push(
            context,
            MaterialPageRoute(builder: (_) => const DetailPage()),
          ),
          child: Hero(
            tag: 'myHero',  // Gleicher Tag
            child: Image.network(
              'https://picsum.photos/200',
              width: 200,
              height: 200,
            ),
          ),
        ),
      ),
    );
  }
}

// Detailseite - Bild mit Hero
class DetailPage extends StatelessWidget {
  const DetailPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Details')),
      body: Center(
        child: Hero(
          tag: 'myHero',  // Gleicher Tag
          child: Image.network(
            'https://picsum.photos/200',
            width: 300,
            height: 300,
          ),
        ),
      ),
    );
  }
}

Implizite Animationen eignen sich für einfache Szenarien, während explizite Animationen für komplexe Steuerungen geeignet sind.

Die Hero-Animation ist die beste Wahl für Seitenübergänge.

Weitere Erweiterungen