flutter_speed_dial 7.0.0 copy "flutter_speed_dial: ^7.0.0" to clipboard
flutter_speed_dial: ^7.0.0 copied to clipboard

Flutter plugin to implement a beautiful and dynamic Material Design Speed Dial with labels, animated icons, multi-directional children and much more.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart';

void main() => runApp(const MyApp());

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  var theme = ValueNotifier(ThemeMode.dark);

  @override
  Widget build(BuildContext context) {
    const appTitle = 'Flutter Speed Dial Example';
    return ValueListenableBuilder<ThemeMode>(
        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<ThemeMode> theme;
  const MyHomePage({Key? key, required this.theme}) : super(key: key);
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> 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<bool>(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<FloatingActionButtonLocation>(
                              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<Widget>((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<SpeedDialDirection>(
                              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<Widget>((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<SpeedDialDirection>(
                                  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<bool>(
                  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];
}
1180
likes
130
pub points
99%
popularity

Publisher

verified publisherprateek.sunal.in

Flutter plugin to implement a beautiful and dynamic Material Design Speed Dial with labels, animated icons, multi-directional children and much more.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (LICENSE)

Dependencies

flutter

More

Packages that depend on flutter_speed_dial