@@ -12,8 +12,8 @@ import 'background_overlay.dart';
1212import 'speed_dial_child.dart' ;
1313import 'speed_dial_direction.dart' ;
1414
15- typedef AsyncChildrenBuilder = Future < List < SpeedDialChild >> Function (
16- BuildContext context);
15+ typedef AsyncChildrenBuilder =
16+ Future < List < SpeedDialChild >> Function ( BuildContext context);
1717
1818/// Builds the Speed Dial
1919class SpeedDial extends StatefulWidget {
@@ -159,7 +159,11 @@ class SpeedDial extends StatefulWidget {
159159 /// that was specific to FAB before like onPress, you will have to provide
160160 /// it again to your dialRoot button.
161161 final Widget Function (
162- BuildContext context, bool open, VoidCallback toggleChildren)? dialRoot;
162+ BuildContext context,
163+ bool open,
164+ VoidCallback toggleChildren,
165+ )?
166+ dialRoot;
163167
164168 /// This is the child of the FAB, if specified it will ignore icon, activeIcon.
165169 final Widget ? child;
@@ -234,10 +238,10 @@ class SpeedDial extends StatefulWidget {
234238 }) : super (key: key);
235239
236240 @override
237- State createState () => _SpeedDialState ();
241+ State createState () => SpeedDialState ();
238242}
239243
240- class _SpeedDialState extends State <SpeedDial >
244+ class SpeedDialState extends State <SpeedDial >
241245 with SingleTickerProviderStateMixin {
242246 late final AnimationController _controller = AnimationController (
243247 duration: widget.animationDuration,
@@ -252,15 +256,16 @@ class _SpeedDialState extends State<SpeedDial>
252256 @override
253257 void initState () {
254258 super .initState ();
255- widget.openCloseDial? .addListener (_onOpenCloseDial);
256259 Future .delayed (Duration .zero, () async {
257260 if (mounted && widget.isOpenOnStart) _toggleChildren ();
258261 });
259262 _checkChildren ();
263+ widget.openCloseDial? .addListener (_onOpenCloseDialChanged);
260264 }
261265
262266 @override
263267 void dispose () {
268+ widget.openCloseDial? .removeListener (_onOpenCloseDialChanged);
264269 if (overlayEntry != null ) {
265270 if (overlayEntry! .mounted) overlayEntry! .remove ();
266271 overlayEntry! .dispose ();
@@ -270,7 +275,6 @@ class _SpeedDialState extends State<SpeedDial>
270275 backgroundOverlay! .dispose ();
271276 }
272277 _controller.dispose ();
273- widget.openCloseDial? .removeListener (_onOpenCloseDial);
274278 super .dispose ();
275279 }
276280
@@ -279,27 +283,41 @@ class _SpeedDialState extends State<SpeedDial>
279283 if (oldWidget.children.length != widget.children.length) {
280284 _controller.duration = widget.animationDuration;
281285 }
282-
283- widget.openCloseDial? .removeListener (_onOpenCloseDial);
284- widget.openCloseDial? .addListener (_onOpenCloseDial);
286+ if (oldWidget.openCloseDial != widget.openCloseDial) {
287+ oldWidget.openCloseDial? .removeListener (_onOpenCloseDialChanged);
288+ widget.openCloseDial? .addListener (_onOpenCloseDialChanged);
289+ }
285290 super .didUpdateWidget (oldWidget);
286291 }
287292
288- void _checkChildren () {
289- if (widget.children.length > 5 ) {
290- debugPrint (
291- 'Warning ! You are using more than 5 children, which is not compliant with Material design specs.' );
292- }
293+ /// Opens the dial programmatically if it is currently closed.
294+ void openDial () {
295+ if (! _open) _toggleChildren ();
293296 }
294297
295- void _onOpenCloseDial () {
296- final show = widget.openCloseDial? .value;
297- if (! mounted) return ;
298- if (_open != show) {
298+ /// Closes the dial programmatically if it is currently open.
299+ void closeDial () {
300+ if (_open) _toggleChildren ();
301+ }
302+
303+ /// Responds to external open/close requests via [SpeedDial.openCloseDial] .
304+ ///
305+ /// Toggles the dial whenever the notifier value diverges from the current
306+ /// open state (open when `true` , close when `false` ).
307+ void _onOpenCloseDialChanged () {
308+ if (widget.openCloseDial! .value != _open) {
299309 _toggleChildren ();
300310 }
301311 }
302312
313+ void _checkChildren () {
314+ if (widget.children.length > 5 ) {
315+ debugPrint (
316+ 'Warning ! You are using more than 5 children, which is not compliant with Material design specs.' ,
317+ );
318+ }
319+ }
320+
303321 void _toggleChildren () async {
304322 if (! mounted) return ;
305323
@@ -320,7 +338,7 @@ class _SpeedDialState extends State<SpeedDial>
320338 }
321339 }
322340
323- toggleOverlay () {
341+ void toggleOverlay () {
324342 if (_open) {
325343 _controller.reverse ().whenComplete (() {
326344 overlayEntry? .remove ();
@@ -359,7 +377,8 @@ class _SpeedDialState extends State<SpeedDial>
359377 onTap: _toggleChildren,
360378 // (_open && !widget.closeManually) ? _toggleChildren : null,
361379 animation: _controller,
362- color: widget.overlayColor ??
380+ color:
381+ widget.overlayColor ??
363382 (dark ? Colors .grey[900 ] : Colors .white)! ,
364383 opacity: widget.overlayOpacity,
365384 );
@@ -370,8 +389,8 @@ class _SpeedDialState extends State<SpeedDial>
370389 if (! mounted) return ;
371390
372391 _controller.forward ();
373- if (widget.renderOverlay) Overlay .of (context)? .insert (backgroundOverlay! );
374- Overlay .of (context)? .insert (overlayEntry! );
392+ if (widget.renderOverlay) Overlay .of (context).insert (backgroundOverlay! );
393+ Overlay .of (context).insert (overlayEntry! );
375394 }
376395
377396 if (! mounted) return ;
@@ -401,72 +420,76 @@ class _SpeedDialState extends State<SpeedDial>
401420 builder: (BuildContext context, _) => Transform .rotate (
402421 angle:
403422 (widget.activeChild != null || widget.activeIcon != null ) &&
404- widget.useRotationAnimation
405- ? _controller.value * widget.animationAngle
406- : 0 ,
423+ widget.useRotationAnimation
424+ ? _controller.value * widget.animationAngle
425+ : 0 ,
407426 child: AnimatedSwitcher (
408- duration: widget.animationDuration,
409- child: (widget.child != null && _controller.value < 0.4 )
410- ? widget.child
411- : (widget.activeIcon == null &&
412- widget.activeChild == null ||
413- _controller.value < 0.4 )
414- ? Container (
427+ duration: widget.animationDuration,
428+ child: (widget.child != null && _controller.value < 0.4 )
429+ ? widget.child
430+ : (widget.activeIcon == null &&
431+ widget.activeChild == null ||
432+ _controller.value < 0.4 )
433+ ? Container (
434+ decoration: BoxDecoration (
435+ shape: widget.gradientBoxShape,
436+ gradient: widget.gradient,
437+ ),
438+ child: Center (
439+ child: widget.icon != null
440+ ? Icon (
441+ widget.icon,
442+ key: const ValueKey <int >(0 ),
443+ color: widget.iconTheme? .color,
444+ size: widget.iconTheme? .size,
445+ )
446+ : widget.child,
447+ ),
448+ )
449+ : Transform .rotate (
450+ angle: widget.useRotationAnimation ? - pi * 1 / 2 : 0 ,
451+ child:
452+ widget.activeChild ??
453+ Container (
415454 decoration: BoxDecoration (
416455 shape: widget.gradientBoxShape,
417456 gradient: widget.gradient,
418457 ),
419458 child: Center (
420- child: widget.icon != null
421- ? Icon (
422- widget.icon,
423- key: const ValueKey <int >(0 ),
424- color: widget.iconTheme? .color,
425- size: widget.iconTheme? .size,
426- )
427- : widget.child,
459+ child: Icon (
460+ widget.activeIcon,
461+ key: const ValueKey <int >(1 ),
462+ color: widget.iconTheme? .color,
463+ size: widget.iconTheme? .size,
464+ ),
428465 ),
429- )
430- : Transform .rotate (
431- angle:
432- widget.useRotationAnimation ? - pi * 1 / 2 : 0 ,
433- child: widget.activeChild ??
434- Container (
435- decoration: BoxDecoration (
436- shape: widget.gradientBoxShape,
437- gradient: widget.gradient,
438- ),
439- child: Center (
440- child: Icon (
441- widget.activeIcon,
442- key: const ValueKey <int >(1 ),
443- color: widget.iconTheme? .color,
444- size: widget.iconTheme? .size,
445- ),
446- ),
447- ),
448- )),
466+ ),
467+ ),
468+ ),
449469 ),
450470 );
451471
452472 var label = AnimatedSwitcher (
453473 duration: widget.animationDuration,
454- transitionBuilder: widget.labelTransitionBuilder ??
474+ transitionBuilder:
475+ widget.labelTransitionBuilder ??
455476 (child, animation) => FadeTransition (
456- opacity: animation,
457- child: child,
458- ),
477+ opacity: animation,
478+ child: child,
479+ ),
459480 child: (! _open || widget.activeLabel == null )
460481 ? widget.label
461482 : widget.activeLabel,
462483 );
463484
464485 final backgroundColorTween = ColorTween (
465- begin: widget.backgroundColor,
466- end: widget.activeBackgroundColor ?? widget.backgroundColor);
486+ begin: widget.backgroundColor,
487+ end: widget.activeBackgroundColor ?? widget.backgroundColor,
488+ );
467489 final foregroundColorTween = ColorTween (
468- begin: widget.foregroundColor,
469- end: widget.activeForegroundColor ?? widget.foregroundColor);
490+ begin: widget.foregroundColor,
491+ end: widget.activeForegroundColor ?? widget.foregroundColor,
492+ );
470493
471494 var animatedFloatingButton = AnimatedBuilder (
472495 animation: _controller,
@@ -602,50 +625,55 @@ class _ChildrensOverlay extends StatelessWidget {
602625 fit: StackFit .loose,
603626 children: [
604627 Positioned (
605- height: widget.maxHeight ??
628+ height:
629+ widget.maxHeight ??
606630 MediaQuery .of (context).size.height -
607631 (widget.childrenButtonSize.height * 3 ),
608632 child: CompositedTransformFollower (
609633 followerAnchor: widget.direction.isDown
610634 ? widget.switchLabelPosition
611- ? Alignment .topLeft
612- : Alignment .topRight
635+ ? Alignment .topLeft
636+ : Alignment .topRight
613637 : widget.direction.isUp
614- ? widget.switchLabelPosition
615- ? Alignment .bottomLeft
616- : Alignment .bottomRight
617- : widget.direction.isLeft
618- ? Alignment .centerRight
619- : widget.direction.isRight
620- ? Alignment .centerLeft
621- : Alignment .center,
638+ ? widget.switchLabelPosition
639+ ? Alignment .bottomLeft
640+ : Alignment .bottomRight
641+ : widget.direction.isLeft
642+ ? Alignment .centerRight
643+ : widget.direction.isRight
644+ ? Alignment .centerLeft
645+ : Alignment .center,
622646 offset: widget.direction.isDown
623647 ? Offset (
624648 (widget.switchLabelPosition ||
625649 dialKey.globalPaintBounds == null
626650 ? 0
627651 : dialKey.globalPaintBounds! .size.width) +
628652 max (widget.childrenButtonSize.height - 56 , 0 ) / 2 ,
629- dialKey.globalPaintBounds! .size.height)
653+ dialKey.globalPaintBounds! .size.height,
654+ )
630655 : widget.direction.isUp
631- ? Offset (
632- (widget.switchLabelPosition ||
633- dialKey.globalPaintBounds == null
634- ? 0
635- : dialKey.globalPaintBounds! .size.width) +
636- max (widget.childrenButtonSize.width - 56 , 0 ) / 2 ,
637- 0 )
638- : widget.direction.isLeft
639- ? Offset (
640- - 10.0 ,
641- dialKey.globalPaintBounds == null
642- ? 0
643- : dialKey.globalPaintBounds! .size.height / 2 )
644- : widget.direction.isRight &&
645- dialKey.globalPaintBounds != null
646- ? Offset (dialKey.globalPaintBounds! .size.width + 12 ,
647- dialKey.globalPaintBounds! .size.height / 2 )
648- : const Offset (- 10.0 , 0.0 ),
656+ ? Offset (
657+ (widget.switchLabelPosition ||
658+ dialKey.globalPaintBounds == null
659+ ? 0
660+ : dialKey.globalPaintBounds! .size.width) +
661+ max (widget.childrenButtonSize.width - 56 , 0 ) / 2 ,
662+ 0 ,
663+ )
664+ : widget.direction.isLeft
665+ ? Offset (
666+ - 10.0 ,
667+ dialKey.globalPaintBounds == null
668+ ? 0
669+ : dialKey.globalPaintBounds! .size.height / 2 ,
670+ )
671+ : widget.direction.isRight && dialKey.globalPaintBounds != null
672+ ? Offset (
673+ dialKey.globalPaintBounds! .size.width + 12 ,
674+ dialKey.globalPaintBounds! .size.height / 2 ,
675+ )
676+ : const Offset (- 10.0 , 0.0 ),
649677 link: layerLink,
650678 showWhenUnlinked: false ,
651679 child: Material (
@@ -701,7 +729,8 @@ Widget _buildColumnOrRow(
701729 mainAxisAlignment: mainAxisAlignment ?? MainAxisAlignment .start,
702730 crossAxisAlignment: crossAxisAlignment ?? CrossAxisAlignment .center,
703731 children: children,
704- ))
732+ ),
733+ )
705734 : Row (
706735 mainAxisSize: mainAxisSize ?? MainAxisSize .max,
707736 mainAxisAlignment: mainAxisAlignment ?? MainAxisAlignment .start,
0 commit comments