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.