DropDownMenuAppi
An advanced dropdown menu widget with animated transitions, custom positioning, and flexible content support for creating interactive menu experiences.
Overview
DropDownMenuAppi
combines the power of animated transitions with flexible dropdown functionality. It provides a customizable parent widget trigger with an animated dropdown menu that can contain any widgets, making it perfect for complex menu interactions and custom dropdown experiences.
Features
- 🎨 Animated Transitions - Smooth color, size, and radius animations
- 📍 Custom Positioning - Precise offset control for dropdown placement
- 🎯 Flexible Content - Support for any widget as menu items
- 🖱️ Interactive Triggers - Customizable parent widget as trigger
- 🎨 Custom Styling - Full control over colors, dimensions, and elevation
- 💫 Hover Effects - Built-in hover state management
- 🔧 Tooltip Support - Optional tooltip for accessibility
- 📱 Responsive Design - Adaptive sizing and positioning
Basic Usage
DropDownMenuAppi(
parentWidget: Icon(Icons.more_vert),
offset: Offset(0, 40),
children: [
ListTile(
leading: Icon(Icons.edit),
title: Text('Edit'),
onTap: () => handleEdit(),
),
ListTile(
leading: Icon(Icons.delete),
title: Text('Delete'),
onTap: () => handleDelete(),
),
],
onChanged: (index) {
print('Selected item at index: $index');
},
)
Properties
Property | Type | Default | Description |
---|---|---|---|
parentWidget | Widget | required | The trigger widget that opens the dropdown |
offset | Offset | required | Position offset for the dropdown menu |
children | List<Widget>? | null | List of widgets to display in dropdown |
child | Widget? | null | Single widget for dropdown (alternative to children) |
onChanged | Function(int)? | null | Callback when an item is selected |
dropdownColor | Color? | Colors.white | Background color of the dropdown |
dropdownRadius | double? | 10 | Border radius of the dropdown |
dropdownWidth | double? | null | Fixed width of the dropdown |
dropdownHeight | double? | null | Maximum height of the dropdown |
dropdownElevation | int? | 2 | Shadow elevation of the dropdown |
itemHeight | double? | 36 | Height of each dropdown item |
fromColor | Color? | Colors.transparent | Initial color for animation |
toColor | Color? | null | Target color for animation |
fromHeight | double? | 30 | Initial height for animation |
toHeight | double? | null | Target height for animation |
fromWidth | double? | 30 | Initial width for animation |
toWidth | double? | null | Target width for animation |
fromRadius | double? | 100 | Initial border radius for animation |
toRadius | double? | null | Target border radius for animation |
tooltip | String? | null | Tooltip text for accessibility |
Examples
Basic Menu with Icons
class BasicMenuExample extends StatelessWidget {
Widget build(BuildContext context) {
return DropDownMenuAppi(
parentWidget: Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8),
),
child: Icon(Icons.menu, color: Colors.white),
),
offset: Offset(0, 45),
tooltip: 'Open menu',
children: [
_buildMenuItem(Icons.home, 'Home'),
_buildMenuItem(Icons.settings, 'Settings'),
_buildMenuItem(Icons.help, 'Help'),
_buildMenuItem(Icons.logout, 'Logout'),
],
onChanged: (index) {
switch (index) {
case 0:
navigateToHome();
break;
case 1:
openSettings();
break;
case 2:
showHelp();
break;
case 3:
logout();
break;
}
},
);
}
Widget _buildMenuItem(IconData icon, String title) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
children: [
Icon(icon, size: 20),
SizedBox(width: 12),
Text(title),
],
),
);
}
}
Animated Button Menu
DropDownMenuAppi(
parentWidget: Text(
'Options',
style: TextStyle(fontWeight: FontWeight.bold),
),
offset: Offset(-50, 35),
fromColor: Colors.grey[200],
toColor: Colors.blue[100],
fromHeight: 30,
toHeight: 40,
fromWidth: 80,
toWidth: 120,
fromRadius: 15,
toRadius: 8,
dropdownColor: Colors.white,
dropdownRadius: 12,
dropdownElevation: 8,
children: [
_buildActionButton('Create New', Icons.add),
_buildActionButton('Import', Icons.upload),
_buildActionButton('Export', Icons.download),
],
onChanged: (index) {
handleAction(index);
},
)
Custom Styled Dropdown
DropDownMenuAppi(
parentWidget: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.purple, Colors.blue],
),
borderRadius: BorderRadius.circular(25),
),
child: Icon(Icons.apps, color: Colors.white),
),
offset: Offset(-100, 55),
dropdownColor: Colors.grey[50],
dropdownRadius: 16,
dropdownWidth: 200,
dropdownHeight: 300,
dropdownElevation: 12,
itemHeight: 48,
children: [
_buildCustomItem('Dashboard', Icons.dashboard, Colors.blue),
_buildCustomItem('Analytics', Icons.analytics, Colors.green),
_buildCustomItem('Reports', Icons.assessment, Colors.orange),
_buildCustomItem('Users', Icons.people, Colors.purple),
],
onChanged: (index) {
navigateToSection(index);
},
)
Single Child Dropdown
DropDownMenuAppi(
parentWidget: CircleAvatar(
backgroundColor: Colors.blue,
child: Text('JD'),
),
offset: Offset(-80, 45),
child: Container(
width: 200,
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'John Doe',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
Text(
'john.doe@example.com',
style: TextStyle(
color: Colors.grey[600],
fontSize: 14,
),
),
SizedBox(height: 12),
Divider(),
ListTile(
leading: Icon(Icons.person),
title: Text('Profile'),
dense: true,
onTap: () => openProfile(),
),
ListTile(
leading: Icon(Icons.logout),
title: Text('Sign Out'),
dense: true,
onTap: () => signOut(),
),
],
),
),
onChanged: (index) {
// Handle single child selection
print('Profile menu opened');
},
)
Advanced Features
Dynamic Content Loading
class DynamicDropdownExample extends StatefulWidget {
_DynamicDropdownExampleState createState() => _DynamicDropdownExampleState();
}
class _DynamicDropdownExampleState extends State<DynamicDropdownExample> {
List<Widget> menuItems = [];
bool isLoading = true;
void initState() {
super.initState();
loadMenuItems();
}
Future<void> loadMenuItems() async {
// Simulate API call
await Future.delayed(Duration(seconds: 1));
setState(() {
menuItems = [
_buildMenuItem('Recent Files', Icons.history),
_buildMenuItem('Favorites', Icons.star),
_buildMenuItem('Shared', Icons.share),
];
isLoading = false;
});
}
Widget build(BuildContext context) {
return DropDownMenuAppi(
parentWidget: Icon(Icons.folder),
offset: Offset(0, 40),
children: isLoading
? [
Container(
padding: EdgeInsets.all(20),
child: CircularProgressIndicator(),
)
]
: menuItems,
onChanged: (index) {
if (!isLoading) {
handleMenuSelection(index);
}
},
);
}
}
Contextual Menu
class ContextualMenuExample extends StatelessWidget {
final bool isOwner;
final bool isOnline;
const ContextualMenuExample({
required this.isOwner,
required this.isOnline,
});
Widget build(BuildContext context) {
List<Widget> menuItems = [];
// Add items based on context
if (isOnline) {
menuItems.add(_buildMenuItem('Send Message', Icons.message));
menuItems.add(_buildMenuItem('Video Call', Icons.video_call));
}
if (isOwner) {
menuItems.add(_buildMenuItem('Edit', Icons.edit));
menuItems.add(_buildMenuItem('Delete', Icons.delete));
}
menuItems.add(_buildMenuItem('Share', Icons.share));
menuItems.add(_buildMenuItem('Report', Icons.flag));
return DropDownMenuAppi(
parentWidget: Icon(Icons.more_horiz),
offset: Offset(-50, 35),
children: menuItems,
onChanged: (index) {
handleContextualAction(index, menuItems);
},
);
}
}
Animation Customization
Smooth Transitions
DropDownMenuAppi(
parentWidget: Container(
padding: EdgeInsets.all(12),
child: Text('Animate'),
),
offset: Offset(0, 45),
// Animation from state
fromColor: Colors.grey[300],
fromHeight: 35,
fromWidth: 80,
fromRadius: 20,
// Animation to state
toColor: Colors.blue[100],
toHeight: 45,
toWidth: 100,
toRadius: 10,
children: menuItems,
onChanged: handleSelection,
)
Hover Effects
The widget automatically handles hover states through the AnimatedBoxAppi
integration:
// The hovered state is automatically managed
child: ({hovered}) => Center(
child: Container(
decoration: BoxDecoration(
color: hovered ? Colors.blue[100] : Colors.transparent,
borderRadius: BorderRadius.circular(8),
),
child: parentWidget,
),
),
Positioning and Layout
Offset Calculation
// Position dropdown to the right
DropDownMenuAppi(
offset: Offset(100, 0), // 100px to the right
// ... other properties
)
// Position dropdown above the trigger
DropDownMenuAppi(
offset: Offset(0, -200), // 200px above
// ... other properties
)
// Position dropdown with custom calculation
DropDownMenuAppi(
offset: Offset(
MediaQuery.of(context).size.width * 0.1, // 10% from left
50, // 50px below
),
// ... other properties
)
Responsive Positioning
class ResponsiveDropdown extends StatelessWidget {
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
final isSmallScreen = screenWidth < 600;
return DropDownMenuAppi(
parentWidget: Icon(Icons.menu),
offset: isSmallScreen
? Offset(-150, 40) // Left-aligned on small screens
: Offset(0, 40), // Default on larger screens
dropdownWidth: isSmallScreen ? screenWidth * 0.8 : 250,
children: menuItems,
onChanged: handleSelection,
);
}
}
Best Practices
- Positioning: Calculate offsets carefully to ensure dropdown doesn't go off-screen
- Content: Keep menu items concise and clearly labeled
- Performance: Use single
child
for simple content,children
for complex lists - Accessibility: Always provide meaningful tooltips
- Animation: Use subtle animations to enhance user experience
- Responsive: Consider different screen sizes when positioning
Common Use Cases
- Context Menus: Right-click or long-press menus
- Action Menus: Quick action buttons with dropdown options
- Navigation Menus: Hierarchical navigation systems
- User Profiles: Profile dropdown with user actions
- Settings Panels: Quick settings and preferences
- File Operations: File management actions
Related Widgets
- AnimatedBoxAppi - For animated container effects
- SearchableTextFieldAppi - For searchable dropdown alternatives
- BottomSheetAppi - For modal alternatives
Migration Notes
When upgrading from basic dropdown widgets:
- Replace
DropdownButton
withDropDownMenuAppi
for enhanced features - Update positioning logic to use
Offset
instead of alignment - Implement animation properties for smooth transitions
- Consider using
children
array for multiple items
Ready for searchable dropdown functionality? Check out SearchableTextFieldAppi for advanced search capabilities!