Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Simple-Complexity/47f580724ec2396b42ff9c10a110f22f to your computer and use it in GitHub Desktop.
Save Simple-Complexity/47f580724ec2396b42ff9c10a110f22f to your computer and use it in GitHub Desktop.
A speed dial fab implementation for Flutter
import 'package:flutter/material.dart';
import 'dart:math';
import 'package:hone/common/defaults.dart';
enum ExpandDirection {
UP,
DOWN,
}
enum LabelDirection {
LEFT,
RIGHT,
}
class SpeedDialFabItem {
VoidCallback onPressed;
IconData icon;
String label;
SpeedDialFabItem({this.onPressed, this.icon, this.label});
}
class SpeedDialFloatingActionButton extends StatefulWidget {
final ExpandDirection expandDirection;
final LabelDirection labelDirection;
final List<SpeedDialFabItem> speedDialFabItems;
final IconData closedIcon;
SpeedDialFloatingActionButton({
this.expandDirection : ExpandDirection.UP,
this.labelDirection: LabelDirection.LEFT,
this.speedDialFabItems : const [],
this.closedIcon : Icons.add,
});
@override
State<StatefulWidget> createState() => new _SpeedDialFloatingActionButtonState();
}
class _SpeedDialFloatingActionButtonState extends State<SpeedDialFloatingActionButton> with TickerProviderStateMixin {
static const FAB_SIZE_DIFFERENCE = DEFAULT_FAB_SIZE - MINI_FAB_SIZE;
AnimationController _controller;
static const List<IconData> icons = const [ Icons.sms, Icons.mail, Icons.phone ];
@override
void initState() {
super.initState();
_controller = new AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
);
}
@override
Widget build(BuildContext context) {
Color backgroundColor = Theme.of(context).cardColor;
Color foregroundColor = Theme.of(context).accentColor;
bool isExpandUp = widget.expandDirection == ExpandDirection.UP;
List<SpeedDialFabItem> fabItems = (isExpandUp) ?
widget.speedDialFabItems.reversed.toList() :
widget.speedDialFabItems;
Widget expandFab = new Container(
margin: new EdgeInsets.only(
top: isExpandUp ? 8.0 : 0.0,
bottom: isExpandUp ? 0.0 : 8.0,
),
child: new FloatingActionButton(
child: new AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
return new Transform(
transform: new Matrix4.rotationZ(_controller.value * 0.5 * PI),
alignment: FractionalOffset.center,
child: new Icon(_controller.isDismissed ? widget.closedIcon : Icons.close),
);
},
),
onPressed: () {
if (_controller.isDismissed) {
_controller.forward();
} else {
_controller.reverse();
}
},
),
);
List<Widget> widgetList = new List.generate(fabItems.length, (int index) {
var fabItem = fabItems[index];
Widget child = new Container(
height: 60.0,
alignment: FractionalOffset.centerRight,
margin: new EdgeInsets.only(right: FAB_SIZE_DIFFERENCE/2),
child: new ScaleTransition(
scale: new CurvedAnimation(
parent: _controller,
curve: new Interval(
isExpandUp ? 0.0 : 0.0 + index / fabItems.length / 2.0,
isExpandUp ? 1.0 - index / fabItems.length / 2.0 : 1.0,
curve: Curves.easeOut,
),
),
child: new Stack(
overflow: Overflow.visible,
children: <Widget>[
(fabItem.label != null && fabItem.label.isNotEmpty) ? new Positioned(
child: new Container(
color: Colors.black,
padding: const EdgeInsets.all(4.0),
child: new Text(fabItem.label,
style: new TextStyle(
color: Colors.white,
),
),
),
right: 50.0,
top: 9.0,
) : new Container(),
new FloatingActionButton(
backgroundColor: backgroundColor,
mini: true,
child: new Icon(fabItem.icon, color: foregroundColor),
onPressed: fabItem.onPressed,
heroTag: 'fab_${fabItem.icon.toString()}',
),
],
),
),
);
return child;
}).toList();
if(isExpandUp){
// Add to the end of the widget list
widgetList.add(expandFab);
}else{
// insert into the beginning of the widget list
widgetList.insert(0, expandFab);
}
return new Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: widgetList,
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment