How to mimic a natural ball bounce animation in Flutter? - flutter

I'm new to flutter animations and want to animate a natural ball bounce in Flutter. I have an image inside a SizedBox within a Transform.translate, when clicked it begins bouncing using the offset, but whenever it gets to the top it reflects backwards (it feels like it collides with an imaginary wall above). I only want it to reflect only when it's at the bottom and when it gets to the top it starts coming back down, as natural as possible.
class MyApp extends StatelessWidget {
const MyApp({super.key});
#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({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _height;
late Animation _curve;
late Animation<Offset> _offset;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this,
);
_curve = CurvedAnimation(parent: _controller, curve: Curves.linear, reverseCurve: Curves.linear);
_offset = TweenSequence(<TweenSequenceItem<Offset>>[
TweenSequenceItem<Offset>(
tween: Tween<Offset>(begin: const Offset(0, 0), end: const Offset(0, -40)), weight: 50),
]).animate(_curve as Animation<double>);
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reverse();
} else if (status == AnimationStatus.dismissed) {
_controller.forward();
}
});
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: AnimatedBuilder(
animation: _controller,
builder: (_, __) {
return InkWell(
hoverColor: Colors.transparent,
highlightColor: Colors.transparent,
focusColor: Colors.transparent,
onTap: () => _controller.forward(),
child: Transform.translate(
offset: _offset.value,
child: SizedBox(
// color: Colors.black,
height: 40,
width: 40,
child: Image.asset('lib/ball.png')),
),
);
}),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
Thanks in advance!

Related

How to animate Align without using AnimatedAlign and control the animation

I am trying to achieve a pretty simple animation in Flutter but I am stuck.
The current animation looks like this (iPhone Screen recording): https://drive.google.com/file/d/10pzUadQrSr85eLLpyh7v3lt-x8XjFNec/view?usp=sharing
I am currently doing this with Positioned but I can not do it precisely as I want. Is there any chance that I can do this animation with Align Widget.
I have tried using AnimatedAlign and Tweens but none of them allow me to modify the Images position by scrolling up or down on the bottom sheet.
The Code:
late AnimationController bottomSheetController;
double bsOffset = 1;
#override
void initState() {
super.initState();
bottomSheetController = BottomSheet.createAnimationController(this);
bottomSheetController.duration = Duration(milliseconds: 200);
bottomSheetController.addListener(() {
double size = (bottomSheetController.value.toDouble() - 1) * -1;
setState(() {
if (size != 0) {
bsOffset = size;
}
});
});
}
.
.
.
void _handleFABPressed() {
showModalBottomSheet(
backgroundColor: Colors.transparent,
barrierColor: Colors.transparent,
transitionAnimationController: bottomSheetController,
context: context,
builder: (context) {
return Popover(
child: Container(
height: 400,
// color: Colors.lightBlue,
),
);
},
);
}
.
.
.
child: Stack(
alignment: Alignment.topCenter,
children: [
Positioned(
bottom: 50 - (50 * bsOffset),
left: ((200 / 2) - 20) - (((200 / 2) - 20) * (bsOffset - 1) * -1),
child: Image.asset(
'assets/images/8.png',
width: 200,
),
),
],
),
Thank You!
If I were to move the two widgets, both Align and the bottomsheet, I would do it like this:
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
late AnimationController _animationController;
late Animation<Offset> _animationOffset;
var _alignment = Alignment.bottomCenter;
void _show() {
showModalBottomSheet(
backgroundColor: Colors.transparent,
barrierColor: Colors.transparent,
transitionAnimationController: _animationController,
context: context,
builder: (context) {
return Container(
height: 400,
color: Colors.red,
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Demo Home Page'),
),
body: Stack(
children: [
Align(
alignment: _alignment,
child: const SizedBox(
width: 100, height: 100, child: Card(color: Colors.red)))
],
),
floatingActionButton:
FloatingActionButton(onPressed: _show, child: const Icon(Icons.add)),
);
}
void _onListenerAnimation() {
setState(() {
_alignment =
Alignment(_animationOffset.value.dx, _animationOffset.value.dy);
});
}
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 200))
..addListener(_onListenerAnimation);
_animationOffset = Tween<Offset>(
begin: const Offset(0, 1),
end: const Offset(-1, -0.6),
).animate(_animationController);
}
#override
void dispose() {
super.dispose();
_animationController
..removeListener(_onListenerAnimation)
..dispose();
}
}

Getting This Error When Trying To Animate An ElevatedButton

