image

14 Mar 2026

9K

35K

Flutter Slide & Scale Animation on Card Tap and Smooth Modal Transitions

Introduction

This article explores how to implement engaging slide and scale animations on card taps and create seamless modal transitions in Flutter applications. These animation techniques significantly enhance user experience by providing visual feedback and guiding the user's attention, making the application feel more polished and intuitive.

1. Slide & Scale Animation on Card Tap

Implementing a subtle slide and scale animation when a user taps on a card provides immediate visual feedback, making the UI feel more interactive and responsive. We'll use an AnimationController along with ScaleTransition and SlideTransition widgets to achieve this effect.

Card Animation Example

First, define a stateful widget that manages the animation controller and applies the transformations. This widget will wrap any child widget (e.g., a Card widget) and animate it on tap gestures.

import 'package:flutter/material.dart';

class AnimatedCard extends StatefulWidget {
  final Widget child;
  final VoidCallback onTap;
  const AnimatedCard({Key? key, required this.child, required this.onTap}) : super(key: key);

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

class _AnimatedCardState extends State with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation _scaleAnimation;
  late Animation _slideAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(milliseconds: 150),
      vsync: this,
    );
    _scaleAnimation = Tween(begin: 1.0, end: 0.95).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeOut)
    );
    _slideAnimation = Tween(begin: Offset.zero, end: const Offset(0, 0.02)).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeOut)
    );
  }

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

  void _onTapDown(_) {
    _controller.forward();
  }

  void _onTapUp(_) {
    _controller.reverse();
    widget.onTap();
  }

  void _onTapCancel() {
    _controller.reverse();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTapDown: _onTapDown,
      onTapUp: _onTapUp,
      onTapCancel: _onTapCancel,
      onTap: () {}, // Handled by onTapUp after animation completes
      child: ScaleTransition(
        scale: _scaleAnimation,
        child: SlideTransition(
          position: _slideAnimation,
          child: widget.child,
        ),
      ),
    );
  }
}
To use this AnimatedCard widget, simply wrap your existing Card widget with it:

// Inside a StatefulWidget or StatelessWidget's build method
AnimatedCard(
  onTap: () {
    print('Card tapped!');
    // This is where you might navigate to a detail screen
    // Navigator.push(context, MaterialPageRoute(builder: (context) => DetailScreen()));
  },
  child: Card(
    elevation: 4.0,
    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)),
    child: Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: const [
          Text('Hello Flutter!', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
          SizedBox(height: 8),
          Text('Tap me for an animation!'),
        ],
      ),
    ),
  ),
)

2. Smooth Modal Transition with Hero Animation

For a truly seamless user experience, a tapped card should visually transition into a full-screen modal or detail view. Flutter's Hero widget is perfect for this, enabling shared element transitions between screens.

Source Card (Home Screen)

Wrap the element you want to animate (e.g., an image or the entire card content) in a Hero widget. Ensure it has a unique tag property.

// In your home screen or list of cards
AnimatedCard(
  onTap: () {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => DetailScreen(heroTag: 'myHeroTag', title: 'Flutter Animation Example'),
      ),
    );
  },
  child: Hero(
    tag: 'myHeroTag', // Must be unique across all Hero widgets on both routes
    child: Card(
      elevation: 4.0,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)),
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: const [
            // Hero needs a Material ancestor if it wraps Text to correctly inherit styles
            Material(
              color: Colors.transparent,
              child: Text('Flutter Animation Example', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
            ),
            SizedBox(height: 8),
            Material(
              color: Colors.transparent,
              child: Text('Tap to see the detail screen with Hero transition.'),
            ),
          ],
        ),
      ),
    ),
  ),
)

Destination Screen (Detail View)

On the detail screen, place a Hero widget with the *same* tag property around the corresponding element. Flutter will automatically animate the element's transition between the two screens.

import 'package:flutter/material.dart';

class DetailScreen extends StatelessWidget {
  final String heroTag;
  final String title;

  const DetailScreen({Key? key, required this.heroTag, required this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Detail View'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Hero(
              tag: heroTag,
              child: Material(
                color: Colors.transparent, // Important for Hero wrapping Text to avoid default Material background
                child: Text(
                  title,
                  style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
                ),
              ),
            ),
            const SizedBox(height: 20),
            const Padding(
              padding: EdgeInsets.symmetric(horizontal: 24.0),
              child: Text(
                'This is the detail content of the card. The title smoothly transitioned from the previous screen using a Hero animation.',
                textAlign: TextAlign.center,
                style: TextStyle(fontSize: 16),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
For Hero widgets containing Text or other widgets that rely on theming, it's often necessary to wrap the content of the Hero in a Material widget with color: Colors.transparent. This ensures the text styles are correctly inherited and the animation appears as expected.

Conclusion

By combining subtle interactive animations on card taps with powerful shared element transitions like Hero widgets, you can create highly polished and intuitive Flutter applications. These techniques not only make your app look professional but also significantly improve the overall user experience by providing clear visual cues and delightful interactions.

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