Sequencing Flutter Animations - android

I am building a language app, when the student is learning a new word, each syllable in the new word will be repeated three times. I have three progress bars to indicate to the student how long they have to wait until the next repetition.
Each progress bar has a duration of 3 seconds. When the first progress bar completes, it should trigger the second progress bar, when the second finishes it should trigger the third progress bar.
When the third bar completes, it should reset all of the progress bars and restart the process all over again.
The key here is to sequence the animation of each progress bar so that they can call a function on complete. This is the beginning of my problem.
I don't know how to get the progress bars to animate in sequence and to call a function when they are complete.
How do I go about achieving this with flutter? I already built this app with KivyMD but now I have to rebuild it with Flutter. However, KivyMD animations are simple but Flutter animations are complicated.
import 'package:blog/screens/widgets/nav_menu.dart';
import 'package:flutter/material.dart';
class AnimationTest3 extends StatefulWidget {
const AnimationTest3({Key? key}) : super(key: key);
#override
_AnimationTest3State createState() => _AnimationTest3State();
}
class _AnimationTest3State extends State<AnimationTest3>
with TickerProviderStateMixin {
late AnimationController _controller;
double progress1 = 0;
double progress2 = 0;
double progress3 = 0;
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: Drawer(child: NavMenu()),
appBar: AppBar(title: Text("Animation Test 3"), elevation: 0),
body: LayoutBuilder(
builder: (context, constraints) {
return Column(children: [
Container(
height: constraints.maxHeight * 0.3, color: Colors.indigo[50]),
Container(height: constraints.maxHeight * 0.4, color: Colors.white),
Container(
height: constraints.maxHeight * 0.3,
color: Colors.indigo[50],
child: LayoutBuilder(
builder: (context, constraints) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// PROGRESS BAR #1
Container(
padding: EdgeInsets.symmetric(horizontal: 2.0),
height: 4,
width: constraints.maxWidth * 1 / 3,
child: LinearProgressIndicator(value: progress1),
),
Container(
// PROGRESS BAR #2
padding: EdgeInsets.symmetric(horizontal: 2.0),
height: 4,
width: constraints.maxWidth * 1 / 3,
child: LinearProgressIndicator(value: progress2),
),
Container(
// PROGRESS BAR #3
padding: EdgeInsets.symmetric(horizontal: 2.0),
height: 4,
width: constraints.maxWidth * 1 / 3,
child: LinearProgressIndicator(value: progress3),
),
],
);
},
),
)
]);
},
),
);
}
}

look into this reference for staggered animation, staggered animation, your situation should be:
controller = AnimationController(
duration: const Duration(milliseconds: 9000), vsync: this);
progress1 = Tween<double>(
begin: 0.0,
end: 0.3,
).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(
0.0,
0.33,
curve: Curves.ease,
),
),
),
progress2 = Tween<double>(
begin: 0.0,
end: 0.3,
).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(
0.33,
0.66,
curve: Curves.ease,
),
),
),
progress3 = Tween<double>(
begin: 0.0,
end: 0.3,
).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(
0.66,
1.0,
curve: Curves.ease,
),
),
),

Related

Expandable Floating Action Button doesn't work

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.

LateInitializationError: Field 'animationController' has not been initialized even using initState()

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"),
),
],
),
),
);
}

How to design a flutter semi circular menu with dynamic radius

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:

Animation with hero and showDialog in flutter

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),
),
),
),
],
),
),
),
);
}
}

Flutter Backdrop Animation

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.

Categories

Resources