Lädt...

🔧 Creating Custom, Reusable Animations in Flutter That Don't Kill Performance


Nachrichtenbereich: 🔧 Programmierung
🔗 Quelle: dev.to

If you've ever tried to add animations to your Flutter app, you know there's a fine line between delightful user experience and watching your app's performance graph take a nosedive. After spending countless hours debugging janky animations and memory leaks, I've compiled my hard-earned lessons into this guide.

The Animation Problem

Flutter offers incredible animation capabilities out of the box, but there's a catch: poorly implemented animations can destroy your app's performance. The biggest culprits I've encountered are:

  1. Rebuilding entire widget trees during animation
  2. Running too many simultaneous animations
  3. Using complex animations on low-end devices
  4. Failing to dispose of animation controllers

These issues become especially problematic when you're trying to create reusable animation components. Let's fix that.

Starting With the Basics: Animation Controllers

Every good Flutter animation starts with proper controller management. Here's my go-to pattern for creating a reusable, performance-friendly animated widget:

class OptimizedAnimatedWidget extends StatefulWidget {
  final Widget child;
  final Duration duration;

  const OptimizedAnimatedWidget({
    Key? key,
    required this.child,
    this.duration = const Duration(milliseconds: 300),
  }) : super(key: key);

  @override
  _OptimizedAnimatedWidgetState createState() => _OptimizedAnimatedWidgetState();
}

class _OptimizedAnimatedWidgetState extends State<OptimizedAnimatedWidget> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: widget.duration,
    );
    _animation = CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    );
    _controller.forward();
  }

  @override
  void dispose() {
    _controller.dispose(); // This is crucial!
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animation,
      builder: (context, child) {
        return Transform.scale(
          scale: _animation.value,
          child: child,
        );
      },
      child: widget.child, // This is our performance trick!
    );
  }
}

The secret sauce here is passing the child to AnimatedBuilder. This prevents Flutter from rebuilding the child on every animation frame, which is a common performance killer.

Technique #1: RepaintBoundary for Complex Animations

When your animations involve complex widgets, wrap them in a RepaintBoundary:

@override
Widget build(BuildContext context) {
  return AnimatedBuilder(
    animation: _animation,
    builder: (context, child) {
      return Transform.scale(
        scale: _animation.value,
        child: RepaintBoundary(child: child),
      );
    },
    child: widget.child,
  );
}

This creates a new "layer" for Flutter's rendering engine, preventing the entire widget tree from repainting on each animation frame.

Technique #2: Custom Tween Classes for Reusability

To make animations truly reusable, I create custom Tween classes:

class ShakeTween extends Tween<double> {
  ShakeTween({double begin = 0.0, double end = 10.0})
      : super(begin: begin, end: end);

  @override
  double lerp(double t) {
    // Custom shake animation
    if (t < 0.25) {
      return -sin(t * 4 * pi) * end! * (t * 4);
    } else if (t < 0.75) {
      return sin((t - 0.25) * 4 * pi) * end! * (0.75 - t) * 1.33;
    } else {
      return -sin((t - 0.75) * 4 * pi) * end! * (1 - t) * 4;
    }
  }
}

Now I can reuse this shake animation anywhere:

final Animation<double> _shakeAnimation = ShakeTween().animate(_controller);

Technique #3: Composable Animation Widgets

For truly reusable animations, I build composable widgets that can be stacked:

class FadeScale extends StatelessWidget {
  final Widget child;
  final Duration duration;
  final bool isActive;

  const FadeScale({
    Key? key,
    required this.child,
    this.duration = const Duration(milliseconds: 200),
    this.isActive = true,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return TweenAnimationBuilder<double>(
      tween: Tween<double>(begin: 0.0, end: isActive ? 1.0 : 0.0),
      duration: duration,
      curve: Curves.easeOut,
      builder: (context, value, child) {
        return Opacity(
          opacity: value,
          child: Transform.scale(
            scale: 0.8 + (value * 0.2),
            child: child,
          ),
        );
      },
      child: child, // This prevents unnecessary rebuilds
    );
  }
}

I can now use this anywhere in my app:

FadeScale(
  isActive: _isVisible,
  child: MyExpensiveWidget(),
)

Performance Testing Tips

After implementing these patterns, I always test animations on real devices (especially low-end Android phones) using the following:

  1. Timeline View: Enable "Track widget builds" in Flutter DevTools to see if widgets are rebuilding unnecessarily.

  2. Performance Overlay: Add MaterialApp(showPerformanceOverlay: true) to check for dropped frames.

  3. Memory Profiling: Watch memory usage during animations to catch leaks from undisposed controllers.

Real-World Example: A Reusable "Heart Beat" Animation

Here's a complete example of a performance-optimized, reusable heart beat animation I use in production:

class HeartBeat extends StatefulWidget {
  final Widget child;
  final Duration duration;
  final bool isAnimating;

  const HeartBeat({
    Key? key,
    required this.child,
    this.duration = const Duration(milliseconds: 1500),
    this.isAnimating = true,
  }) : super(key: key);

