image

31 Mar 2026

9K

35K

Flutter Fade & Scale Animations for Interactive Toast Notifications

Toast notifications are a ubiquitous component in modern applications, providing non-intrusive feedback to users. While a simple static toast serves its purpose, incorporating smooth animations significantly elevates the user experience. This article delves into creating interactive toast notifications in Flutter, utilizing fade and scale animations to deliver a polished and engaging visual feedback mechanism.

We will build a custom toast solution that features:

  • Smooth entry and exit animations (fade and scale).
  • Automatic dismissal after a set duration.
  • Interactive dismissal by tapping the toast.
  • Display over existing content using OverlayEntry.

Understanding the Core Components

Before diving into the code, let's briefly understand the key Flutter concepts we'll be leveraging:

  • OverlayEntry: Allows us to insert a widget tree on top of all other widgets in the Overlay widget tree. This is perfect for displaying toasts that need to appear above the regular app content.
  • StatefulWidget with SingleTickerProviderStateMixin: Our custom toast widget will be stateful to manage its animation lifecycle. The mixin provides a Ticker needed for AnimationController.
  • AnimationController: Manages the animation's playback (e.g., forward, reverse, duration).
  • Tween and CurvedAnimation: Define the range of values for our animation (e.g., 0.0 to 1.0 for opacity) and the curve of the animation (e.g., Curves.easeOut, Curves.bounceOut).
  • FadeTransition and ScaleTransition: Widgets that automatically apply opacity and scale transformations to their children based on an Animation object.
  • GestureDetector: Enables user interaction, such as tapping, to dismiss the toast early.

Crafting the Interactive Animated Toast Widget

First, let's create a custom widget, CustomToast, that encapsulates our toast's appearance, animation logic, and interactivity. This widget will manage its own show and hide animations and provide a callback when it's fully dismissed.


import 'package:flutter/material.dart';

class CustomToast extends StatefulWidget {
  final String message;
  final Duration duration;
  final VoidCallback onDismissed;

  const CustomToast({
    Key? key,
    required this.message,
    this.duration = const Duration(seconds: 3),
    required this.onDismissed,
  }) : super(key: key);

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

class _CustomToastState extends State with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation _fadeAnimation;
  late Animation _scaleAnimation;

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

    // Fade animation from transparent to opaque
    _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeOut),
    );
    // Scale animation from slightly smaller to full size, with a bounce effect
    _scaleAnimation = Tween(begin: 0.8, end: 1.0).animate(
      CurvedAnimation(parent: _controller, curve: Curves.bounceOut),
    );

    // Start the entry animation
    _controller.forward();

    // Set a timer for automatic dismissal
    Future.delayed(widget.duration, () {
      if (mounted) { // Ensure the widget is still in the tree
        _controller.reverse().then((_) {
          widget.onDismissed(); // Notify parent that toast is fully dismissed
        });
      }
    });
  }

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

  // Method to manually dismiss the toast
  void _dismissToast() {
    if (mounted) {
      _controller.reverse().then((_) {
        widget.onDismissed();
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Positioned(
      bottom: 50.0, // Position the toast near the bottom
      left: 20.0,
      right: 20.0,
      child: GestureDetector(
        onTap: _dismissToast, // Make the toast interactively dismissible
        child: FadeTransition(
          opacity: _fadeAnimation,
          child: ScaleTransition(
            scale: _scaleAnimation,
            child: Material(
              color: Colors.transparent, // Important for consistent styling and hit testing
              child: Container(
                padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0),
                decoration: BoxDecoration(
                  color: Colors.black87,
                  borderRadius: BorderRadius.circular(25.0),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black.withOpacity(0.2),
                      blurRadius: 10,
                      offset: const Offset(0, 5),
                    ),
                  ],
                ),
                child: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    const Icon(Icons.info_outline, color: Colors.white),
                    const SizedBox(width: 8.0),
                    Expanded(
                      child: Text(
                        widget.message,
                        style: const TextStyle(color: Colors.white, fontSize: 16.0),
                        maxLines: 2,
                        overflow: TextOverflow.ellipsis,
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

Implementing a Toast Service with OverlayEntry

To display our CustomToast over all other widgets and manage its lifecycle (showing and dismissing), we'll implement a simple static service class. This service will handle the creation and removal of OverlayEntry instances.


import 'package:flutter/material.dart';
// Assuming CustomToast is in a separate file (e.g., 'custom_toast.dart')
// import 'custom_toast.dart'; 

class ToastService {
  static OverlayEntry? _currentOverlayEntry;

  static void show(BuildContext context, String message, {Duration duration = const Duration(seconds: 3)}) {
    // Dismiss any existing toast first to prevent multiple toasts
    dismiss();

    _currentOverlayEntry = OverlayEntry(
      builder: (context) => CustomToast(
        message: message,
        duration: duration,
        onDismissed: () {
          // This callback is triggered when CustomToast's exit animation completes
          dismiss();
        },
      ),
    );

    // Insert the OverlayEntry into the Overlay
    Overlay.of(context).insert(_currentOverlayEntry!);
  }

  static void dismiss() {
    // Remove the OverlayEntry if it exists
    _currentOverlayEntry?.remove();
    _currentOverlayEntry = null;
  }
}

Using the Toast Notification Service

Finally, integrating the toast service into your application is straightforward. You can call ToastService.show() from anywhere you have access to a BuildContext.


import 'package:flutter/material.dart';
// Assuming ToastService is in a separate file (e.g., 'toast_service.dart')
// import 'toast_service.dart'; 

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Interactive Toast',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Interactive Toast Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                ToastService.show(
                  context, 
                  'This is a success message! Tap to dismiss early.', 
                  duration: const Duration(seconds: 4)
                );
              },
              child: const Text('Show Success Toast'),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                ToastService.show(
                  context, 
                  'A warning occurred. Please review your input carefully.', 
                  duration: const Duration(seconds: 5)
                );
              },
              child: const Text('Show Warning Toast'),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                ToastService.show(
                  context, 
                  'User settings updated!', 
                  duration: const Duration(seconds: 2)
                );
              },
              child: const Text('Show Quick Toast'),
            ),
          ],
        ),
      ),
    );
  }
}

Conclusion

By combining Flutter's powerful animation framework with OverlayEntry, we've successfully created an interactive toast notification system that is both visually appealing and user-friendly. The fade and scale animations provide a fluid entry and exit, while the ability to dismiss the toast with a tap enhances the user's control over their interface. This pattern can be extended further to include different toast types (success, error, info), custom icons, and more complex animation sequences, allowing for highly personalized and engaging user feedback across your Flutter applications.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is