image

05 Apr 2026

9K

35K

Mastering Slide & Scale Animations in Flutter: A Comprehensive Guide

Flutter's declarative UI and robust animation framework empower developers to create visually rich and engaging user experiences. Among the myriad of animation possibilities, "Slide & Scale" effects stand out for their elegance and ability to convey depth and focus. This article will guide you through implementing professional-grade slide and scale animations for common UI patterns: Card Tap, Modal Transitions, Page Transitions, and Notification Animations.

1. Card Tap Animation: Interactive Slide & Scale

A "Slide & Scale" animation on a card tap can provide immediate visual feedback, indicating that the card is becoming active or expanding to reveal more content. This effect draws the user's attention and creates a dynamic interface.

Implementation Details:

We'll use an `AnimatedContainer` to smoothly transition between different states (scaled and potentially slightly shifted), triggered by a `GestureDetector`.


import 'package:flutter/material.dart';

class CardTapAnimationScreen extends StatefulWidget {
  @override
  _CardTapAnimationScreenState createState() => _CardTapAnimationScreenState();
}

class _CardTapAnimationScreenState extends State {
  double _scale = 1.0;
  AlignmentGeometry _alignment = Alignment.center; // For slide effect

  void _handleTap() {
    setState(() {
      _scale = _scale == 1.0 ? 1.1 : 1.0; // Scale up on tap, scale down on second tap
      _alignment = _scale == 1.0 ? Alignment.center : Alignment.topCenter; // Slide up slightly
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Card Tap Animation')),
      body: Center(
        child: GestureDetector(
          onTap: _handleTap,
          child: AnimatedContainer(
            duration: Duration(milliseconds: 300),
            curve: Curves.easeInOut,
            width: 200,
            height: 150,
            alignment: _alignment, // Apply alignment for slide
            transform: Matrix4.identity()..scale(_scale), // Apply scale
            decoration: BoxDecoration(
              color: Colors.blueAccent,
              borderRadius: BorderRadius.circular(15),
              boxShadow: _scale == 1.0
                  ? [
                      BoxShadow(
                        color: Colors.black.withOpacity(0.2),
                        spreadRadius: 2,
                        blurRadius: 5,
                        offset: Offset(0, 3),
                      ),
                    ]
                  : [
                      BoxShadow(
                        color: Colors.blueAccent.withOpacity(0.6),
                        spreadRadius: 4,
                        blurRadius: 10,
                        offset: Offset(0, 5),
                      ),
                    ],
            ),
            child: Center(
              child: Text(
                'Tap Me!',
                style: TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

2. Modal Transition: Elegant Entry for Dialogs

When a modal dialog or bottom sheet appears, a sudden pop can be jarring. A "Slide & Scale" transition makes its appearance feel more natural and integrated into the user flow. Flutter's `showGeneralDialog` provides an excellent way to customize these transitions.

Implementation Details:

We'll use `showGeneralDialog` and its `transitionBuilder` to combine `ScaleTransition` and `SlideTransition` for a smooth entry animation.


import 'package:flutter/material.dart';

class ModalTransitionScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Modal Transition')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            _showAnimatedModal(context);
          },
          child: Text('Show Animated Modal'),
        ),
      ),
    );
  }

  void _showAnimatedModal(BuildContext context) {
    showGeneralDialog(
      context: context,
      barrierColor: Colors.black.withOpacity(0.5), // Background color
      transitionDuration: Duration(milliseconds: 400),
      pageBuilder: (context, anim1, anim2) {
        return Center(
          child: Material(
            borderRadius: BorderRadius.circular(16),
            child: Container(
              width: MediaQuery.of(context).size.width * 0.8,
              height: 250,
              padding: EdgeInsets.all(20),
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(16),
              ),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(
                    'Welcome to the Modal!',
                    style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
                  ),
                  SizedBox(height: 15),
                  Text('This modal appeared with a beautiful slide and scale animation.'),
                  SizedBox(height: 25),
                  ElevatedButton(
                    onPressed: () {
                      Navigator.of(context).pop();
                    },
                    child: Text('Close'),
                  ),
                ],
              ),
            ),
          ),
        );
      },
      transitionBuilder: (context, anim1, anim2, child) {
        // Combine slide and scale animations
        var slideTween = Tween(
          begin: Offset(0.0, 1.0), // Start from bottom
          end: Offset.zero,       // End at center
        ).animate(CurvedAnimation(
          parent: anim1,
          curve: Curves.easeOutBack, // A nice bouncy curve for entry
        ));

        var scaleTween = Tween(
          begin: 0.5, // Start smaller
          end: 1.0,   // End at full size
        ).animate(CurvedAnimation(
          parent: anim1,
          curve: Curves.easeOutBack,
        ));

        return SlideTransition(
          position: slideTween,
          child: ScaleTransition(
            scale: scaleTween,
            child: child,
          ),
        );
      },
    );
  }
}

3. Page Transition: Smooth Navigation Experience

Navigating between pages is a fundamental interaction. Custom page transitions, especially those incorporating slide and scale, can significantly enhance the user's perception of fluidity and spatial relationships within the app. `PageRouteBuilder` is your tool for this.