  @override
  _HeartBeatState createState() => _HeartBeatState();
}

class _HeartBeatState extends State<HeartBeat> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _scaleAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: widget.duration,
    );

    _scaleAnimation = TweenSequence<double>([
      TweenSequenceItem(tween: Tween<double>(begin: 1.0, end: 1.2), weight: 10),
      TweenSequenceItem(tween: Tween<double>(begin: 1.2, end: 1.0), weight: 10),
      TweenSequenceItem(tween: Tween<double>(begin: 1.0, end: 1.15), weight: 10),
      TweenSequenceItem(tween: Tween<double>(begin: 1.15, end: 1.0), weight: 10),
      TweenSequenceItem(tween: Tween<double>(begin: 1.0, end: 1.0), weight: 60),
    ]).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    ));

    if (widget.isAnimating) {
      _controller.repeat();
    }
  }

  @override
  void didUpdateWidget(HeartBeat oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.isAnimating != oldWidget.isAnimating) {
      if (widget.isAnimating) {
        _controller.repeat();
      } else {
        _controller.stop();
      }
    }
  }

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

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _scaleAnimation,
      builder: (context, child) {
        return Transform.scale(
          scale: _scaleAnimation.value,
          child: child,
        );
      },
      child: widget.child,
    );
  }
}

Conclusion

After implementing these techniques in multiple apps, I've seen dramatic performance improvements, especially on older devices. The key takeaways are:

  1. Prevent unnecessary rebuilds by using the child parameter in AnimatedBuilder
  2. Create custom Tweens for complex motion
  3. Use RepaintBoundary for complex widgets
  4. Always dispose your controllers
  5. Test on real, low-end devices

What animation challenges have you faced in your Flutter projects? I'd love to hear about them in the comments below!

...

🔧 Creating Custom, Reusable Animations in Flutter That Don't Kill Performance


📈 79.32 Punkte
🔧 Programmierung

🔧 Angular Animations Tutorial: Creating Reusable Animations


📈 51.04 Punkte
🔧 Programmierung

🔧 Angular Animations Tutorial: Creating Reusable Animations


📈 51.04 Punkte
🔧 Programmierung

🎥 Flutter Performance, Flutter Games, Flutter Tooling, &amp; More (#AskFlutter at Flutter Live)


📈 45.08 Punkte
🎥 Videos

🔧 Thất nghiệp tuổi 35


📈 39.71 Punkte
🔧 Programmierung

🔧 Create Reusable Toaster Notifications in React with Custom Animations


📈 36.99 Punkte
🔧 Programmierung

🐧 Why is kill() system call named kill if it doesn't kill but send a signal?


📈 32.83 Punkte
🐧 Linux Tipps

🔧 Creating Custom Hooks for Reusable Logic in React


📈 31.58 Punkte
🔧 Programmierung

🔧 Best Practices for Creating Reusable Custom Hooks in React


📈 31.58 Punkte
🔧 Programmierung

🔧 Lottie Animations in Flutter: Creating Engaging User Experiences


📈 31.31 Punkte
🔧 Programmierung

🔧 Lottie Animations in Flutter: Creating Engaging User Experiences


📈 31.31 Punkte
🔧 Programmierung

🎥 Create custom hero animations in Flutter With flightShuttleBuilder #Shorts


📈 30.76 Punkte
🎥 Video | Youtube

🔧 Flutter news from GDD China: uniting Flutter on web and mobile, and introducing Flutter 1.9


📈 29.25 Punkte
🔧 Programmierung

🎥 Origin of Flutter, Dart 2.0, E-Commerce with Flutter, &amp; More (#AskFlutter at Flutter Live)


📈 29.25 Punkte
🎥 Videos

🔧 Tailwind CSS: Creating Custom Animations


📈 29.08 Punkte
🔧 Programmierung

🔧 Creating Custom Lottie Animations With SVGator


📈 29.08 Punkte
🔧 Programmierung

🔧 Creating Custom Animations with React.js and GreenSock


📈 29.08 Punkte
🔧 Programmierung

🔧 Learn Flutter by creating your first Flutter app!


📈 27.57 Punkte
🔧 Programmierung

🎥 ScrollSpy animations with just CSS thanks to Scroll-Driven Animations


📈 26.98 Punkte
🎥 Video | Youtube

🔧 Pure CSS Scroll Animations Compared To CSS Scroll Animations With Trig.js


📈 26.98 Punkte
🔧 Programmierung

🔧 Elevate Your Web Animations with Trig.js: Introducing Version 4 and Trig-Animations.css


📈 26.98 Punkte
🔧 Programmierung

🔧 Reusable components in Flutter


📈 25.74 Punkte
🔧 Programmierung

🔧 Reusable Components in Flutter: Write Once, Use Everywhere! 🚀


📈 25.74 Punkte
🔧 Programmierung

🔧 Flutter reusable split view


📈 25.74 Punkte
🔧 Programmierung

🔧 Ultimate guide to Flutter Keys: Optimizing Your Flutter App's Performance


📈 25.58 Punkte
🔧 Programmierung

🔧 Creating Android Build for Flutter Apps with Custom Build Script (Part 2)


📈 25.34 Punkte
🔧 Programmierung

🔧 Creating iOS Build for Flutter Apps with Custom Build Script (Part 1)


📈 25.34 Punkte
🔧 Programmierung

🔧 Creating Custom PDFs in Flutter


📈 25.34 Punkte
🔧 Programmierung

🔧 🌟 Creating Reusable Components with TypeScript in Next.js: A Sidebar Example 📚


📈 24.06 Punkte
🔧 Programmierung

🔧 Part Two: Creating Our Recipe Reusable Skeleton Component


📈 24.06 Punkte
🔧 Programmierung

🔧 Creating a Reusable Component in React: Handling Unlimited Future Changes


📈 24.06 Punkte
🔧 Programmierung

🔧 🛠️ Vue Tip: Creating Reusable Components with Slots


📈 24.06 Punkte
🔧 Programmierung

🔧 Compound components pattern for creating reusable Rating component


📈 24.06 Punkte
🔧 Programmierung

🎥 Creating reusable GitHub Action workflows for Bicep


📈 24.06 Punkte
🎥 Video | Youtube