Skip to main content

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

PropertyTypeDefaultDescription
parentWidgetWidgetrequiredThe trigger widget that opens the dropdown
offsetOffsetrequiredPosition offset for the dropdown menu
childrenList<Widget>?nullList of widgets to display in dropdown
childWidget?nullSingle widget for dropdown (alternative to children)
onChangedFunction(int)?nullCallback when an item is selected
dropdownColorColor?Colors.whiteBackground color of the dropdown
dropdownRadiusdouble?10Border radius of the dropdown
dropdownWidthdouble?nullFixed width of the dropdown
dropdownHeightdouble?nullMaximum height of the dropdown
dropdownElevationint?2Shadow elevation of the dropdown
itemHeightdouble?36Height of each dropdown item
fromColorColor?Colors.transparentInitial color for animation
toColorColor?nullTarget color for animation
fromHeightdouble?30Initial height for animation
toHeightdouble?nullTarget height for animation
fromWidthdouble?30Initial width for animation
toWidthdouble?nullTarget width for animation
fromRadiusdouble?100Initial border radius for animation
toRadiusdouble?nullTarget border radius for animation
tooltipString?nullTooltip 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

  1. Positioning: Calculate offsets carefully to ensure dropdown doesn't go off-screen
  2. Content: Keep menu items concise and clearly labeled
  3. Performance: Use single child for simple content, children for complex lists
  4. Accessibility: Always provide meaningful tooltips
  5. Animation: Use subtle animations to enhance user experience
  6. 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

Migration Notes

When upgrading from basic dropdown widgets:

  1. Replace DropdownButton with DropDownMenuAppi for enhanced features
  2. Update positioning logic to use Offset instead of alignment
  3. Implement animation properties for smooth transitions
  4. Consider using children array for multiple items

Ready for searchable dropdown functionality? Check out SearchableTextFieldAppi for advanced search capabilities!