Help, I am trying to create a semi circular menu like this, image of what I want here
But I dont know how to place the widgets on top of the larger white line...I have tried using the following code. Please use the widget as the home widget in your main.dart and see the result. I have used the font_awesome_flutter package in packages pub for the icons:-
image of how it is currently here
import 'package:flutter/material.dart';
import 'dart:math';
import 'package:vector_math/vector_math.dart' show radians, Vector3;
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class Homewidgettoslack extends StatefulWidget {
Homewidgettoslack({
Key key,
}) : super(key: key);
#override
_HomewidgettoslackState createState() => _HomewidgettoslackState();
}
class _HomewidgettoslackState extends State<Homewidgettoslack> with TickerProviderStateMixin {
Animation<double> rotation;
Animation<double> translation;
Animation<double> menuscale;
AnimationController menuController;
#override
void initState() {
super.initState();
menuController =
AnimationController(duration: Duration(milliseconds: 900), vsync: this);
rotation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(
CurvedAnimation(
parent: menuController,
curve: Interval(
0.0,
0.7,
curve: Curves.decelerate,
),
),
);
translation = Tween<double>(
begin: 0.0,
end: 100.0,
).animate(
CurvedAnimation(parent: menuController, curve: Curves.elasticOut),
);
menuscale = Tween<double>(
begin: 1.5,
end: 0.0,
).animate(
CurvedAnimation(parent: menuController, curve: Curves.fastOutSlowIn),
);
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.blueGrey,
child: Scaffold(
backgroundColor: Colors.transparent,
body: Stack(
children: <Widget>[
Positioned(
left: -27,
right: -27,
top: 57,
bottom: 57,
child: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: CustomPaint(
painter: CurvedblacklinePainter(),
),
),
),
Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: CustomPaint(
painter: CurvedblacklinePainter(),
),
),
Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: AnimatedBuilder(
animation: menuController,
builder: (context, widget) {
return Transform.rotate(
angle: radians(rotation.value),
child:
Stack(alignment: Alignment.center, children: <Widget>[
_buildButton(0,
color: Colors.red, icon: FontAwesomeIcons.thumbtack),
_buildButton(45,
color: Colors.green, icon: FontAwesomeIcons.sprayCan),
_buildButton(90,
color: Colors.orange, icon: FontAwesomeIcons.fire),
_buildButton(270,
color: Colors.pink, icon: FontAwesomeIcons.car),
_buildButton(315,
color: Colors.yellow, icon: FontAwesomeIcons.bolt),
Transform.scale(
scale: menuscale.value - 1,
child: FloatingActionButton(
child: Icon(FontAwesomeIcons.timesCircle),
onPressed: _close,
backgroundColor: Colors.red),
),
Transform.scale(
scale: menuscale.value,
child: FloatingActionButton(
child: Icon(
FontAwesomeIcons.solidDotCircle,
color: Colors.red,
),
onPressed: _open),
)
]),
);
},
),
),
],
),
),
);
}
_buildButton(double angle, {Color color, IconData icon}) {
final double rad = radians(angle);
return Transform(
transform: Matrix4.identity()
..translate(
(translation.value) * cos(rad), (translation.value) * sin(rad)),
child: FloatingActionButton(
child: Icon(icon),
backgroundColor: color,
onPressed: _close,
elevation: 0));
}
_open() {
print('OPEN CLICKED');
menuController.forward();
}
_close() {
print('CLOSE CLICKED');
menuController.reverse();
}
}
class CurvedblacklinePainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
Paint paint = new Paint()
..color = Colors.white
..strokeWidth = 1.8
..style = PaintingStyle.stroke;
Path path = Path();
path.moveTo(0, 0);
var secondEndPoint = Offset(0, size.height);
path.arcToPoint(secondEndPoint,
radius: Radius.circular((size.width / 2)),
clockwise: true,
largeArc: false);
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
I improve your code and i fixed it as below ,
please replace my code with your code
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:vector_math/vector_math.dart' show radians, Vector3;
class Homewidgettoslack extends StatefulWidget {
Homewidgettoslack({
Key key,
}) : super(key: key);
#override
_HomewidgettoslackState createState() => _HomewidgettoslackState();
}
class _HomewidgettoslackState extends State<Homewidgettoslack>
with TickerProviderStateMixin {
Animation<double> rotation;
Animation<double> translationY;
Animation<double> translationX;
Animation<double> menuscale;
AnimationController menuController;
double _menuIconSize;
#override
void initState() {
super.initState();
menuController =
AnimationController(duration: Duration(milliseconds: 900), vsync: this);
rotation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(
CurvedAnimation(
parent: menuController,
curve: Interval(
0.0,
0.7,
curve: Curves.decelerate,
),
),
);
menuscale = Tween<double>(
begin: 1.5,
end: 0.0,
).animate(
CurvedAnimation(parent: menuController, curve: Curves.fastOutSlowIn),
);
}
#override
Widget build(BuildContext context) {
ScreenUtil.init(width: 360, height: 640, allowFontScaling: false);
_menuIconSize = ScreenUtil().setWidth(60);
double _bigCurveHeight = ScreenUtil().setHeight(520);
double _bigCurveWidth = ScreenUtil().setWidth(250);
translationY = Tween<double>(
begin: 0.0,
end: ((_bigCurveHeight / 2) - (_menuIconSize / 2)),
).animate(
CurvedAnimation(parent: menuController, curve: Curves.elasticOut),
);
translationX = Tween<double>(
begin: 0.0,
end: (_bigCurveWidth - (_menuIconSize / 2)),
).animate(
CurvedAnimation(parent: menuController, curve: Curves.elasticOut),
);
return Container(
color: Colors.blueGrey,
child: Scaffold(
backgroundColor: Colors.transparent,
body: Align(
alignment: Alignment.centerLeft,
child: Stack(
alignment: Alignment.centerLeft,
children: <Widget>[
Container(
height: _bigCurveHeight,
width: _bigCurveWidth,
child: CustomPaint(
painter: CurvedblacklinePainter(strokeWidth: 2),
),
),
Container(
height: ScreenUtil().setHeight(350),
width: ScreenUtil().setWidth(130),
child: CustomPaint(
painter: CurvedblacklinePainter(
strokeWidth: ScreenUtil().setWidth(60)),
),
),
Container(
child: AnimatedBuilder(
animation: menuController,
builder: (context, widget) {
return Transform.rotate(
angle: radians(rotation.value),
child:
Stack(alignment: Alignment.center, children: <Widget>[
_buildButton(0,
color: Colors.red,
icon: FontAwesomeIcons.thumbtack),
_buildButton(45,
color: Colors.green,
icon: FontAwesomeIcons.sprayCan),
_buildButton(90,
color: Colors.orange, icon: FontAwesomeIcons.fire),
_buildButton(270,
color: Colors.pink, icon: FontAwesomeIcons.car),
_buildButton(315,
color: Colors.yellow, icon: FontAwesomeIcons.bolt),
Transform.scale(
scale: menuscale.value - 1,
child: FloatingActionButton(
child: Icon(FontAwesomeIcons.timesCircle),
onPressed: _close,
backgroundColor: Colors.red),
),
Transform.scale(
scale: menuscale.value,
child: FloatingActionButton(
child: Icon(
FontAwesomeIcons.solidDotCircle,
color: Colors.red,
),
onPressed: _open),
)
]),
);
},
),
),
],
),
),
),
);
}
_buildButton(double angle, {Color color, IconData icon}) {
final double rad = radians(angle);
return Transform(
transform: Matrix4.identity()
..translate(
(translationX.value) * cos(rad), (translationY.value) * sin(rad)),
child: FloatingActionButton(
child: Icon(icon),
backgroundColor: color,
onPressed: _close,
elevation: 0));
}
_open() {
print('OPEN CLICKED');
menuController.forward();
}
_close() {
print('CLOSE CLICKED');
menuController.reverse();
}
}
class CurvedblacklinePainter extends CustomPainter {
final double strokeWidth;
CurvedblacklinePainter({#required this.strokeWidth});
#override
void paint(Canvas canvas, Size size) {
Paint paint = new Paint()
..color = Colors.white
..strokeWidth = strokeWidth
..style = PaintingStyle.stroke;
canvas.drawOval(
Rect.fromCenter(
center: Offset(-size.width / 2, size.height / 2),
width: size.width * 3,
height: size.height),
paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
and add dependency in your pubspec.yaml :
dev_dependencies:
flutter_test:
sdk: flutter
font_awesome_flutter: ^8.8.1
flutter_screenutil: ^2.1.0
result screenshot:
Related
I'm trying to use the Expandable Floating Action Button available at the link: https://docs.flutter.dev/cookbook/effects/expandable-fab but the internal buttons don't work, I press them and nothing happens. I'm using this widget inside a Stack. Below the Expandable Floating Action Button code is where it is used;
#immutable
class ExpandableFab extends StatefulWidget {
final bool? initialOpen;
final double distance;
final List<Widget> children;
final VoidCallback onTap;
const ExpandableFab({
super.key,
this.initialOpen,
required this.distance,
required this.children,
required this.onTap,
});
#override
_ExpandableFabState createState() => _ExpandableFabState();
}
class _ExpandableFabState extends State<ExpandableFab>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation<double> _expandAnimation;
bool _open = false;
#override
void initState() {
super.initState();
_open = widget.initialOpen ?? false;
_controller = AnimationController(
value: _open ? 1.0 : 0.0,
duration: const Duration(milliseconds: 250),
vsync: this,
);
_expandAnimation = CurvedAnimation(
curve: Curves.fastOutSlowIn,
reverseCurve: Curves.easeOutQuad,
parent: _controller,
);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
void _toggle() {
setState(() {
_open = !_open;
if (_open) {
_controller.forward();
} else {
_controller.reverse();
}
});
}
#override
Widget build(BuildContext context) {
return SizedBox.expand(
child: Stack(
alignment: Alignment.bottomRight,
clipBehavior: Clip.none,
children: [
_buildTapToCloseFab(),
..._buildExpandingActionButtons(),
_buildTapToOpenFab()
],
),
);
}
Widget _buildTapToCloseFab() {
return SizedBox(
width: 56.0,
height: 56.0,
child: Center(
child: Material(
shape: const CircleBorder(),
clipBehavior: Clip.antiAlias,
elevation: 4.0,
child: InkWell(
onTap: _toggle,
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Icon(Icons.close, color: Colors.deepPurple),
),
),
),
),
);
}
List<Widget> _buildExpandingActionButtons() {
final children = <Widget>[];
final count = widget.children.length;
final step = 90.0 / (count - 1);
for (var i = 0, angleInDegrees = 0.0;
i < count;
i++, angleInDegrees += step) {
children.add(
_ExpandingActionButton(
directionInDegrees: angleInDegrees,
maxDistance: widget.distance,
progress: _expandAnimation,
child: widget.children[i],
),
);
}
return children;
}
Widget _buildTapToOpenFab() {
return IgnorePointer(
ignoring: _open,
child: AnimatedContainer(
transformAlignment: Alignment.center,
transform: Matrix4.diagonal3Values(
_open ? 0.7 : 1.0,
_open ? 0.7 : 1.0,
1.0,
),
duration: const Duration(milliseconds: 250),
curve: const Interval(0.0, 0.5, curve: Curves.easeOut),
child: AnimatedOpacity(
opacity: _open ? 0.0 : 1.0,
curve: const Interval(0.25, 1.0, curve: Curves.easeInOut),
duration: const Duration(milliseconds: 250),
child: ClipOval(
child: Material(
color: Colors.deepPurple, // Button color
child: InkWell(
//splashColor: Colors.white, // Splash color
onLongPress: _toggle,
onTap: widget.onTap,
onDoubleTap: () {},
child: const SizedBox(
width: 56,
height: 56,
child: Icon(Icons.add, color: Colors.white),
),
),
),
),
),
),
);
}
}
#immutable
class _ExpandingActionButton extends StatelessWidget {
const _ExpandingActionButton({
Key? key,
required this.directionInDegrees,
required this.maxDistance,
required this.progress,
required this.child,
}) : super(key: key);
final double directionInDegrees;
final double maxDistance;
final Animation<double> progress;
final Widget child;
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: progress,
builder: (context, child) {
final offset = Offset.fromDirection(
directionInDegrees * (math.pi / 180.0),
progress.value * maxDistance,
);
return Positioned(
right: 4.0 + offset.dx,
bottom: 4.0 + offset.dy,
child: Transform.rotate(
angle: (1.0 - progress.value) * math.pi / 2,
child: child!,
),
);
},
child: FadeTransition(opacity: progress, child: child),
);
}
}
#immutable
class ActionButton extends StatelessWidget {
const ActionButton({
super.key,
this.onPressed,
required this.icon,
});
final VoidCallback? onPressed;
final Widget icon;
#override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Material(
shape: const CircleBorder(),
clipBehavior: Clip.antiAlias,
color: Colors.deepPurple,
elevation: 4.0,
child: IconTheme.merge(
data: theme.primaryIconTheme,
child: IconButton(onPressed: onPressed, icon: icon),
),
);
}
}
Expandable FLoating Action Button is used within this Stack:
Stack(
children: <Widget>[
// some widget
Positioned(
right: 30.0,
bottom: 140.0,
child: SizedBox(
width: 60.0,
height: 60.0,
child: ExpandableFab(
distance: 112.0,
children: <Widget>[
ActionButton(
icon: const Icon(Icons.delete),
onPressed: () {
debugPrint("test1");
},
),
ActionButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
debugPrint("test2");
},
),
ActionButton(
icon: const Icon(Icons.arrow_forward),
onPressed: () {
debugPrint("test3");
},
)
],
onTap: () {
debugPrint("test4");
},
),
),
),
],
),
If you do want to use it in the stack, place the code for the ExpandedButton in the last position so that it gets reported with touch events.
Code sample:
Stack(
children: <Widget>[
// All your other widgets go up here
RandomWidget(),
RandomWidget2(),
........... //other widgets
// This comes last.
Positioned(
right: 30.0,
bottom: 140.0,
child: SizedBox(
width: 60.0,
height: 60.0,
child: ExpandableFab(
distance: 112.0,
children: <Widget>[
ActionButton(
icon: const Icon(Icons.delete),
onPressed: () {
debugPrint("test1");
},
),
ActionButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
debugPrint("test2");
},
),
ActionButton(
icon: const Icon(Icons.arrow_forward),
onPressed: () {
debugPrint("test3");
},
)
],
onTap: () {
debugPrint("test4");
},
),
),
),
],
),
By the way, Why are you using this in a stack? You can pretty much use it as a FloatingActionButton by passing it to floatingActionButton parameter of the Scaffold.
I am trying to build a login/signup page by using SingleTickerProviderStateMixin but I am getting errors on AnimationController. How I can solve it, Even try to use nullable? but I have to check! on operation. At that time I also get errors. Please help me to find out any solutions
class log2 extends StatefulWidget {
const log2({Key? key}) : super(key: key);
#override
State<log2> createState() => _log2State();
}
class _log2State extends State<log2> with SingleTickerProviderStateMixin {
bool islogin = true;
Animation<double>? containSize;
late AnimationController animationController;
Duration duration = Duration(milliseconds: 270);
#override
Widget build(BuildContext context) {
double mobHeight = MediaQuery.of(context).size.height;
double mobWidth = MediaQuery.of(context).size.width;
TextEditingController emailController = TextEditingController(),
passController = TextEditingController();
initState() {
super.initState();
animationController =
AnimationController(vsync: this, duration: duration);
SystemChrome.setEnabledSystemUIOverlays([]);
}
//For Ticker Screen
containSize = Tween<double>(begin: mobHeight * 0.1, end: mobWidth * 0.1)
.animate(
CurvedAnimation(parent: animationController, curve: Curves.linear));
#override
void dispose() {
animationController.dispose();
super.dispose();
}
Build Widget:
return SafeArea(
child: Scaffold(
body: Stack(
children: [
Padding(
padding: const EdgeInsets.only(top: 50),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"Welcome",
style: TextStyle(color: Colors.black, fontSize: 25),
),
SizedBox(
height: 40,
),
Transform.rotate(
angle: pi / 4,
child: Image.asset(
"images/computerdesk.png",
fit: BoxFit.cover,
height: mobHeight / 3,
width: mobWidth / 0.2,
),
),
AnimatedBuilder(
animation: animationController,
builder: (context, child) {
return buiderSignupContainer();
},
),
],
),
),
Positioned(
child: Image.asset("images/ball.png"),
),
],
),
),
);
}
2nd stack widget:
Widget buiderSignupContainer() {
double mobheight = MediaQuery.of(context).size.height;
return Align(
alignment: Alignment.bottomCenter,
child: GestureDetector(
child: Container(
width: double.infinity,
height: containSize?.value,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topRight: Radius.circular(30),
topLeft: Radius.circular(30),
),
color: Colors.black.withAlpha(50),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Don't Have a Account Yet?"),
VerticalDivider(width: 5),
Text(
"Sign Up",
style: TextStyle(color: Colors.red),
),
],
),
),
onTap: () {
setState(() {
islogin = !islogin;
});
},
),
);
}
You put initState and dispose methods to wrong block. Change it to:
class _log2State extends State<log2> with SingleTickerProviderStateMixin { bool islogin = true; Animation<double>? containSize; late AnimationController animationController; Duration duration = Duration(milliseconds: 270);
bool islogin = true;
Animation<double>? containSize;
late AnimationController animationController;
Duration duration = Duration(milliseconds: 270);
#override
void initState() {
super.initState();
animationController = AnimationController(vsync: this, duration: duration);
SystemChrome.setEnabledSystemUIOverlays([]);
//For Ticker Screen
containSize = Tween<double>(begin: mobHeight * 0.1, end: mobWidth * 0.1).animate(CurvedAnimation(parent: animationController, curve: Curves.linear));
}
#override
void dispose() {
animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
double mobHeight = MediaQuery.of(context).size.height;
double mobWidth = MediaQuery.of(context).size.width;
TextEditingController emailController = TextEditingController() passController = TextEditingController();
return SafeArea(
child: Scaffold(
body: Stack(
children: [
Padding(
padding: const EdgeInsets.only(top: 50),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"Welcome",
style: TextStyle(color: Colors.black, fontSize: 25),
),
SizedBox(
height: 40,
),
Transform.rotate(
angle: pi / 4,
child: Image.asset(
"images/computerdesk.png",
fit: BoxFit.cover,
height: mobHeight / 3,
width: mobWidth / 0.2,
),
),
AnimatedBuilder(
animation: animationController,
builder: (context, child) {
return buiderSignupContainer();
},
),
],
),
),
Positioned(
child: Image.asset("images/ball.png"),
),
],
),
),
);
}
this code is for 4 floating action button but the problem is that the onPressed didn't work with the 3 animated button and work with the fixed button
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
AnimationController animationController;
Animation degOneTranslationAnimation, degTwoTranslationAnimation, degThreeTranslationAnimation;
Animation rotationAnimation;
double getRadiansFromDegree(double degree) {
double unitRadian = 57.295779513;
return degree / unitRadian;
}
#override
void initState() {
animationController = AnimationController(vsync: this, duration: Duration(milliseconds: 250));
degOneTranslationAnimation = TweenSequence(<TweenSequenceItem>[
TweenSequenceItem<double>(tween: Tween<double >(begin: 0.0,end: 1.2), weight: 75.0),
TweenSequenceItem<double>(tween: Tween<double>(begin: 1.2,end: 1.0), weight: 25.0),
])
.animate(animationController);
degTwoTranslationAnimation = TweenSequence(<TweenSequenceItem>[
TweenSequenceItem<double>(tween: Tween<double >(begin: 0.0,end: 1.4), weight: 55.0),
TweenSequenceItem<double>(tween: Tween<double>(begin: 1.4,end: 1.0), weight: 45.0)
])
.animate(animationController);
degThreeTranslationAnimation = TweenSequence(<TweenSequenceItem>[
TweenSequenceItem<double>(tween: Tween<double >(begin: 0.0,end: 1.75), weight: 35.0),
TweenSequenceItem<double>(tween: Tween<double>(begin: 1.75,end: 1.0), weight: 65.0)
])
.animate(animationController);
rotationAnimation = Tween<double>(begin: 180.0, end: 0.0)
.animate(CurvedAnimation(parent: animationController, curve: Curves.easeOut));
super.initState();
animationController.addListener(() {
setState(() {});
});
}
#override
void dispose() {
animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Scaffold(
body: Container(
width: size.width,
height: size.height,
child: Stack(
children: <Widget>[
Positioned(
right: 30,
bottom: 30,
child: Stack(
children: <Widget>[
Transform.translate(
offset: Offset.fromDirection(
getRadiansFromDegree(270), degOneTranslationAnimation.value * 100),
child: Transform(
transform: Matrix4.rotationZ(getRadiansFromDegree(rotationAnimation.value))
..scale(degOneTranslationAnimation.value),
alignment: Alignment.center,
child: CircularButton(
color: Colors.blue,
width: 50,
height: 50,
icon: Icon(
Icons.add,
color: Colors.white,
),
),
),
),
Transform.translate(
offset: Offset.fromDirection(
getRadiansFromDegree(225), degOneTranslationAnimation.value * 100),
child: Transform(
transform: Matrix4.rotationZ(getRadiansFromDegree(rotationAnimation.value))
..scale(degOneTranslationAnimation.value),
alignment: Alignment.center,
child: CircularButton(
color: Colors.black,
width: 50,
height: 50,
icon: Icon(
Icons.camera_alt,
color: Colors.white,
),
),
),
),
Transform.translate(
offset: Offset.fromDirection(
getRadiansFromDegree(180), degOneTranslationAnimation.value * 100),
child: Transform(
transform: Matrix4.rotationZ(getRadiansFromDegree(rotationAnimation.value))
..scale(degOneTranslationAnimation.value),
alignment: Alignment.center,
child: CircularButton(
color: Colors.orangeAccent,
width: 50,
height: 50,
icon: Icon(
Icons.person,
color: Colors.white,
),
),
),
),
Transform(
transform: Matrix4.rotationZ(getRadiansFromDegree(rotationAnimation.value)),
alignment: Alignment.center,
child: CircularButton(
color: Colors.red,
width: 60,
height: 60,
icon: Icon(
Icons.menu,
color: Colors.white,
),
onClick: () {
if (animationController.isCompleted) {
animationController.reverse();
} else {
animationController.forward();
}
},
),
),
],
),
)
],
),
),
);
}
}
that's my complete code you can paste it and test it i need some help because the onPressed didn't work with the animated button and only works with fixed button and thank you
I could not use run your code since I do not have any idea about "CircularButton" which is probably custom widget you have created.
I can suggest you to use fab_circular_menu if it fits your needs.
Happy coding.
i need help for recreate this sample animation with flutter, i try with using showDialog() but i don't see animation, i used Hero() with tag but don'work, please help me!
it can be done using Stack, AnimatedPositioned and AnimatedContainer:
check sample code below:
import 'package:flutter/material.dart';
class MyPage extends StatefulWidget {
#override
_MyPageState createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
bool isOpen = false;
// AnimatedContainer dimensions:
double width = 55, height = 55;
// AnimatedPositioned positions:
double left = 20, bottom = 20;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
color: Color(0xff151515),
child: Stack(
children: <Widget>[
Positioned(
left: 0,
top: 0,
right: 0,
bottom: 0,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("TIMER", style: TextStyle(color: Colors.white, fontSize: 36, fontWeight: FontWeight.w700),),
Text("00 : 00", style: TextStyle(color: Colors.white, fontSize: 36, fontWeight: FontWeight.w700),),
],
),
),
Positioned(
left: 0,
top: 0,
right: 0,
bottom: 0,
child: GestureDetector(
child: AnimatedOpacity(
// If the widget is visible, animate to 0.0 (invisible).
// If the widget is hidden, animate to 1.0 (fully visible).
opacity: isOpen ? 1.0 : 0.0,
duration: Duration(milliseconds: 500),
// The green box must be a child of the AnimatedOpacity widget.
child: Container(
width: 200.0,
height: 200.0,
color: Colors.black45,
),
),
onTap: () {
if (isOpen) {
setState(() {
width = 55; height = 55;
left = 20; bottom = 20;
isOpen = false;
});
}
},
),
),
AnimatedPositioned(
left: left,
bottom: bottom,
duration: Duration(milliseconds: 500),
curve: Curves.easeInOut,
child: GestureDetector(
child: AnimatedContainer(
width: width,
height: height,
decoration: new BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(28)),
duration: Duration(milliseconds: 500),
curve: Curves.easeInOut,
// child: dialog container content goes here,
),
onTap: () {
if (!isOpen) {
setState(() {
width = 200; height = 200;
left = 60; bottom = 60;
isOpen = !isOpen;
});
}
},
),
),
Positioned(
bottom: 30,
left: 40,
child: IgnorePointer(
child: Text(
'1',
style: TextStyle(color: Colors.white, fontSize: 30),
),
),
),
],
),
),
),
);
}
}
I'm working a little bit with Flutter and I understand everything except Animations (i never liked working with animations).
I've tried to implement a Backdrop in my Flutter app using this Flutter Demo. Implementing the Backdrop is easy.
I stuck on implementing the navigation of the Backdrop which let it slide down and up by the hamburger button.
I have read the Animations in Flutter Tutorial. I understood the basics of animations (controller, animation etc.). But in this Backdrop example, it is a little bit different.
Can someone explain to me this case step by step? Thanks.
I managed to reach a solution combining the information of the following links. Please note that I’m not an expert in Flutter (neither animations). That being said, suggestions and corrections are really appreciated.
Backdrop implementation:
https://github.com/material-components/material-components-flutter-codelabs/blob/104-complete/mdc_100_series/lib/backdrop.dart
Animation:
https://medium.com/#vigneshprakash15/flutter-image-rotate-animation-6b6eaed7fb33
After creating your backdrop.dart file, go to the _BackdropTitle class and edit the part where you define the IconButton for the menu and close icons. You have to perform a rotation in the Opacity item that contains the Icon:
icon: Stack(children: <Widget>[
new Opacity(
opacity: new CurvedAnimation(
parent: new ReverseAnimation(animation),
curve: const Interval(0.5, 1.0),
).value,
child: new AnimatedBuilder(
animation: animationController,
child: new Container(
child: new Icon(Icons.close),
),
builder: (BuildContext context, Widget _widget) {
return new Transform.rotate(
angle: animationController.value * -6.3,
child: _widget,
);
},
),
),
new Opacity(
opacity: new CurvedAnimation(
parent: animation,
curve: const Interval(0.5, 1.0),
).value,
child: new AnimatedBuilder(
animation: animationController,
child: new Container(
child: new Icon(Icons.menu),
),
builder: (BuildContext context, Widget _widget) {
return new Transform.rotate(
angle: animationController.value * 6.3,
child: _widget,
);
},
),
),
],
I had to use a negative value in the angle of the close transformation in order to rotate it in the same direction of the menu animation.
And here is the full code:
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
import 'menu.dart';
const double _kFlingVelocity = 2.0;
class Backdrop extends StatefulWidget {
final MenuCategory currentCategory;
final Widget frontLayer;
final Widget backLayer;
final Widget frontTitle;
final Widget backTitle;
const Backdrop({
#required this.currentCategory,
#required this.frontLayer,
#required this.backLayer,
#required this.frontTitle,
#required this.backTitle,
}) : assert(currentCategory != null),
assert(frontLayer != null),
assert(backLayer != null),
assert(frontTitle != null),
assert(backTitle != null);
#override
_BackdropState createState() => _BackdropState();
}
class _BackdropState extends State<Backdrop>
with SingleTickerProviderStateMixin {
final GlobalKey _backdropKey = GlobalKey(debugLabel: 'Backdrop');
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 300),
value: 1.0,
vsync: this,
);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
void didUpdateWidget(Backdrop old) {
super.didUpdateWidget(old);
if (widget.currentCategory != old.currentCategory) {
_toggleBackdropLayerVisibility();
} else if (!_frontLayerVisible) {
_controller.fling(velocity: _kFlingVelocity);
}
}
Widget _buildStack(BuildContext context, BoxConstraints constraints) {
const double layerTitleHeight = 48.0;
final Size layerSize = constraints.biggest;
final double layerTop = layerSize.height - layerTitleHeight;
Animation<RelativeRect> layerAnimation = RelativeRectTween(
begin: RelativeRect.fromLTRB(
0.0, layerTop, 0.0, layerTop - layerSize.height),
end: RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0),
).animate(_controller.view);
return Stack(
key: _backdropKey,
children: <Widget>[
widget.backLayer,
PositionedTransition(
rect: layerAnimation,
child: _FrontLayer(
onTap: _toggleBackdropLayerVisibility,
child: widget.frontLayer,
),
),
],
);
}
#override
Widget build(BuildContext context) {
var appBar = AppBar(
brightness: Brightness.light,
elevation: 0.0,
titleSpacing: 0.0,
title: _BackdropTitle(
animationController: _controller,
onPress: _toggleBackdropLayerVisibility,
frontTitle: widget.frontTitle,
backTitle: widget.backTitle,
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: () {
// TODO
},
)
],
);
return Scaffold(
appBar: appBar,
body: LayoutBuilder(builder: _buildStack)
);
}
bool get _frontLayerVisible {
final AnimationStatus status = _controller.status;
return status == AnimationStatus.completed ||
status == AnimationStatus.forward;
}
void _toggleBackdropLayerVisibility() {
_controller.fling(
velocity: _frontLayerVisible ? -_kFlingVelocity : _kFlingVelocity);
}
}
class _FrontLayer extends StatelessWidget {
const _FrontLayer({
Key key,
this.onTap,
this.child,
}) : super(key: key);
final VoidCallback onTap;
final Widget child;
#override
Widget build(BuildContext context) {
return Material(
elevation: 16.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16.0),
topRight: Radius.circular(16.0)
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: Container(
height: 40.0,
alignment: AlignmentDirectional.centerStart,
),
),
Expanded(
child: child,
),
],
),
);
}
}
class _BackdropTitle extends AnimatedWidget {
final AnimationController animationController;
final Function onPress;
final Widget frontTitle;
final Widget backTitle;
_BackdropTitle({
Key key,
this.onPress,
#required this.frontTitle,
#required this.backTitle,
#required this.animationController,
}) : assert(frontTitle != null),
assert(backTitle != null),
assert(animationController != null),
super(key: key, listenable: animationController.view);
#override
Widget build(BuildContext context) {
final Animation<double> animation = this.listenable;
return DefaultTextStyle(
style: Theme.of(context).primaryTextTheme.title,
softWrap: false,
overflow: TextOverflow.ellipsis,
child: Row(children: <Widget>[
// branded icon
SizedBox(
width: 72.0,
child: IconButton(
padding: EdgeInsets.only(right: 8.0),
onPressed: this.onPress,
icon: Stack(children: <Widget>[
new Opacity(
opacity: new CurvedAnimation(
parent: new ReverseAnimation(animation),
curve: const Interval(0.5, 1.0),
).value,
child: new AnimatedBuilder(
animation: animationController,
child: new Container(
child: new Icon(Icons.close),
),
builder: (BuildContext context, Widget _widget) {
return new Transform.rotate(
angle: animationController.value * -6.3,
child: _widget,
);
},
),
),
new Opacity(
opacity: new CurvedAnimation(
parent: animation,
curve: const Interval(0.5, 1.0),
).value,
child: new AnimatedBuilder(
animation: animationController,
child: new Container(
child: new Icon(Icons.menu),
),
builder: (BuildContext context, Widget _widget) {
return new Transform.rotate(
angle: animationController.value * 6.3,
child: _widget,
);
},
),
),
]),
),
),
// Here, we do a custom cross fade between backTitle and frontTitle.
// This makes a smooth animation between the two texts.
Stack(
children: <Widget>[
Opacity(
opacity: CurvedAnimation(
parent: ReverseAnimation(animation),
curve: Interval(0.5, 1.0),
).value,
child: FractionalTranslation(
translation: Tween<Offset>(
begin: Offset.zero,
end: Offset(0.5, 0.0),
).evaluate(animation),
child: backTitle,
),
),
Opacity(
opacity: CurvedAnimation(
parent: animation,
curve: Interval(0.5, 1.0),
).value,
child: FractionalTranslation(
translation: Tween<Offset>(
begin: Offset(-0.25, 0.0),
end: Offset.zero,
).evaluate(animation),
child: frontTitle,
),
),
],
)
]),
);
}
}
You can find a sample demo for backdrop in flutter gallery..which you will get while installing Flutter.