import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_speed_dial/flutter_speed_dial.dart'; void main() => runApp(const SpeedDial()); class SpeedDials extends StatefulWidget { const SpeedDials({Key? key}) : super(key: key); @override _SpeedDials createState() => _SpeedDials(); } class _SpeedDials extends State { var theme = ValueNotifier(ThemeMode.dark); @override Widget build(BuildContext context) { const appTitle = 'Flutter Speed Dial Example'; return ValueListenableBuilder( valueListenable: theme, builder: (context, value, child) => MaterialApp( title: appTitle, home: MyHomePage(theme: theme), debugShowCheckedModeBanner: false, theme: ThemeData( brightness: Brightness.light, primaryColor: Colors.blue, ), darkTheme: ThemeData( brightness: Brightness.dark, primaryColor: Colors.lightBlue[900], ), themeMode: value, )); } } class MyHomePage extends StatefulWidget { final ValueNotifier theme; const MyHomePage({Key? key, required this.theme}) : super(key: key); @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State with TickerProviderStateMixin { var renderOverlay = true; var visible = true; var switchLabelPosition = false; var extend = false; var mini = false; var rmicons = false; var customDialRoot = false; var closeManually = false; var useRAnimation = true; var isDialOpen = ValueNotifier(false); var speedDialDirection = SpeedDialDirection.up; var buttonSize = const Size(56.0, 56.0); var childrenButtonSize = const Size(56.0, 56.0); var selectedfABLocation = FloatingActionButtonLocation.endDocked; var items = [ FloatingActionButtonLocation.startFloat, FloatingActionButtonLocation.startDocked, FloatingActionButtonLocation.centerFloat, FloatingActionButtonLocation.endFloat, FloatingActionButtonLocation.endDocked, FloatingActionButtonLocation.startTop, FloatingActionButtonLocation.centerTop, FloatingActionButtonLocation.endTop, ]; @override Widget build(BuildContext context) { return WillPopScope( onWillPop: () async { if (isDialOpen.value) { isDialOpen.value = false; return false; } return true; }, child: Scaffold( appBar: AppBar( title: const Text("Flutter Speed Dial Example"), ), body: SingleChildScrollView( padding: const EdgeInsets.all(16), physics: const BouncingScrollPhysics(), child: Center( child: Container( constraints: const BoxConstraints(maxWidth: 800), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.symmetric( vertical: 6, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("SpeedDial Location", style: Theme.of(context).textTheme.bodyLarge), const SizedBox(height: 10), Container( decoration: BoxDecoration( color: Theme.of(context).brightness == Brightness.dark ? Colors.grey[800] : Colors.grey[200], borderRadius: BorderRadius.circular(10)), child: DropdownButton( value: selectedfABLocation, isExpanded: true, icon: const Icon(Icons.arrow_drop_down), iconSize: 20, underline: const SizedBox(), onChanged: (fABLocation) => setState( () => selectedfABLocation = fABLocation!), selectedItemBuilder: (BuildContext context) { return items.map((item) { return Align( alignment: Alignment.centerLeft, child: Container( padding: const EdgeInsets.symmetric( vertical: 4, horizontal: 10), child: Text(item.value))); }).toList(); }, items: items.map((item) { return DropdownMenuItem< FloatingActionButtonLocation>( value: item, child: Text( item.value, ), ); }).toList(), ), ), ], ), ), Container( padding: const EdgeInsets.symmetric( vertical: 6, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("SpeedDial Direction", style: Theme.of(context).textTheme.bodyLarge), const SizedBox(height: 10), Container( decoration: BoxDecoration( color: Theme.of(context).brightness == Brightness.dark ? Colors.grey[800] : Colors.grey[200], borderRadius: BorderRadius.circular(10)), child: DropdownButton( value: speedDialDirection, isExpanded: true, icon: const Icon(Icons.arrow_drop_down), iconSize: 20, underline: const SizedBox(), onChanged: (sdo) { setState(() { speedDialDirection = sdo!; selectedfABLocation = (sdo.isUp && selectedfABLocation.value .contains("Top")) || (sdo.isLeft && selectedfABLocation.value .contains("start")) ? FloatingActionButtonLocation.endDocked : sdo.isDown && !selectedfABLocation.value .contains("Top") ? FloatingActionButtonLocation.endTop : sdo.isRight && selectedfABLocation.value .contains("end") ? FloatingActionButtonLocation .startDocked : selectedfABLocation; }); }, selectedItemBuilder: (BuildContext context) { return SpeedDialDirection.values .toList() .map((item) { return Container( padding: const EdgeInsets.symmetric( vertical: 4, horizontal: 10), child: Align( alignment: Alignment.centerLeft, child: Text( describeEnum(item).toUpperCase())), ); }).toList(); }, items: SpeedDialDirection.values .toList() .map((item) { return DropdownMenuItem( value: item, child: Text(describeEnum(item).toUpperCase()), ); }).toList(), ), ), ], ), ), if (!customDialRoot) SwitchListTile( contentPadding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), value: extend, title: const Text("Extend Speed Dial"), onChanged: (val) { setState(() { extend = val; }); }), SwitchListTile( contentPadding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), value: visible, title: const Text("Visible"), onChanged: (val) { setState(() { visible = val; }); }), SwitchListTile( contentPadding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), value: mini, title: const Text("Mini"), onChanged: (val) { setState(() { mini = val; }); }), SwitchListTile( contentPadding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), value: customDialRoot, title: const Text("Custom dialRoot"), onChanged: (val) { setState(() { customDialRoot = val; }); }), SwitchListTile( contentPadding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), value: renderOverlay, title: const Text("Render Overlay"), onChanged: (val) { setState(() { renderOverlay = val; }); }), SwitchListTile( contentPadding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), value: closeManually, title: const Text("Close Manually"), onChanged: (val) { setState(() { closeManually = val; }); }), SwitchListTile( contentPadding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), value: rmicons, title: const Text("Remove Icons (for children)"), onChanged: (val) { setState(() { rmicons = val; }); }), if (!customDialRoot) SwitchListTile( contentPadding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), value: useRAnimation, title: const Text("Use Rotation Animation"), onChanged: (val) { setState(() { useRAnimation = val; }); }), SwitchListTile( contentPadding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), value: switchLabelPosition, title: const Text("Switch Label Position"), onChanged: (val) { setState(() { switchLabelPosition = val; if (val) { if ((selectedfABLocation.value.contains("end") || selectedfABLocation.value .toLowerCase() .contains("top")) && speedDialDirection.isUp) { selectedfABLocation = FloatingActionButtonLocation.startDocked; } else if ((selectedfABLocation.value .contains("end") || !selectedfABLocation.value .toLowerCase() .contains("top")) && speedDialDirection.isDown) { selectedfABLocation = FloatingActionButtonLocation.startTop; } } }); }), const Text("Button Size"), Slider( value: buttonSize.width, min: 50, max: 500, label: "Button Size", onChanged: (val) { setState(() { buttonSize = Size(val, val); }); }, ), const Text("Children Button Size"), Slider( value: childrenButtonSize.height, min: 50, max: 500, onChanged: (val) { setState(() { childrenButtonSize = Size(val, val); }); }, ), Container( padding: const EdgeInsets.symmetric( vertical: 6, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Navigation", style: Theme.of(context).textTheme.bodyLarge), const SizedBox(height: 10), ElevatedButton( onPressed: () { Navigator.of(context).push( MaterialPageRoute( builder: (_) => MyHomePage(theme: widget.theme), ), ); }, child: const Text("Push Duplicate Page")), ], ), ), ], ), ), )), floatingActionButtonLocation: selectedfABLocation, floatingActionButton: SpeedDial( // animatedIcon: AnimatedIcons.menu_close, // animatedIconTheme: IconThemeData(size: 22.0), // / This is ignored if animatedIcon is non null // child: Text("open"), // activeChild: Text("close"), icon: Icons.add, activeIcon: Icons.close, spacing: 3, mini: mini, openCloseDial: isDialOpen, childPadding: const EdgeInsets.all(5), spaceBetweenChildren: 4, dialRoot: customDialRoot ? (ctx, open, toggleChildren) { return ElevatedButton( onPressed: toggleChildren, style: ElevatedButton.styleFrom( backgroundColor: Colors.blue[900], padding: const EdgeInsets.symmetric( horizontal: 22, vertical: 18), ), child: const Text( "Custom Dial Root", style: TextStyle(fontSize: 17), ), ); } : null, buttonSize: buttonSize, // it's the SpeedDial size which defaults to 56 itself // iconTheme: IconThemeData(size: 22), label: extend ? const Text("Open") : null, // The label of the main button. /// The active label of the main button, Defaults to label if not specified. activeLabel: extend ? const Text("Close") : null, /// Transition Builder between label and activeLabel, defaults to FadeTransition. // labelTransitionBuilder: (widget, animation) => ScaleTransition(scale: animation,child: widget), /// The below button size defaults to 56 itself, its the SpeedDial childrens size childrenButtonSize: childrenButtonSize, visible: visible, direction: speedDialDirection, switchLabelPosition: switchLabelPosition, /// If true user is forced to close dial manually closeManually: closeManually, /// If false, backgroundOverlay will not be rendered. renderOverlay: renderOverlay, // overlayColor: Colors.black, // overlayOpacity: 0.5, onOpen: () => debugPrint('OPENING DIAL'), onClose: () => debugPrint('DIAL CLOSED'), useRotationAnimation: useRAnimation, tooltip: 'Open Speed Dial', heroTag: 'speed-dial-hero-tag', // foregroundColor: Colors.black, // backgroundColor: Colors.white, // activeForegroundColor: Colors.red, // activeBackgroundColor: Colors.blue, elevation: 8.0, animationCurve: Curves.elasticInOut, isOpenOnStart: false, shape: customDialRoot ? const RoundedRectangleBorder() : const StadiumBorder(), // childMargin: EdgeInsets.symmetric(horizontal: 10, vertical: 5), children: [ SpeedDialChild( child: !rmicons ? const Icon(Icons.accessibility) : null, backgroundColor: Colors.red, foregroundColor: Colors.white, label: 'First', onTap: () => setState(() => rmicons = !rmicons), onLongPress: () => debugPrint('FIRST CHILD LONG PRESS'), ), SpeedDialChild( child: !rmicons ? const Icon(Icons.brush) : null, backgroundColor: Colors.deepOrange, foregroundColor: Colors.white, label: 'Second', onTap: () => debugPrint('SECOND CHILD'), ), SpeedDialChild( child: !rmicons ? const Icon(Icons.margin) : null, backgroundColor: Colors.indigo, foregroundColor: Colors.white, label: 'Show Snackbar', visible: true, onTap: () => ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text(("Third Child Pressed")))), onLongPress: () => debugPrint('THIRD CHILD LONG PRESS'), ), ], ), bottomNavigationBar: BottomAppBar( shape: const CircularNotchedRectangle(), notchMargin: 8.0, child: Row( mainAxisAlignment: selectedfABLocation == FloatingActionButtonLocation.startDocked ? MainAxisAlignment.end : selectedfABLocation == FloatingActionButtonLocation.endDocked ? MainAxisAlignment.start : MainAxisAlignment.center, mainAxisSize: MainAxisSize.max, children: [ IconButton( icon: const Icon(Icons.nightlight_round), tooltip: "Switch Theme", onPressed: () => { widget.theme.value = widget.theme.value.index == 2 ? ThemeMode.light : ThemeMode.dark }, ), ValueListenableBuilder( valueListenable: isDialOpen, builder: (ctx, value, _) => IconButton( icon: const Icon(Icons.open_in_browser), tooltip: (!value ? "Open" : "Close") + (" Speed Dial"), onPressed: () => {isDialOpen.value = !isDialOpen.value}, )) ], ), ), ), ); } } extension EnumExt on FloatingActionButtonLocation { /// Get Value of The SpeedDialDirection Enum like Up, Down, etc. in String format String get value => toString().split(".")[1]; }