Building a Product Quick View Modal with Animation and Details in Flutter
In the competitive world of e-commerce, user experience is paramount. One feature that significantly enhances the shopping journey is a "Product Quick View" modal. This allows users to get essential product information and make quick decisions without navigating away from the main product listing page. This article will guide you through building a professional Product Quick View modal in Flutter, complete with smooth animations and comprehensive details.
Why a Quick View Modal?
- Improved User Experience: Users can preview product details without full page loads.
- Faster Decision Making: Key information is presented concisely, reducing friction in the buying process.
- Engaging Interaction: Animations and interactive elements make the experience more dynamic.
Prerequisites
To follow along, you should have a basic understanding of Flutter development, including widgets, state management (stateless/stateful widgets), and routing.
Step 1: Define the Product Model
First, let's create a simple Dart class to represent our product data. This model will hold all the information we want to display in our quick view modal.
class Product {
final String id;
final String name;
final String description;
final double price;
final String imageUrl;
Product({
required this.id,
required this.name,
required this.description,
required this.price,
required this.imageUrl,
});
}
Step 2: Create the Product Card Widget
Next, we'll build a widget to display each product in a grid or list. This widget will also contain a button to trigger our quick view modal. We'll introduce the
Hero widget here to enable a beautiful shared-element animation for the product image.
import 'package:flutter/material.dart';
// Assume Product model is defined in product_model.dart or above
class ProductCard extends StatelessWidget {
final Product product;
final VoidCallback onQuickViewPressed;
const ProductCard({
Key? key,
required this.product,
required this.onQuickViewPressed,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
elevation: 2,
margin: const EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Hero(
tag: 'productImage-${product.id}', // Unique tag for Hero animation
child: Image.network(
product.imageUrl,
height: 120,
width: double.infinity,
fit: BoxFit.cover,
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
product.name,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
Text('\$${product.price.toStringAsFixed(2)}'),
Align(
alignment: Alignment.centerRight,
child: ElevatedButton(
onPressed: onQuickViewPressed,
child: const Text('Quick View'),
),
),
],
),
),
],
),
);
}
}
Step 3: Implement the Quick View Modal Widget
For the quick view modal, we'll use
showModalBottomSheet combined with DraggableScrollableSheet. This combination provides a native-feeling bottom sheet that users can drag up and down, and it also comes with built-in entry/exit animations. The Hero widget will be used again here to complete the image animation.
import 'package:flutter/material.dart';
// Assume Product model is defined in product_model.dart or above
class ProductQuickViewModal extends StatelessWidget {
final Product product;
const ProductQuickViewModal({
Key? key,
required this.product,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return DraggableScrollableSheet(
initialChildSize: 0.8, // Start at 80% height
minChildSize: 0.5,
maxChildSize: 1.0,
expand: false,
builder: (context, scrollController) {
return Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
child: Column(
children: [
// Drag handle
Container(
margin: const EdgeInsets.symmetric(vertical: 10),
height: 4,
width: 40,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(2),
),
),
Expanded(
child: ListView(
controller: scrollController,
padding: const EdgeInsets.all(16.0),
children: [
Align(
alignment: Alignment.topRight,
child: IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.of(context).pop(),
),
),
Hero(
tag: 'productImage-${product.id}', // Must match the tag in ProductCard
child: Image.network(
product.imageUrl,
height: 250,
width: double.infinity,
fit: BoxFit.contain, // Changed to contain for better modal view
),
),
const SizedBox(height: 16),
Text(
product.name,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
'\$${product.price.toStringAsFixed(2)}',
style: TextStyle(
fontSize: 20,
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 16),
Text(
product.description,
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: () {
// Handle Add to Cart logic
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('${product.name} added to cart!')),
);
Navigator.of(context).pop(); // Close modal
},
icon: const Icon(Icons.shopping_cart),
label: const Text('Add to Cart'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
textStyle: const TextStyle(fontSize: 18),
),
),
),
const SizedBox(height: 20), // Padding for the bottom
],
),
),
],
),
);
},
);
}
}
Step 4: Integrating and Animating
Now, let's put it all together in a main application screen that displays a grid of products. When the "Quick View" button is pressed, the
_showProductQuickView function will be called, which then uses showModalBottomSheet to present our ProductQuickViewModal.
The
Hero widgets automatically handle the animation between the image in the ProductCard and the image in the ProductQuickViewModal, as long as they share the same tag. The showModalBottomSheet and DraggableScrollableSheet provide the bottom-up entry animation and drag functionality.
import 'package:flutter/material.dart';
// Import your Product model and widgets
// import 'product_model.dart';
// import 'product_card.dart';
// import 'product_quick_view_modal.dart';
class ProductGridScreen extends StatelessWidget {
final List products = [
Product(
id: '1',
name: 'Wireless Headphones',
description: 'Experience crystal clear audio with these premium wireless headphones. Long-lasting battery, active noise cancellation, and comfortable design for all-day wear.',
price: 129.99,
imageUrl: 'https://via.placeholder.com/150/FF0000/FFFFFF?text=Headphones',
),
Product(
id: '2',
name: 'Smart Watch',
description: 'Stay connected and track your fitness goals with this stylish smart watch. Features include heart rate monitoring, GPS, and notifications for calls and messages.',
price: 89.99,
imageUrl: 'https://via.placeholder.com/150/0000FF/FFFFFF?text=Smart+Watch',
),
Product(
id: '3',
name: 'Portable Speaker',
description: 'Take your music anywhere with this compact and powerful portable speaker. Waterproof design for outdoor adventures, rich bass, and 10 hours of playtime.',
price: 49.99,
imageUrl: 'https://via.placeholder.com/150/00FF00/FFFFFF?text=Speaker',
),
Product(
id: '4',
name: 'E-reader',
description: 'Enjoy reading on the go with this lightweight e-reader. Glare-free display, adjustable front light, and weeks of battery life.',
price: 99.99,
imageUrl: 'https://via.placeholder.com/150/FFFF00/000000?text=E-reader',
),
];
ProductGridScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Our Products'),
),
body: GridView.builder(
padding: const EdgeInsets.all(8.0),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8.0,
mainAxisSpacing: 8.0,
childAspectRatio: 0.7, // Adjust as needed
),
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return ProductCard(
product: product,
onQuickViewPressed: () {
_showProductQuickView(context, product);
},
);
},
),
);
}
void _showProductQuickView(BuildContext context, Product product) {
showModalBottomSheet(
context: context,
isScrollControlled: true, // Allows modal to take up more screen space
backgroundColor: Colors.transparent, // Ensures rounded corners are visible
builder: (context) {
return ProductQuickViewModal(product: product);
},
);
}
}
void main() {
runApp(
MaterialApp(
title: 'Product Quick View Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: ProductGridScreen(),
),
);
}
Conclusion
You have successfully built a sophisticated Product Quick View modal in Flutter. By combining
showModalBottomSheet with DraggableScrollableSheet for an interactive, animated modal, and leveraging the Hero widget for seamless image transitions, you've created a highly engaging and user-friendly component. This pattern can be easily adapted and extended for various e-commerce applications, significantly improving the overall user experience.