Creating an Expandable Card Widget for Product Descriptions in Flutter
In the dynamic world of e-commerce and mobile applications, presenting product information effectively is crucial. Product descriptions often need to be comprehensive, but displaying a large block of text upfront can overwhelm users and clutter the interface. A common and elegant solution to this challenge is to use an expandable card widget. This allows users to see a summary and then expand to view the full details only when they choose to, leading to a cleaner UI and improved user experience.
Why an Expandable Card?
- Enhanced UI/UX: Keeps the initial screen clean and focused on essential information.
- Efficient Space Usage: Maximizes screen real estate, especially on smaller mobile devices.
- User Control: Gives users the power to decide how much information they want to consume.
- Better Readability: Breaks down long texts into manageable, on-demand sections.
Core Concepts for Implementation
To build an expandable card in Flutter, we'll utilize a few key widgets and concepts:
StatefulWidget: Essential for managing the expanded/collapsed state of the card.Card: Provides a visually distinct container for the product description.Column: Used to stack the header (title and toggle icon) and the expandable content.InkWellorGestureDetector: To detect taps on the card header and toggle its state.AnimatedSize: Creates a smooth animation for the size change when the card expands or collapses.Visibility: Conditionally renders the description text based on the expansion state.AnimatedRotation: Adds a subtle rotation animation to the expansion icon for better visual feedback.
Building the Widget Step-by-Step
Let's walk through the process of creating our ExpandableProductDescriptionCard widget.
1. Basic Structure and State Management
We'll start with a StatefulWidget to hold the _isExpanded boolean state and a method to toggle it.
2. Header and Toggle Icon
The card's header will contain the title and an icon (e.g., a down arrow) that rotates to indicate the expansion state. We'll wrap this in an InkWell to make it tappable.
3. Expandable Content
The actual product description text will be placed inside a Visibility widget, which in turn is wrapped by AnimatedSize to animate its appearance and disappearance.
Full Code Example
Here's the complete code for our ExpandableProductDescriptionCard and an example of how to use it in a Flutter application.
import 'package:flutter/material.dart';
class ExpandableProductDescriptionCard extends StatefulWidget {
final String title;
final String description;
const ExpandableProductDescriptionCard({
Key? key,
required this.title,
required this.description,
}) : super(key: key);
@override
_ExpandableProductDescriptionCardState createState() =>
_ExpandableProductDescriptionCardState();
}
class _ExpandableProductDescriptionCardState
extends State {
bool _isExpanded = false;
void _toggleExpanded() {
setState(() {
_isExpanded = !_isExpanded;
});
}
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header Row: Contains title and toggle icon
InkWell(
onTap: _toggleExpanded,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
widget.title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
AnimatedRotation(
turns: _isExpanded ? 0.5 : 0.0, // Rotate 180 degrees
duration: const Duration(milliseconds: 300),
child: const Icon(Icons.keyboard_arrow_down),
),
],
),
),
),
// Expandable Content: Description text
AnimatedSize(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
child: Visibility(
visible: _isExpanded,
// The key prevents issues when content changes rapidly if multiple such widgets exist
key: ValueKey(_isExpanded),
child: Padding(
padding: const EdgeInsets.fromLTRB(16.0, 0, 16.0, 16.0),
child: Text(
widget.description,
style: const TextStyle(fontSize: 16),
),
),
),
),
],
),
);
}
}
// Example Usage in main.dart
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Expandable Card Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Scaffold(
appBar: AppBar(
title: const Text('Product Details'),
),
body: SingleChildScrollView(
child: Column(
children: [
ExpandableProductDescriptionCard(
title: 'Product Features',
description:
'This is a comprehensive list of all the amazing features of our product. It includes high-quality materials, durable design, long-lasting battery life, and smart connectivity options. Enjoy seamless integration with your other devices and an intuitive user experience. The product also boasts a sleek, ergonomic design for maximum comfort and style. Ideal for both professional and casual use, it stands out with its innovative technology and robust performance.',
),
ExpandableProductDescriptionCard(
title: 'Technical Specifications',
description:
'Weight: 250g, Dimensions: 10cm x 5cm x 2cm, Battery: 3000mAh Li-ion, Connectivity: Bluetooth 5.0, Wi-Fi 802.11ac, USB-C. Processor: Octa-core 2.5GHz, RAM: 8GB, Storage: 128GB. Display: 6.5-inch AMOLED, 1080p resolution. Operating System: Android 12.',
),
ExpandableProductDescriptionCard(
title: 'Customer Reviews',
description:
'Our customers love this product! With an average rating of 4.8 stars, users praise its ease of use, robust performance, and stylish design. Many highlight the exceptional battery life and quick charging capabilities as major advantages. See what others are saying and join our growing community of satisfied users.',
),
],
),
),
),
);
}
}
Code Explanation
ExpandableProductDescriptionCard: This is ourStatefulWidget, takingtitleanddescriptionas required parameters._ExpandableProductDescriptionCardState: Manages the internal state._isExpanded: A boolean variable that determines if the description is currently visible._toggleExpanded(): A method called when the header is tapped. It flips the value of_isExpandedand callssetState()to rebuild the widget.
Card: Provides the material design card look and feel, with a default elevation and rounded corners.InkWell: Wraps the header `Row` and makes it responsive to taps, providing a visual ripple effect.- Header
Row: Contains thetitleof the card and anIcon.Expanded(child: Text(...)): Ensures the title takes up available space, pushing the icon to the right.AnimatedRotation: Rotates theIcons.keyboard_arrow_downicon by 180 degrees (0.5 turns) when_isExpandedis true, providing a smooth arrow-down/arrow-up animation.
AnimatedSize: This is crucial for the smooth animation. It wraps theVisibilitywidget and automatically animates its size change (height) when its child's size changes. It has adurationandcurvefor customizing the animation speed and style.Visibility: Controls whether theTextwidget containing thedescriptionis rendered. Whenvisibleisfalse, the widget is effectively removed from the render tree, allowingAnimatedSizeto collapse. TheValueKey(_isExpanded)helps Flutter optimize widget tree updates.Padding: Used to add appropriate spacing around the title and description for better aesthetics.
Conclusion
Creating an expandable card widget for product descriptions in Flutter is an effective way to enhance your application's user interface and experience. By combining Flutter's powerful layout widgets with state management and animation capabilities, you can build a highly functional and aesthetically pleasing component that efficiently presents information. This pattern is versatile and can be adapted for various other use cases where content needs to be revealed on demand.