Implementation Details:

We'll create a custom `PageRouteBuilder` to wrap our new page with `ScaleTransition` and `SlideTransition` during navigation.


import 'package:flutter/material.dart';

// First Page
class PageTransitionScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Page Transition')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.push(
              context,
              _buildSlideScalePageRoute(SecondPage()),
            );
          },
          child: Text('Go to Second Page'),
        ),
      ),
    );
  }

  PageRouteBuilder _buildSlideScalePageRoute(Widget page) {
    return PageRouteBuilder(
      pageBuilder: (context, animation, secondaryAnimation) => page,
      transitionsBuilder: (context, animation, secondaryAnimation, child) {
        // Slide from right
        var slideTween = Tween(
          begin: Offset(1.0, 0.0), // Start from right
          end: Offset.zero,
        ).animate(CurvedAnimation(
          parent: animation,
          curve: Curves.easeOutCubic, // A smooth entry curve
        ));

        // Scale from a slightly smaller size
        var scaleTween = Tween(
          begin: 0.9,
          end: 1.0,
        ).animate(CurvedAnimation(
          parent: animation,
          curve: Curves.easeOutCubic,
        ));

        return SlideTransition(
          position: slideTween,
          child: ScaleTransition(
            scale: scaleTween,
            child: child,
          ),
        );
      },
      transitionDuration: Duration(milliseconds: 500),
    );
  }
}

// Second Page
class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Second Page')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'You arrived with style!',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                Navigator.pop(context);
              },
              child: Text('Go Back'),
            ),
          ],
        ),
      ),
    );
  }
}

4. Notification Animation: Dynamic Alerts

Transient notifications (like toast messages or in-app banners) benefit greatly from "Slide & Scale" animations. This makes them appear and disappear smoothly, catching the user's eye without being overly intrusive.

Implementation Details:

We'll use an `OverlayEntry` for a true system-level notification, driven by an `AnimationController` that manages both slide and scale transitions.


import 'package:flutter/material.dart';

class NotificationAnimationScreen extends StatefulWidget {
  @override
  _NotificationAnimationScreenState createState() => _NotificationAnimationScreenState();
}

class _NotificationAnimationScreenState extends State with SingleTickerProviderStateMixin {
  OverlayEntry? _overlayEntry;
  late AnimationController _animationController;
  late Animation _slideAnimation;
  late Animation _scaleAnimation;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 500),
    );

    _slideAnimation = Tween(
      begin: Offset(0, -1.5), // Start far above the screen
      end: Offset(0, 0),    // Slide to its final position (e.g., top-center)
    ).animate(CurvedAnimation(
      parent: _animationController,
      curve: Curves.easeOutBack,
    ));

    _scaleAnimation = Tween(
      begin: 0.5, // Start smaller
      end: 1.0,   // Grow to full size
    ).animate(CurvedAnimation(
      parent: _animationController,
      curve: Curves.easeOutBack,
    ));
  }

  @override
  void dispose() {
    _animationController.dispose();
    _overlayEntry?.remove();
    super.dispose();
  }

  void _showNotification() {
    if (_overlayEntry != null) return; // Prevent multiple notifications

    _overlayEntry = OverlayEntry(
      builder: (context) => Positioned(
        top: 50, // Position from top
        left: MediaQuery.of(context).size.width * 0.1,
        width: MediaQuery.of(context).size.width * 0.8,
        child: SlideTransition(
          position: _slideAnimation,
          child: ScaleTransition(
            scale: _scaleAnimation,
            child: Material(
              color: Colors.transparent, // Make Material transparent
              child: Container(
                padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
                decoration: BoxDecoration(
                  color: Colors.green,
                  borderRadius: BorderRadius.circular(10),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black.withOpacity(0.2),
                      blurRadius: 8,
                      offset: Offset(0, 4),
                    ),
                  ],
                ),
                child: Row(
                  children: [
                    Icon(Icons.check_circle_outline, color: Colors.white),
                    SizedBox(width: 10),
                    Expanded(
                      child: Text(
                        'Item added to cart!',
                        style: TextStyle(color: Colors.white, fontSize: 16),
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
    );

    Overlay.of(context)!.insert(_overlayEntry!);
    _animationController.forward().then((_) {
      Future.delayed(Duration(seconds: 2), () {
        _hideNotification();
      });
    });
  }

  void _hideNotification() {
    if (_overlayEntry == null) return;

    _animationController.reverse().then((_) {
      _overlayEntry?.remove();
      _overlayEntry = null;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Notification Animation')),
      body: Center(
        child: ElevatedButton(
          onPressed: _showNotification,
          child: Text('Show Notification'),
        ),
      ),
    );
  }
}

Conclusion

Slide and Scale animations are incredibly versatile and can elevate the user experience across various components in a Flutter application. By strategically applying these effects to card interactions, modal dialogues, page transitions, and notifications, developers can create a more intuitive, engaging, and professional-feeling app. Remember to use appropriate `Curves` and `Durations` to fine-tune the feel of your animations, making them both smooth and responsive.

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