I am currently trying to animate an elevatedButton, nothing too fancy just trying to animate the elevated property. So I’ve scaffolded a new flutter starter app in VS code and added in the relevant code for the animations and animation controller, however I keep getting this error : type ‘double’ is not a subtype of type ‘MaterialStateProperty<double?>?’ I don’t understand why, here’s my code
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
AnimationController animationController;
Animation animation;
#override void initState() {
super.initState();
animationController = AnimationController(
vsync: this,
duration: Duration(seconds: 5),
);
animation = Tween(begin: 50.0, end: 150.0).animate(
CurvedAnimation(
parent: animationController,
curve: Interval(0.0, 1.0, curve: Curves.easeInOut),
),
);
}
#override void dispose() {
animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: (){
animationController.forward();
},
child: Text("Button Text"),
style: ButtonStyle(
elevation: animation.value,
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: (){},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
try with this with MaterialStateProperty.all<double>(animation.value), in button style
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
AnimationController animationController;
Animation animation;
#override void initState() {
super.initState();
animationController = AnimationController(
vsync: this,
duration: Duration(seconds: 5),
);
animation = Tween(begin: 50.0, end: 150.0).animate(
CurvedAnimation(
parent: animationController,
curve: Interval(0.0, 1.0, curve: Curves.easeInOut),
),
);
}
#override void dispose() {
animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: (){
animationController.forward();
},
child: Text("Button Text"),
style: ButtonStyle(
// here you will change the `MaterialStateProperty` value
elevation: MaterialStateProperty.all<double>(animation.value),
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: (){},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
According to ButtonStyle Documentation:
Many of the ButtonStyle properties are MaterialStateProperty objects which resolve to different values depending on the button's state. For example the Color properties are defined with MaterialStateProperty and can resolve to different colors depending on if the button is pressed, hovered, focused, disabled, etc.
So what you need to do is Pass the animation value as a MaterialStateProperty.
In your case it would look like this:
style: ButtonStyle(
elevation: MaterialStateProperty.all(animation.value),
),
Check out the MaterialStateProperty Class documentation to get more info.

How can I animate a Dialog in Flutter like this

I'm trying to animate an alert dialog in flutter so that when it pop ups it shows an animation like this below.
How can I achieve following look and behaviour from Pokemon Go in an alertDialog?
I would really like to have this animation in my app.
Thanks for your Answers!
Try this, modify any variable to meet your requirement:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
AnimationController _controller;
Animation<Offset> _animation;
double _width = 20;
double _height = 200;
Color _color = Colors.transparent;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
);
_animation = Tween<Offset>(
begin: const Offset(0.0, 1.0),
end: const Offset(0.0, -2.0),
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeInCubic,
));
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
actions: [
IconButton(
icon: Icon(Icons.send),
onPressed: () {
setState(() {
_color = Colors.white;
});
_controller.forward().then((_) {
_width = 200;
setState(() {});
});
},
),
],
),
body: Stack(
children: [
Align(
alignment: Alignment.bottomCenter,
child: SlideTransition(
position: _animation,
child: AnimatedContainer(
width: _width,
height: _height,
decoration: BoxDecoration(
color: _color,
borderRadius: BorderRadius.circular(10),
),
duration: Duration(seconds: 1),
curve: Curves.fastOutSlowIn,
),
),
),
],
),
);
}
}

Animate size increase in increments, animation is only smooth on first increment, flutter

I am having some problems with an animation I'm trying to do in Flutter using Dart. I am trying to incrementally increase the height of a box when the user clicks a button. Below is a very simple example of what I am trying to achieve. My problem is that the smooth animation only works for the first click but after that there is no animation, the box only gets larger.
Is there a way to fix this problem?
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Size Animation'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
AnimationController _animationController;
double _myHeight = 310;
double _counter = 30;
double _target = 10;
#override
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: Duration(seconds: 1));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Container(
alignment: Alignment.center,
child: Column(
children: <Widget>[
FlatButton(
child: Text('Reset me'),
color: Colors.red,
onPressed: () {
_target = 10;
_animationController.reset();
},
),
FlatButton(
child: Text('Click me'),
color: Colors.blue,
onPressed: () {
if (_target < _myHeight) {
setState(() {
_target += _counter;
});
}
_animationController.forward();
},
),
AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Container(
color: Colors.green,
width: 80,
height: _animationController.value * _target,
);
}),
],
),
),
),
);
}
}
You can easily reach this just by having a simple AnimatedContainer, that will automatically animate itself whenever one of it's proprierties changes, so your widget would look like this:
AnimatedContainer(
duration: const Duration(seconds: 1),
color: Colors.green,
width: 80,
height: _target
)
That way when _target value changes with setState value, your widget would animate the height.
Your current animation is working only the first time, because actually your are just running the animationController only once, then you recall the forward() again and again, but your animation has already been animated so, there's no forward, the value is going from 0 to 1 only the first time, then everytime you call the forward() method the animation is already at value 1, so you see no animation. One way you could do that if you really wanna keep this animation with an AnimatedBuilder you should give it a Tween instead of your animationController

