Flutter animatedOpacity onEnd not being called - flutter

I'm unable to get the onEnd portion of AnimatedOpacity to get called.
I've got a _visible variable set to false, I'm changing that value with setState that is called on a button press, however the print statement in onEnd is never called.
Declared at the top of my widget and set to false initially.
bool _visible = false;
setState is updating visible from a textbutton onPressed
setState(() {
_visible = !_visible;
});
The visibility widget is toggled once the above state is passed however the container that is the child of the AnimatedOpacity widget is immediately shown ( there is no animated fade out from opacity to solid black ), and the only way to trigger the animation completely is to modify the _visible ? 1.0 : 0.0.
The goal is to get the container once triggered to change from an opacity of 0 to 1 then call onEnd, which currently is not happening.
Visibility(
visible: _visible,
child: Center(
child: AnimatedOpacity(
opacity: _visibility ? 1.0 : 0.0,
duration: const Duration(milliseconds: 1000),
onEnd: () {
print("I am never called");
},
child: Container(
width: double.infinity,
height: double.infinity,
color: Colors.black,
),
),
),
),
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MyWidgetState();
}
}
class _MyWidgetState extends State<MyWidget> {
bool _visible = false;
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: Stack(children: [
TextButton(
style: ElevatedButton.styleFrom(
elevation: 10,
shape: CircleBorder(),
primary: Colors.transparent,
onSurface: Colors.transparent,
shadowColor: Colors.transparent,
),
onPressed: () {
setState(() {
_visible = !_visible;
});
},
child: Icon(Icons.home, color: Colors.white),
),
Visibility(
visible: _visible,
child: Center(
child: AnimatedOpacity(
opacity: _visible ? 1 : 0,
duration: const Duration(milliseconds: 1000),
onEnd: () {
print("I am not called but should be");
},
child: Container(
width: 200,
height: 200,
color: Colors.black,
),
),
),
),
]));
}
}

The problem with you code is that the AnimatedOpacity widget is wrapped inside a Visibility widget, which is not animated. Therefore when you call setState to toggle _visible member, the container will become instantly visible. The animation won't run at all, because by the time the Container becomes visible, the _visible member's value is already 1.
To solve this, simply remove Visibility widget, when the AnimatedOpacity reaches opacity value of 0, the Container will be invisible and on value 1 it will be visible, animating between the two states. The print is also executed in onEnd:
class MyWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MyWidgetState();
}
}
class _MyWidgetState extends State<MyWidget> {
bool _visible = false;
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: Stack(children: [
TextButton(
style: ElevatedButton.styleFrom(
elevation: 10,
shape: const CircleBorder(),
primary: Colors.transparent,
onSurface: Colors.transparent,
shadowColor: Colors.transparent,
),
onPressed: () {
setState(() {
_visible = !_visible;
});
},
child: const Icon(Icons.home, color: Colors.white),
),
Center(
child: AnimatedOpacity(
opacity: _visible ? 1 : 0,
duration: const Duration(milliseconds: 1000),
onEnd: () {
print("I am not called but should be");
},
child: Container(
width: 200,
height: 200,
color: Colors.black,
),
),
]));
}
}

Related

How to make custom animated Container from button of the app till half of the app screen

expected behavior
i tried this code but it give me completely difference result from left side and strange animated
double val = 0;
#override
Widget build(BuildContext context) {
return Stack(
children: [
Container(
height: 400,
color: Colors.red,
),
TweenAnimationBuilder(
duration: const Duration(milliseconds: 150),
tween: Tween<double>(begin: 0 , end: val) ,
builder: (BuildContext context, double? value, Widget? child) {
return (
Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..setEntry(0, 3, 200 * value!)
..rotateY((pi/6)*value),
child: DefaultTabController(
length: 5,
child: Scaffold(
body: Center(
child: Container(
color: Colors.yellowAccent,
child: IconButton(
onPressed: () {
setState(() {
setState(() {
val == 0 ? val = 1 : val = 0 ;
});
});
},
icon: Text('tab me'),
),
),
)
)
)
)
);
}
)
],
);
}
also i need only the red Container the one who animated from down to up , but i don't know why the main screen is also animate .. i need it never animated ..
any suggestion most welcome guys .. thanks
Instead of custom animation, you can use AnimatedContainer().
Create a boolean like selected which will tell the animated container when to close and when to open the container. And using setState you can toggle the animation.
Align your AnimatedContainer() with Align() and give alignment: Alignment.bottomCenter. And give height:0 is not selected and when selected give height the half of screen using MediaQuery.of(context)
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
bool selected = false;
#override
Widget build(BuildContext context) {
return Column(children: [
ElevatedButton(
onPressed: () {
setState(() {
selected = !selected;
});
},
child: Text("Tap Me!!"),
),
Spacer(),
GestureDetector(
onTap: () {
setState(() {
selected = !selected;
});
},
child: Align(
alignment: Alignment.bottomCenter,
child: AnimatedContainer(
width: double.infinity,
height: selected ? MediaQuery.of(context).size.height / 2 : 0,
color: selected ? Colors.red : Colors.blue,
alignment:
selected ? Alignment.center : AlignmentDirectional.topCenter,
duration: const Duration(seconds: 2),
curve: Curves.fastOutSlowIn,
child: const FlutterLogo(size: 75),
),
),
)
]);
}
}
You can try the same code in dartpad here

