Mastering Slide & Scale Animations in Flutter: A Comprehensive Guide

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

Aug 02, 2026

Building a Flutter Notification Center with Grouping & Actions

Learn to create a sophisticated in-app notification center in Flutter, featuring grouping, swipe-to-dismiss, custom icons, and actionable buttons for enhanced u

Aug 01, 2026

Flutter Layout Tips: Using FractionallySizedBox, AspectRatio, ConstrainedBox for Adaptive UI

Master Flutter's FractionallySizedBox, AspectRatio, and ConstrainedBox to build truly adaptive UIs. Learn how to control widget sizing, maintain proportions, an

Aug 01, 2026

Flutter Product Quick View: Animation, Related Items, Badges

Learn to create a dynamic product quick view widget in Flutter, incorporating smooth animations, relevant related items, and eye-catching promo badges for an en