How to use 'CupertinoFullscreenDialogTransition'?

I didn't find any example for constructor CupertinoFullscreenDialogTransition
https://api.flutter.dev/flutter/cupertino/CupertinoFullscreenDialogTransition-class.html
I tried to understand below code but I didn't get it.
CupertinoFullscreenDialogTransition({
Key key,
#required Animation<double> animation,
#required this.child,
}) : _positionAnimation = CurvedAnimation(
parent: animation,
curve: Curves.linearToEaseOut,
// The curve must be flipped so that the reverse animation doesn't play
// an ease-in curve, which iOS does not use.
reverseCurve: Curves.linearToEaseOut.flipped,
).drive(_kBottomUpTween),
super(key: key);
Here's a more complete example
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
static const String _title = 'AppBar tutorial';
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
brightness: Brightness.light,
primaryColor: Colors.blue[900],
appBarTheme: AppBarTheme(iconTheme: IconThemeData(color: Colors.white)),
),
title: _title,
home: CupertinoFullscreenDialogTransitionPage(),
);
}
}
//First Page
class CupertinoFullscreenDialogTransitionPage extends StatefulWidget {
#override
_CupertinoFullscreenDialogTransitionState createState() =>
_CupertinoFullscreenDialogTransitionState();
}
class _CupertinoFullscreenDialogTransitionState
extends State<CupertinoFullscreenDialogTransitionPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(60),
child: AppBar(
title: Text("Cupertino Screen Transition"),
centerTitle: true,
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CupertinoButton.filled(
child: Text("Next Page Cupertino Transition"),
onPressed: () => Navigator.of(context).push(
PageRouteBuilder(
opaque: false,
pageBuilder: (context, _, __) {
return FullDialogPage();
},
),
),
),
],
)),
);
}
}
//Second Page
class FullDialogPage extends StatefulWidget {
#override
_FullDialogPageState createState() => _FullDialogPageState();
}
class _FullDialogPageState extends State<FullDialogPage>
with TickerProviderStateMixin {
AnimationController _primary, _secondary;
Animation<double> _animationPrimary, _animationSecondary;
#override
void initState() {
//Primaty
_primary = AnimationController(vsync: this, duration: Duration(seconds: 1));
_animationPrimary = Tween<double>(begin: 0, end: 1)
.animate(CurvedAnimation(parent: _primary, curve: Curves.easeOut));
//Secondary
_secondary =
AnimationController(vsync: this, duration: Duration(seconds: 1));
_animationSecondary = Tween<double>(begin: 0, end: 1)
.animate(CurvedAnimation(parent: _secondary, curve: Curves.easeOut));
_primary.forward();
super.initState();
}
#override
void dispose() {
_primary.dispose();
_secondary.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return CupertinoFullscreenDialogTransition(
primaryRouteAnimation: _animationPrimary,
secondaryRouteAnimation: _animationSecondary,
linearTransition: false,
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.indigo[900],
title: Text("Testing"),
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
_primary.reverse();
Future.delayed(Duration(seconds: 1), () {
Navigator.of(context).pop();
});
},
),
),
),
);
}
}
I've made this simple example, I hope it helps you understand how to implement the CupertinoFullscreenDialogTransition Widget.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.orange,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin{
AnimationController _animationController;
#override
void initState() {
_animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Stackoverflow playground'),
),
body: Container(
child: Column(
children: <Widget>[
CupertinoFullscreenDialogTransition(
primaryRouteAnimation: _animationController,
secondaryRouteAnimation: _animationController,
linearTransition: false,
child: Center(
child: Container(
color: Colors.blueGrey,
width: 300,
height: 300,
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
RaisedButton(
onPressed: () => _animationController.forward(),
child: Text('Forward'),
),
RaisedButton(
onPressed: () => _animationController.reverse(),
child: Text('Reverse'),
),
],
),
],
),
)
);
}
}