Toggle an animation between two separate Card classes in a Dialog with Flutter

In my Flutter application, I have a function that will open a dialog that shows two Stateful cards. I'm hoping to make it so that when one card is pressed, it will light up and the animation will run. Then, the other card will fade. However, in the current configuration, both options can be selected at once, which in a production setting might confuse the user. When the dialog opens, it should look like this:
Then the user should be able to select one or the other, and the buttons should toggle back and forth like so:
However, with the current way that my code is set up, the buttons could both be toggled at the same time, like this:
I haven't been able to figure out how to change the way that my code works to fit this. I've tried using Flutter's native ToggleButtons class, but I haven't been able to make it work to fit my needs in this project. Here's the code:
class CustomRoomStateCard extends StatefulWidget {
final bool isPublicCard; // true: card is green, false: card is red
static bool
choice; //true: user's room will be public, false: user's room will be private
CustomRoomStateCard({this.isPublicCard});
#override
_CustomRoomStateCardState createState() => _CustomRoomStateCardState();
}
class _CustomRoomStateCardState extends State<CustomRoomStateCard>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation animation;
#override
void initState() {
super.initState();
controller = AnimationController(
upperBound: 1,
duration: Duration(milliseconds: 200),
vsync: this,
);
animation = ColorTween(
begin: (widget.isPublicCard == true
? Colors.green[100]
: Colors.red[100]),
end: (widget.isPublicCard == true ? Colors.green : Colors.red))
.animate(controller);
controller.addListener(() {
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
if (widget.isPublicCard == true) {
CustomRoomStateCard.choice = true;
} else {
CustomRoomStateCard.choice = false;
}
if (animation.isCompleted) {
controller.reverse();
CustomRoomStateCard.choice = false;
print("choice is ${CustomRoomStateCard.choice}");
} else {
controller.forward();
print("choice is ${CustomRoomStateCard.choice}");
}
});
},
child: Card(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
color: animation.value,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.all(15.0),
child: widget.isPublicCard
? Icon(Icons.radar, color: Colors.white)
: Icon(Icons.shield, color: Colors.white),
),
Padding(
padding: EdgeInsets.all(15.0),
child: Text(
widget.isPublicCard ? "Public" : "Private",
style: kBoldText.copyWith(color: Colors.white),
textAlign: TextAlign.center,
))
],
),
));
}
}
Future<void> showPublicPrivateChoiceDialog(BuildContext context) {
List<bool> toggledValues = [false, false]; // an idea
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20.0))),
title: Text(
"Set room privacy level",
style: TextStyle(fontWeight: FontWeight.bold),
),
content: Container(
height: MediaQuery.of(context).size.height * 0.2,
width: MediaQuery.of(context).size.height * 0.7,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
child: CustomRoomStateCard(
isPublicCard: true,
),
),
Expanded(
child: CustomRoomStateCard(
isPublicCard: false,
),
)
],
),
),
actions: [
TextButton(
onPressed: () {
print("the choice is ${CustomRoomStateCard.choice}");
isBroadcasting = CustomRoomStateCard.choice ??
true; // default to true in case they don't press anything
Navigator.pop(context);
return;
},
child: Text(
"Create",
style: TextStyle(fontWeight: FontWeight.bold),
))
],
);
});
}
My first thought would be to make a boolean variable that is true if one of the cards is already active. When I press a card, it would check this variable, change itself accordingly, but then would also have to call setState() in the other card, which I'm not sure how to do at the moment. How can I make it so these two cards will toggle back and forth and not be active at the same time? Any assistance would be greatly appreciated!
This depends on how much control you need over your animations. But if you don't need the controls, you can user AnimatedOpacity(..) to achieve this.
See this example:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool isPublic = true;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
child: Column(
children: [
AnimatedOpacity(
duration: const Duration(milliseconds: 500),
opacity: isPublic ? 1.0 : 0.20,
child: Card(
child: InkWell(
onTap: () {
setState(() {
isPublic = true;
});
print('is public = true');
},
child: SizedBox(
child: Text('Public'),
height: 120,
width: 120,
),
),
color: Colors.green[600],
),
),
SizedBox(height: 20),
AnimatedOpacity(
duration: const Duration(milliseconds: 500),
opacity: !isPublic ? 1.0 : 0.20,
child: Card(
child: InkWell(
onTap: () {
setState(() {
isPublic = false;
});
print('is public = false');
},
child: SizedBox(
child: Text('Private'),
height: 120,
width: 120,
),
),
color: Colors.red[600],
),
),
],
)), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}

