FloatingActionButton - Animated FloatingActionButton in Flutter
Published April 17, 2020Flutter offers two types of FloatingActionButtons out-of-the-box
- FloatingActionButton
- FloatingActionButton.extended
FloatingActionButton
The default constructor creates a simple circular FAB with a child widget inside it. It takes an onPressed method to react to taps and a child (not compulsory) to display a widget inside the FAB
FloatingActionButton( |
FloatingActionButton.extended
FloatingActionButton.extended offers a wide FAB, usually with an icon and a label inside it.
FloatingActionButton.extended( |
How to Create AnimatedFloatingActionButton with Multiple Childs?
In the below example we are going to create Multiple FloatingAction buttons with HORIZONTAL and VERTICAL directions.
For this we are going to use UnicornButton dart class file.
Let's get Started
Step 1: Create flutter Application
Step 2: Create a class UnicornButton.dart and add below code
import 'package:flutter/material.dart';
import 'package:vector_math/vector_math_64.dart' as vector;
class UnicornOrientation {
static const HORIZONTAL = 0;
static const VERTICAL = 1;
}
class UnicornButton extends FloatingActionButton {
final FloatingActionButton currentButton;
final String labelText;
final double labelFontSize;
final Color labelColor;
final Color labelBackgroundColor;
final Color labelShadowColor;
final bool labelHasShadow;
final bool hasLabel;
UnicornButton(
{this.currentButton,
this.labelText,
this.labelFontSize = 14.0,
this.labelColor,
this.labelBackgroundColor,
this.labelShadowColor,
this.labelHasShadow = true,
this.hasLabel = false})
: assert(currentButton != null);
Widget returnLabel() {
return Container(
decoration: BoxDecoration(
boxShadow: this.labelHasShadow
? [
new BoxShadow(
color: this.labelShadowColor == null
? Color.fromRGBO(204, 204, 204, 1.0)
: this.labelShadowColor,
blurRadius: 3.0,
),
]
: null,
color: this.labelBackgroundColor == null
? Colors.white
: this.labelBackgroundColor,
borderRadius: BorderRadius.circular(3.0)), //color: Colors.white,
padding: EdgeInsets.all(9.0),
child: Text(this.labelText,
style: TextStyle(
fontSize: this.labelFontSize,
fontWeight: FontWeight.bold,
color: this.labelColor == null
? Color.fromRGBO(119, 119, 119, 1.0)
: this.labelColor)));
}
Widget build(BuildContext context) {
return this.currentButton;
}
}
class UnicornDialer extends StatefulWidget {
final int orientation;
final Icon parentButton;
final Icon finalButtonIcon;
final bool hasBackground;
final Color parentButtonBackground;
final List<UnicornButton> childButtons;
final int animationDuration;
final int mainAnimationDuration;
final double childPadding;
final Color backgroundColor;
final Function onMainButtonPressed;
final Object parentHeroTag;
final bool hasNotch;
UnicornDialer(
{this.parentButton,
this.parentButtonBackground,
this.childButtons,
this.onMainButtonPressed,
this.orientation = 1,
this.hasBackground = true,
this.backgroundColor = Colors.white30,
this.parentHeroTag = "parent",
this.finalButtonIcon,
this.animationDuration = 180,
this.mainAnimationDuration = 200,
this.childPadding = 4.0,
this.hasNotch = false})
: assert(parentButton != null);
_UnicornDialer createState() => _UnicornDialer();
}
class _UnicornDialer extends State<UnicornDialer>
with TickerProviderStateMixin {
AnimationController _animationController;
AnimationController _parentController;
bool isOpen = false;
@override
void initState() {
this._animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: widget.animationDuration));
this._parentController =
AnimationController(vsync: this, duration: Duration(milliseconds: widget.mainAnimationDuration));
super.initState();
}
@override
dispose() {
this._animationController.dispose();
this._parentController.dispose();
super.dispose();
}
void mainActionButtonOnPressed() {
if (this._animationController.isDismissed) {
this._animationController.forward();
} else {
this._animationController.reverse();
}
}
@override
Widget build(BuildContext context) {
this._animationController.reverse();
var hasChildButtons =
widget.childButtons != null && widget.childButtons.length > 0;
if (!this._parentController.isAnimating) {
if (this._parentController.isCompleted) {
this._parentController.forward().then((s) {
this._parentController.reverse().then((e) {
this._parentController.forward();
});
});
}
if (this._parentController.isDismissed) {
this._parentController.reverse().then((s) {
this._parentController.forward();
});
}
}
var mainFAB = AnimatedBuilder(
animation: this._parentController,
builder: (BuildContext context, Widget child) {
return Transform(
transform: new Matrix4.diagonal3(vector.Vector3(
_parentController.value,
_parentController.value,
_parentController.value)),
alignment: FractionalOffset.center,
child: FloatingActionButton(
isExtended: false,
heroTag: widget.parentHeroTag,
backgroundColor: widget.parentButtonBackground,
onPressed: () {
mainActionButtonOnPressed();
if (widget.onMainButtonPressed != null) {
widget.onMainButtonPressed();
}
},
child: !hasChildButtons
? widget.parentButton
: AnimatedBuilder(
animation: this._animationController,
builder: (BuildContext context, Widget child) {
return Transform(
transform: new Matrix4.rotationZ(
this._animationController.value * 0.8),
alignment: FractionalOffset.center,
child: new Icon(
this._animationController.isDismissed
? widget.parentButton.icon
: widget.finalButtonIcon == null
? Icons.close
: widget.finalButtonIcon.icon),
);
})));
});
if (hasChildButtons) {
var mainFloatingButton = AnimatedBuilder(
animation: this._animationController,
builder: (BuildContext context, Widget child) {
return Transform.rotate(
angle: this._animationController.value * 0.8, child: mainFAB);
});
var childButtonsList = widget.childButtons == null ||
widget.childButtons.length == 0
? List<Widget>()
: List.generate(widget.childButtons.length, (index) {
var intervalValue = index == 0
? 0.9
: ((widget.childButtons.length - index) /
widget.childButtons.length) -
0.2;
intervalValue =
intervalValue < 0.0 ? (1 / index) * 0.5 : intervalValue;
var childFAB = FloatingActionButton(
onPressed: () {
print("OnClicked");
if (widget.childButtons[index].currentButton.onPressed !=
null) {
widget.childButtons[index].currentButton.onPressed();
}
this._animationController.reverse();
},
child: widget.childButtons[index].currentButton.child,
heroTag: widget.childButtons[index].currentButton.heroTag,
backgroundColor:
widget.childButtons[index].currentButton.backgroundColor,
mini: widget.childButtons[index].currentButton.mini,
tooltip: widget.childButtons[index].currentButton.tooltip,
key: widget.childButtons[index].currentButton.key,
elevation: widget.childButtons[index].currentButton.elevation,
foregroundColor:
widget.childButtons[index].currentButton.foregroundColor,
highlightElevation: widget
.childButtons[index].currentButton.highlightElevation,
isExtended:
widget.childButtons[index].currentButton.isExtended,
shape: widget.childButtons[index].currentButton.shape);
return Positioned(
right: widget.orientation == UnicornOrientation.VERTICAL
? widget.childButtons[index].currentButton.mini ? 4.0 : 0.0
: ((widget.childButtons.length - index) * 55.0) + 15,
bottom: widget.orientation == UnicornOrientation.VERTICAL
? ((widget.childButtons.length - index) * 55.0) + 15
: 8.0,
child: Row(children: [
ScaleTransition(
scale: CurvedAnimation(
parent: this._animationController,
curve:
Interval(intervalValue, 1.0, curve: Curves.linear),
),
alignment: FractionalOffset.center,
child: (!widget.childButtons[index].hasLabel) ||
widget.orientation ==
UnicornOrientation.HORIZONTAL
? Container()
: Container(
padding:
EdgeInsets.only(right: widget.childPadding),
child: widget.childButtons[index].returnLabel())),
ScaleTransition(
scale: CurvedAnimation(
parent: this._animationController,
curve:
Interval(intervalValue, 1.0, curve: Curves.linear),
),
alignment: FractionalOffset.center,
child: childFAB)
]),
);
});
var unicornDialWidget = Container(
margin: widget.hasNotch ? EdgeInsets.only(bottom: 15.0) : null,
width: MediaQuery.of(context).size.width-100,
child: Stack(
//fit: StackFit.expand,
alignment: Alignment.bottomRight,
overflow: Overflow.visible,
children: childButtonsList.toList()
..add(Positioned(
right: null, bottom: null, child: mainFloatingButton))));
var modal = ScaleTransition(
scale: CurvedAnimation(
parent: this._animationController,
curve: Interval(1.0, 1.0, curve: Curves.linear),
),
alignment: FractionalOffset.center,
child: GestureDetector(
onTap: mainActionButtonOnPressed,
child: Container(
color: widget.backgroundColor,
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
)));
return widget.hasBackground
? Stack(
alignment: Alignment.topCenter,
overflow: Overflow.visible,
children: [
Positioned(right: -16.0, bottom: -16.0, child: modal),
unicornDialWidget
])
: unicornDialWidget;
}
return mainFAB;
}
}
|
Step 3: Update main dart file
import 'dart:ui';
import 'package:animated_floatactionbuttons/animated_floatactionbuttons.dart';
import 'package:flutter/material.dart';
import 'UnicornButton.dart';
void main() => runApp(MaterialApp(home: CanvasPainting(),));
class CanvasPainting extends StatefulWidget {
@override
_CanvasPaintingState createState() => _CanvasPaintingState();
}
class _CanvasPaintingState extends State<CanvasPainting> {
static final List lineWidths = [3.0, 5.0, 8.0];
// File imageFile;
int selectedLine = 0;
StrokeCap strokeType = StrokeCap.round;
static double strokeWidth = 3.0;
static Color selectedColor = Colors.black;
int curFrame = 0;
bool isClear = false;
double opacity = 1.0;
List points = [Point(selectedColor, strokeWidth, [])];
Future<void> _pickStroke() async {
//Shows AlertDialog
return showDialog<void>(
context: context,
//Dismiss alert dialog when set true
barrierDismissible: true, // user must tap button!
builder: (BuildContext context) {
//Clips its child in a oval shape
return ClipOval(
child: AlertDialog(
//Creates three buttons to pick stroke value.
actions: <Widget>[
//Resetting to default stroke value
FlatButton(
child: Icon(
Icons.clear,
),
onPressed: () {
strokeWidth = 3.0;
Navigator.of(context).pop();
},
),
FlatButton(
child: Icon(
Icons.brush,
size: 24,
),
onPressed: () {
strokeWidth = 10.0;
Navigator.of(context).pop();
},
),
FlatButton(
child: Icon(
Icons.brush,
size: 40,
),
onPressed: () {
strokeWidth = 30.0;
Navigator.of(context).pop();
},
),
FlatButton(
child: Icon(
Icons.brush,
size: 60,
),
onPressed: () {
strokeWidth = 50.0;
Navigator.of(context).pop();
},
),
],
),
);
},
);
}
Future<void> _opacity() async {
//Shows AlertDialog
return showDialog<void>(
context: context,
//Dismiss alert dialog when set true
barrierDismissible: true,
builder: (BuildContext context) {
//Clips its child in a oval shape
return ClipOval(
child: AlertDialog(
//Creates three buttons to pick opacity value.
actions: <Widget>[
FlatButton(
child: Icon(
Icons.opacity,
size: 24,
),
onPressed: () {
//most transparent
opacity = 0.1;
Navigator.of(context).pop();
},
),
FlatButton(
child: Icon(
Icons.opacity,
size: 40,
),
onPressed: () {
opacity = 0.5;
Navigator.of(context).pop();
},
),
FlatButton(
child: Icon(
Icons.opacity,
size: 60,
),
onPressed: () {
//not transparent at all.
opacity = 1.0;
Navigator.of(context).pop();
},
),
],
),
);
},
);
}
paintOptions()
{
return UnicornDialer(
backgroundColor: Color.fromRGBO(255, 255, 255, 0.6),
parentButtonBackground: Colors.redAccent,
orientation: UnicornOrientation.HORIZONTAL,
parentButton: Icon(Icons.more),
onMainButtonPressed: (){
print("OnClicked Parent");
},
childButtons: [
UnicornButton(
currentButton: FloatingActionButton(
heroTag: "paint_stroke",
backgroundColor: Colors.teal,
child: Icon(Icons.brush),
tooltip: 'Stroke',
onPressed: () {
print("OnClicked");
//min: 0, max: 50
setState(() {
_pickStroke();
});
},
),
),
UnicornButton(
currentButton: FloatingActionButton(
backgroundColor: Color(0XFF8E44AD),
heroTag: "paint_opacity",
child: Icon(Icons.opacity),
tooltip: 'Opacity',
onPressed: () {
//min:0, max:1
setState(() {
_opacity();
});
},
),
),
UnicornButton(
currentButton: FloatingActionButton(
backgroundColor: Color(0XFFF39C12),
heroTag: "erase",
child: Icon(Icons.clear),
tooltip: "Erase",
onPressed: () {
setState(() {
points.clear();
curFrame=0;
points = [Point(selectedColor, strokeWidth, [])];
});
}),
),
]);
}
getColorFAB()
{
return UnicornDialer(
backgroundColor: Color.fromRGBO(255, 255, 255, 0.6),
parentButtonBackground: Colors.redAccent,
orientation: UnicornOrientation.HORIZONTAL,
parentButton: Icon(Icons.view_list),
onMainButtonPressed: (){
print("OnClicked Parent");
},
childButtons: [
UnicornButton(
currentButton: FloatingActionButton(
backgroundColor: Colors.white,
heroTag: "color_red",
child: colorMenuItem(Colors.red),
tooltip: 'Color',
onPressed: () {
setState(() {
selectedColor = Colors.red;
});
},
),
),
UnicornButton(
currentButton: FloatingActionButton(
backgroundColor: Colors.white,
heroTag: "color_green",
child: colorMenuItem(Colors.green),
tooltip: 'Color',
onPressed: () {
setState(() {
selectedColor = Colors.green;
});
},
),
),
UnicornButton(
currentButton: FloatingActionButton(
backgroundColor: Colors.white,
heroTag: "color_pink",
child: colorMenuItem(Colors.pink),
tooltip: 'Color',
onPressed: () {
setState(() {
selectedColor = Colors.pink;
});
},
),
),
UnicornButton(
currentButton: FloatingActionButton(
backgroundColor: Colors.white,
heroTag: "color_blue",
child: colorMenuItem(Colors.blue),
tooltip: 'Color',
onPressed: () {
setState(() {
selectedColor = Colors.blue;
});
},
),
),
],
);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: SafeArea(
child: Scaffold(
body: Card(
color: Colors.blueGrey,
elevation: 10,
clipBehavior: Clip.hardEdge,
child: Container(
margin: EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10)),
color: Colors.white
),
child: _buildCanvas()),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
paintOptions(),
SizedBox(height: 10,),
getColorFAB()
,],)
),
),
);
}
Widget colorMenuItem(Color color) {
return GestureDetector(
onTap: () {
setState(() {
selectedColor = color;
});
},
child: ClipOval(
child: Container(
padding: const EdgeInsets.only(bottom: 8.0),
height: 36,
width: 36,
color: color,
),
),
);
}
final List colors = [
Colors.black,
Colors.purple,
Colors.green,
];
Widget _buildCanvas() {
return StatefulBuilder(builder: (context, state) {
return CustomPaint(
painter: CustomePainter(
points: points,
strokeColor: selectedColor,
strokeWidth: strokeWidth,
isClear: isClear,
),
child: GestureDetector(
onPanStart: (details) {
// before painting, set color & strokeWidth.
isClear = false;
points[curFrame].color = selectedColor;
points[curFrame].strokeWidth = strokeWidth;
},
onPanUpdate: (details) {
RenderBox referenceBox = context.findRenderObject();
Offset localPosition =
referenceBox.globalToLocal(details.globalPosition);
state(() {
points[curFrame].points.add(localPosition);
});
},
onPanEnd: (details) {
// preparing for next line painting.
points.add(Point(selectedColor, strokeWidth, []));
curFrame++;
},
),
);
});
}
}
class Point {
Color color;
List points;
double strokeWidth = 5.0;
Point(this.color, this.strokeWidth, this.points);
}
class CustomePainter extends CustomPainter {
final double strokeWidth;
final Color strokeColor;
Paint _linePaint;
final bool isClear;
final List points;
CustomePainter({
@required this.points,
@required this.strokeColor,
@required this.strokeWidth,
this.isClear = true,
}) {
_linePaint = Paint()
..color = strokeColor
..strokeWidth = strokeWidth
..strokeCap = StrokeCap.round;
}
void paint(Canvas canvas, Size size) {
if (isClear || points == null || points.length == 0) {
return;
}
for (int i = 0; i < points.length; i++) {
_linePaint..color = points[i].color;
_linePaint..strokeWidth = points[i].strokeWidth;
List curPoints = points[i].points;
if (curPoints == null || curPoints.length == 0) {
break;
}
for (int i = 0; i < curPoints.length - 1; i++) {
if (curPoints[i] != null && curPoints[i + 1] != null)
canvas.drawLine(curPoints[i], curPoints[i + 1], _linePaint);
// canvas.drawPoints(PointMode.polygon, curPoints, _linePaint);
}
}
}
bool shouldRepaint(CustomePainter other) => true;
}
/*class MyPainter extends CustomPainter {
MyPainter({this.pointsList});
//Keep track of the points tapped on the screen
List<TouchPoints> pointsList;
List<Offset> offsetPoints = List();
//This is where we can draw on canvas.
@override
void paint(Canvas canvas, Size size) {
for (int i = 0; i < pointsList.length - 1; i++) {
if (pointsList[i] != null && pointsList[i + 1] != null) {
//Drawing line when two consecutive points are available
canvas.drawLine(pointsList[i].points, pointsList[i + 1].points,
pointsList[i].paint);
} else if (pointsList[i] != null && pointsList[i + 1] == null) {
offsetPoints.clear();
offsetPoints.add(pointsList[i].points);
offsetPoints.add(Offset(
pointsList[i].points.dx + 0.1, pointsList[i].points.dy + 0.1));
//Draw points when two points are not next to each other
canvas.drawPoints(PointMode.points, offsetPoints, pointsList[i].paint);
}
}
}
//Called when CustomPainter is rebuilt.
//Returning true because we want canvas to be rebuilt to reflect new changes.
@override
bool shouldRepaint(MyPainter oldDelegate) => true;
}*/
|
Step 4: Run application
Article Contributed By :
|
|
|
|
2447 Views |