Animate FAB button like Gmail Compose Button in Flutter

Am working on a button that animates like Gmail Compose Button. The behavior is such that on scrolling up it shrinks to a circle with icon at center while on scrolling downwards it expands to show icon and text. My implantation works well, but the issue now is that I want a fade like effect for the text such that after the FAB button expands, the text fades in smoothly rather than appearing abruptly.
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
bool isLoaded = false;
bool upDirection = true, flag = true;
ScrollController _scrollController = ScrollController();
#override
void initState() {
// TODO: implement initState
super.initState();
_scrollController
..addListener(() {
upDirection = _scrollController.position.userScrollDirection ==
ScrollDirection.forward;
// makes sure we don't call setState too much, but only when it is needed
if (upDirection != flag) setState(() {});
flag = upDirection;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: Stack(
children: [
SingleChildScrollView(
controller: _scrollController,
child: Stack(
children: [
Positioned(
bottom: MediaQuery.of(context).size.height * 0.1,
right: 20.0,
child: AnimatedContainer(
width: flag ? 170 : 56,
height: 56,
duration: Duration(milliseconds: 300),
child: FloatingActionButton.extended(
backgroundColor: AppColors.customFabRed,
heroTag: null,
onPressed: () {
},
icon: flag
? Icon(
Icons.call,
color: Colors.white,
)
: null,
label: flag
? AnimatedOpacity( //trying to get the text to fade in after the fab is expanded but nothing happens
opacity: flag ? 1.0 : 0.0,
duration: const Duration(milliseconds: 9000),
child: Text(
'Call a doctor',
textAlign: TextAlign.start,
style: TextStyle(
fontSize: 13.5,
height: 1.5,
fontWeight: FontWeight.w400,
fontFamily: 'Euclid',
color: Colors.white,
),
),
)
: Icon(
Icons.call,
color: Colors.white,
))),
),
],
),
);
}
}
Try below code hope it helps to you :
Declare one Boolean variable
bool isFABExtended = false;
Create function for button action change:
void _switchButton() {
setState(
() {
isFABExtended = !isFABExtended;
},
);
}
Declare your Widget:
floatingActionButton: FloatingActionButton.extended(
onPressed: _switchButton,
label: AnimatedSwitcher(
duration: Duration(seconds: 1),
transitionBuilder: (Widget child, Animation<double> animation) =>
FadeTransition(
opacity: animation,
child: SizeTransition(
child: child,
sizeFactor: animation,
axis: Axis.horizontal,
),
),
child: isFABExtended
? Icon(Icons.check)
: Row(
children: [
Padding(
padding: const EdgeInsets.only(right: 4.0),
child: Icon(Icons.add),
),
Text("Add Button")
],
),
),
),
Your Button Look like and

In Expandable Floating action button the gestures in the child widget is not detected in flutter

Trying to implement a floating action button that extends in two dimension and then show some more option of floating action button.
Somehow able to animated the child widget of the floating action button to their correct position, using the Transform Widget, but when I try to press on the child widget, i.e. the widgets that come out on pressing the floating action button, they do not respond to the onPressed handler.
Tried many different thing like IgnorePointer, stacked rows and Columns, AnimatedBuilder,etc. but was unable to find the correct solution.
It was like sometimes used to get the UI correct then the gesture was not detected and if the gesture were detected the UI got distorted.
And I am somewhat new to flutter. Any help in sorting out this issue would be appreciated.
Here is my Code:
main.dart
import "package:flutter/material.dart";
import 'myhome.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primaryColor: Colors.blue,
),
title: "Custom Expandable FAB",
home: MyHome(),
);
}
}
myhome.dart
import 'package:flutter/material.dart';
import 'package:tester_project/customFAB.dart';
class MyHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
extendBody: true,
backgroundColor: Colors.white,
floatingActionButton: CustomFab(),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: BottomAppBar(
color: Colors.blue,
shape: CircularNotchedRectangle(),
child: Container(
height: 55,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: Icon(Icons.search),
onPressed: () {},
),
IconButton(
icon: Icon(Icons.menu),
onPressed: () {},
),
],
),
),
),
appBar: AppBar(
title: Text("Custom FAB"),
),
body: Container(
alignment: Alignment.center,
child: Text("Click on Fab to expand"),
color: Colors.white,
),
);
}
}
CustomFab.dart
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
class CustomFab extends StatefulWidget {
#override
_CustomFabState createState() => _CustomFabState();
}
class _CustomFabState extends State<CustomFab>
with SingleTickerProviderStateMixin {
AnimationController _animationController;
Animation<double> _translateAnimation;
Animation<double> _rotationAnimation;
Animation<double> _iconRotation;
bool _isExpanded = false;
void animate() {
if (!_isExpanded) {
_animationController.forward();
} else {
_animationController.reverse();
}
_isExpanded = !_isExpanded;
}
Widget fab1() {
return Container(
height: 60,
width: 60,
child: FittedBox(
child: FloatingActionButton(
heroTag: "btn3",
backgroundColor: Color(0xffFFC852),
elevation: 0,
onPressed: () {
print("pressed");
},
),
),
);
}
Widget fab2() {
return Container(
height: 60,
width: 60,
child: FittedBox(
child: FloatingActionButton(
heroTag: "btn4",
child: Transform.rotate(
angle: _iconRotation.value,
child: Icon(Icons.home),
),
elevation: _isExpanded ? 5 : 0,
backgroundColor: Color(0xffE5E4F4),
onPressed: () {
print("Pressed");
},
),
),
);
}
Widget fab3() {
return Container(
height: 60,
width: 60,
child: FittedBox(
child: FloatingActionButton(
heroTag: "btn5",
child: Transform.rotate(
angle: _rotationAnimation.value,
child: Icon(Icons.add),
),
backgroundColor: Color(0xffFFC852),
onPressed: () async {
await Permission.contacts.request();
if (await Permission.contacts.status.isGranted) {
animate();
}
},
),
),
);
}
#override
void initState() {
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 400))
..addListener(() {
setState(() {});
});
_translateAnimation = Tween<double>(begin: 0, end: 80)
.chain(
CurveTween(
curve: _isExpanded ? Curves.fastOutSlowIn : Curves.bounceOut,
),
)
.animate(_animationController);
_iconRotation = Tween<double>(begin: 3.14 / 2, end: 0)
.chain(
CurveTween(curve: Curves.bounceInOut),
)
.animate(_animationController);
_rotationAnimation = Tween<double>(begin: 0, end: 3 * 3.14 / 4)
.chain(
CurveTween(
curve: Curves.bounceInOut,
),
)
.animate(_animationController);
super.initState();
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Stack(
clipBehavior: Clip.none,
children: [
Transform(
transform:
Matrix4.translationValues(0, -_translateAnimation.value, 0),
child: fab1(),
),
Transform(
transform:
Matrix4.translationValues(-_translateAnimation.value, 0, 0),
child: fab2(),
),
fab3(),
],
);
}
}
Floating action button before and after expansion
Look at this. https://api.flutter.dev/flutter/widgets/Transform-class.html
Unlike RotatedBox, which applies a rotation prior to layout, this object applies its transformation just prior to painting, which means the transformation is not taken into account when calculating how much space this widget's child (and thus this widget) consumes.
So, your fab1(),fab2(),fab3() have the same position.
Although you animate them, it just move at painting, their real position wont change.
Just give a color to your fabs, you will know what I mean.
Container(
color:Colors.green,
child: Transform(
transform:
Matrix4.translationValues(-_translateAnimation!.value, 0, 0),
child: fab2(),
),
),
Now you know why, and you need to know how.
So I hope you can look at this. https://api.flutter.dev/flutter/animation/animation-library.html
You can use Stack&&Positioned,and with Tween, caculate each button's position, or other way. I will leave you to explore.
Here's some code of CustomFab.dart
#override
Widget build(BuildContext context) {
return Container(
// color: Colors.green, // Give this area a background color, then you know why.
height: 150,// You need to give your "action area" a bigger size.
width: 150,// make these bigger have a problem, your bar will have a bigger circle.
// if you need this effect, you need to change all your fabs "Stack on the appbar"
// or just remove `shape: CircularNotchedRectangle(),` in myhome.dart
child: Stack(
clipBehavior: Clip.none,
children: [
Positioned(// These numbers just for example, you can make your own size or position.
left: 150 / 2 - 30,
bottom: _translateAnimation.value + 40,
child: fab1(),
),
Positioned(
left: 150 / 2 - 30 -_translateAnimation.value,
bottom: 40,
child: fab2(),
),
Positioned(
left: 150 / 2 - 30,
bottom: 40,
child: fab3(),
),
],
),
);
}
Try Speed Dial using this package https://pub.dev/packages/flutter_speed_dial
SpeedDial(
marginBottom: 25,
marginEnd: 25,
backgroundColor: Colors.blue,
activeBackgroundColor: Colors.white,
activeForegroundColor: Colors.blue,
animatedIcon: AnimatedIcons.menu_close,
children: [
SpeedDialChild(
child: Icon(
Icons.filter_alt,
),
label: 'ABC',
onTap: () {
}),
SpeedDialChild(
labelBackgroundColor: Colors.white,
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
child: Icon(Icons.add),
label: 'ABC',
onTap: () {
}),
],
),

flutter notify from top of the screen

I'm trying to figure out how to notify user with alert that comes from top of the screen like normal push notification does.
How can I alert user from top of the screen.
AlertDialog is not customizable so I'm stuck with this. Is there any way to show something like alert or snack bar from top of the screen?
Flutter gives you the possiblity to create notifications with the help of the class Overlay. To animate these entering the screen from the top you can use the SlideTransition in combination with an AnimationController. Here is an example application I created:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: Home());
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton.icon(
icon: Icon(Icons.notifications_active),
label: Text('Notify!'),
onPressed: () {
Navigator.of(context)
.overlay
.insert(OverlayEntry(builder: (BuildContext context) {
return FunkyNotification();
}));
},
),
),
);
}
}
class FunkyNotification extends StatefulWidget {
#override
State<StatefulWidget> createState() => FunkyNotificationState();
}
class FunkyNotificationState extends State<FunkyNotification>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<Offset> position;
#override
void initState() {
super.initState();
controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 750));
position = Tween<Offset>(begin: Offset(0.0, -4.0), end: Offset.zero)
.animate(
CurvedAnimation(parent: controller, curve: Curves.bounceInOut));
controller.forward();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Material(
color: Colors.transparent,
child: Align(
alignment: Alignment.topCenter,
child: Padding(
padding: EdgeInsets.only(top: 32.0),
child: SlideTransition(
position: position,
child: Container(
decoration: ShapeDecoration(
color: Colors.deepPurple,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0))),
child: Padding(
padding: EdgeInsets.all(10.0),
child: Text(
'Notification!',
style: TextStyle(
color: Colors.white, fontWeight: FontWeight.bold),
),
),
),
),
),
),
),
);
}
}
Here you can dismiss notifications using the swipe up or down. This is the perfect notification for promotion in-app.
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> with TickerProviderStateMixin {
bool _fromTop = true;
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.fireplace_outlined),
onPressed: () {
showGeneralDialog(
barrierLabel: "Label",
barrierDismissible: true,
barrierColor: Colors.transparent,
transitionDuration: Duration(milliseconds: 700),
context: context,
pageBuilder: (context, anim1, anim2) {
return GestureDetector(
onVerticalDragUpdate: (dragUpdateDetails) {
Navigator.of(context).pop();
},
child: Column(
children: [
SizedBox(height: 40),
Card(
margin:
EdgeInsets.symmetric(vertical: 20, horizontal: 10),
child: Container(
height: 100,
child: Image.asset('lib/model/promo.png',
fit: BoxFit.fill),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(40),
),
),
),
],
),
);
},
transitionBuilder: (context, anim1, anim2, child) {
return SlideTransition(
position: anim1.drive(Tween(
begin: Offset(0, _fromTop ? -1 : 1), end: Offset(0, 0))
.chain(CurveTween(curve: Sprung()))),
child: child,
);
},
);
},
),
);
}
}
class Sprung extends Curve {
factory Sprung([double damping = 20]) => Sprung.custom(damping: damping);
Sprung.custom({
double damping = 20,
double stiffness = 180,
double mass = 1.0,
double velocity = 0.0,
}) : this._sim = SpringSimulation(
SpringDescription(
damping: damping,
mass: mass,
stiffness: stiffness,
),
0.0,
1.0,
velocity,
);
final SpringSimulation _sim;
#override
double transform(double t) => _sim.x(t) + t * (1 - _sim.x(1.0));
}