Tween animation flutter - flutter

I want to play an animation when a button is clicked. On the first press, the widget rotates 180 degrees, on the second press, another 180 degrees (that is, it returns to its original position). How can I do this?
Simulated gesture detector button
Expanded(
child: GestureDetector(
onTap: () => setState(() {
if (tapValue == 0) {
tapValue++;
animController.forward();
beginValue = 0.0;
endValue = 0.5;
} else {
tapValue--;
animController.forward();
}
}),
child: Container(
child: Image.asset('assets/images/enableAsset.png'),
),
),
),
The widget I want to rotate
child: CustomPaint (
painter: SmileyPainter(),
child: RotationTransition(
turns: Tween(begin: beginValue, end: endValue,).animate(animController),
child: CustomPaint (
painter: Smile(),
),
),
)
animation controller
#override
void initState() {
animController = AnimationController(
duration: const Duration(milliseconds: 400),
vsync: this,
);
super.initState();
}

If what you want to achieve is only to rotate a widget, I would recommend avoiding a controller. Not only will this simplify your code but it will also save you the chore of disposing it.
I have come to realize that pretty much any controller can be avoided using the TweenAnimationBuilder widget.
Here is an example of how to to make it work for your case:
Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
_rotationAngle += pi;
print(_rotationAngle);
setState(() {
});
},
),
body: Center(
child: TweenAnimationBuilder(
duration: Duration(milliseconds: 300),
tween: Tween<double>(begin: 0, end: _rotationAngle),
builder: (BuildContext context, double value, Widget child) {
return Transform.rotate(
angle: value,
child: child,
);
},
child: Container(
height: 500,
width: 50,
color: Colors.redAccent,
),
),
),
);

just replace
else {tapValue--;
animController.forward();
}
to
else {tapValue--;
animController.reverce();
}

Related

Revealing a widget from beneath another thourgh animation in Flutter

I'm trying to make an animation with Flutter Hooks where I have a symbol going up and back down from behind a container when I tap it, but I can't get the symbol to stay behind the container. It instead goes way down. What am I doing wrong?
#override
Widget build(BuildContext context) {
AnimationController controller = useAnimationController(
duration: const Duration(seconds: 3), initialValue: 0);
Animation<double> animation = Tween<double>(begin: 0, end: 1)
.animate(CurvedAnimation(parent: controller, curve: Curves.easeOut));
return Stack(alignment: Alignment.bottomCenter, children: [
AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Transform.translate(
offset: Offset(0, cos(animation.value) * 500),
child: child,
);
},
child: const CircleAvatar(
backgroundColor: Colors.red,
radius: 30,
)),
GestureDetector(
child: Container(
color: Colors.brown,
width: 300,
height: 100,
),
onTap: () {
print("TAP : ${(controller.status != AnimationStatus.completed)}");
if (controller.status != AnimationStatus.completed) {
controller.forward().whenComplete(() => controller.reverse());
}
},
),
]);
}

How to hide actions in floatingButton

I am trying to have a FloatingButton with multiple options and each option triggers an action. I achieve that so far, and also an animated transition when pressed. What I need is to achieve that when the Button is not pressed, only one icon is showing.
This is the code so far:
class _FloatingButtonMultipleOptionState extends State<FloatingButtonMultipleOption> with SingleTickerProviderStateMixin {
bool isOpened = false;
AnimationController _animationController;
Animation<Color> _buttonColor;
Animation<double> _animateIcon;
Animation<double> _translateButton;
Curve _curve = Curves.easeOut;
double _fabHeight = 56.0;
#override
initState() {
_animationController = AnimationController(vsync: this, duration: Duration(milliseconds: 500))
..addListener(() {
setState(() {});
});
_animateIcon = Tween<double>(begin: 0.0, end: 1.0).animate(_animationController);
_buttonColor = ColorTween(
begin: TheBaseColors.lightBlue,
end: TheBaseColors.lightRed,
).animate(CurvedAnimation(
parent: _animationController,
curve: Interval(
0.00,
1.00,
curve: Curves.linear,
),
));
_translateButton = Tween<double>(
begin: _fabHeight,
end: -14.0,
).animate(CurvedAnimation(
parent: _animationController,
curve: Interval(
0.0,
0.75,
curve: _curve,
),
));
super.initState();
}
#override
dispose() {
_animationController.dispose();
super.dispose();
}
animate() {
if (!isOpened) {
_animationController.forward();
} else {
_animationController.reverse();
}
isOpened = !isOpened;
}
Widget post() {
return FloatingActionButton.extended(
heroTag: null,
onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (context) => CreatePost()));
},
tooltip: 'Post',
label: Row(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text('Send a Post'),
),
Icon(
Icons.message,
),
],
),
);
}
Widget activity() {
return FloatingActionButton.extended(
heroTag: null,
onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (context) => CreateActivity()));
},
tooltip: 'Activiy',
label: Row(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text('Create activity'),
),
Icon(Icons.event),
],
),
);
}
Widget close() {
return FloatingActionButton(
backgroundColor: _buttonColor.value,
onPressed: animate,
tooltip: 'Close',
child: AnimatedIcon(
icon: AnimatedIcons.menu_close,
progress: _animateIcon,
),
);
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Transform(
transform: Matrix4.translationValues(
0.0,
_translateButton.value * 3.0,
0.0,
),
child: post(),
),
Transform(
transform: Matrix4.translationValues(
0.0,
_translateButton.value * 2.0,
0.0,
),
child: activity(),
),
close(),
],
);
}
}
In the photo attached you can see how when the button is not pressed, the user can see the option 'Create activity' and this should only be seen when the button is pressed.
You could set a boolean "isButtonPressed" that changes state every time you press the FloatingActionButton, and then wrap the other buttons into the Visibility widget like this
Visibility(
visible: isButtonPressed,
child: activity(),
)
This will make your floatingActionButton "Create activity" visible when the button is pressed, and hide it when the button is pressed a second time.

Animations with curve property in flutter

In my app, I have a container that I want to start rotating with a slow-curve on a click, then keep rotation, and then the next click will make it stop with a slow-curve.
How do I make a curve-animation in flutter?
Somthing like this:
https://miro.medium.com/max/960/1*ZLekwO4QthfAWlBgM-9vpA.gif
Make an animation controller and an animation.
AnimationController _animController;
Animation<double> _animation;
#override
void initState() {
super.initState();
_animController = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
_animation =
Tween<double>(begin: 0, end: 2 * math.pi).animate(_animController)
..addListener(() {
setState(() {});
});
}
#override
void dispose() {
_animController.dispose();
super.dispose();
}
Define a boolean variable. This variable indicates whether the object is animating or not.
var _animating = false;
End the animation on Stop and repeat the animation on Start.
Scaffold(
backgroundColor: Colors.blueGrey,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Transform.rotate(
angle: _animation.value,
child: Container(
color: Colors.green,
height: 80,
width: 80,
padding: EdgeInsets.all(30),
),
),
Padding(
padding: const EdgeInsets.only(top: 20.0),
child: RaisedButton(
color: Colors.white,
child: Text(_animating ? "Stop" : "Start"),
onPressed: () {
if (_animating) {
_animController.animateTo(1,
duration: Duration(seconds: 3), curve: Curves.ease);
} else {
_animController.repeat();
}
setState(() => _animating = !_animating);
},
),
),
],
),
),
)
Result:

Possible to copy iOS App Store transition using Flutter?

Is it possible to copy the transition effect of iOS App Store using Flutter?
I tried using Hero Animation by placing two tags into the root layout of both widgets, but animation looks janky or not what I expected. But good thing about this is I am able to do iOS swipe back as I'm using MaterialPageRoute.
Source
Hero(
tag: 'heroTag_destinationScreen',
transitionOnUserGestures: true,
flightShuttleBuilder: (BuildContext flightContext,
Animation<double> animation,
HeroFlightDirection flightDirection,
BuildContext fromHeroContext,
BuildContext toHeroContext,) {
final Hero toHero = toHeroContext.widget;
return ScaleTransition(
scale: animation,
child: toHero,
);
},
child: GestureDetector(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
return DestinationScreen()
},
),
);
},
child: Card(
...someCardContent
),
),
)
Destination Screen
#override
Widget build(BuildContext context) {
return Hero(
tag: 'heroTag_destinationScreen',
child: Scaffold(
appBar: ...someAppBar
body: ...someMainBodyContent
),
)
}
Then I have been looking around and there is a package created by Flutter team which can simulate this effect using container transform. I implemented it, works awesome but then I'm not able to do iOS swipe from left to go back and shrink the layout to card view.
https://pub.dev/packages/animations
here is my solution.
https://imgur.com/2WYn6TX
(Sorry for my reputation, I can't post a image.)
I customize hero transition to remake App store transition as much as possible.
child: Hero(
tag: widget.product.id,
child: Image.asset(widget.product.image, fit: BoxFit.cover),
flightShuttleBuilder:
(flightContext, animation, direction, fromcontext, toContext) {
final Hero toHero = toContext.widget;
// Change push and pop animation.
return direction == HeroFlightDirection.push
? ScaleTransition(
scale: animation.drive(
Tween<double>(
begin: 0.75,
end: 1.02,
).chain(
CurveTween(
curve: Interval(0.4, 1.0, curve: Curves.easeInOut)),
),
),
child: toHero.child,
)
: SizeTransition(
sizeFactor: animation,
child: toHero.child,
);
},
),
Next, I use ScaleTransition and onVerticalDragUpdate to control pop animation.
https://imgur.com/a/xEMYOPr
double _initPoint = 0;
double _pointerDistance = 0;
GestureDetector(
onVerticalDragDown: (detail) {
_initPoint = detail.globalPosition.dy;
},
onVerticalDragUpdate: (detail) {
_pointerDistance = detail.globalPosition.dy - _initPoint;
if (_pointerDistance >= 0 && _pointerDistance < 200) {
// scroll up
double _scaleValue = double.parse((_pointerDistance / 100).toStringAsFixed(2));
if (_pointerDistance < 100) {
_closeController.animateTo(_scaleValue,
duration: Duration(milliseconds: 300),
curve: Curves.linear);
}
} else if (_pointerDistance >= 260) {
if (_pop) {
_pop = false;
_closeController.fling(velocity: 1).then((_) {
setState(() {
_heightController.reverse();
});
Timer(Duration(milliseconds: 100), () {
Navigator.of(context).pop();
});
});
}
} else {
// scroll down
}
},
onVerticalDragEnd: (detail) {
if (_pointerDistance >= 550) {
if (_pop) {
_closeController.fling(velocity: 1).then((_) {
setState(() {
_heightController.reverse();
});
Timer(Duration(milliseconds: 100), () {
Navigator.of(context).pop();
});
});
}
} else {
_closeController.fling(velocity: -1);
}
},
child: Hero(
tag: _product.id,
child: Image.asset(
_product.image,
fit: BoxFit.cover,
height: 300,
),
),
),
If use Hero as a animation, you need to customize the text section transition.
Here: https://imgur.com/a/gyD6tiZ
In my case, I control text section transition by Sizetransition.
// horizontal way and vertical way.
SizeTransition(
axis: Axis.horizontal,
sizeFactor: Tween<double>(begin: 0.5, end: 1).animate(
CurvedAnimation(
curve: Curves.easeInOut, parent: _widthController),
),
child: SizeTransition(
sizeFactor: Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(
curve: Curves.easeInOut, parent: _heightController),
),
child: Container(
padding: EdgeInsets.only(
left: 20, right: 20, top: 50, bottom: 30),
width: double.infinity,
color: Colors.white,
constraints: BoxConstraints(
minHeight: 650,
),
child: Column(
// title and text
children: <Widget>[
Text('Title', style: TextStyle(fontSize: 18)),
SizedBox(height: 30),
Text(_text,
style: TextStyle(
fontSize: 15,
)),
],
),
),
),
),
Although it isn't the same as App Store, i hope it is helpful for you.
Source code: https://github.com/HelloJunWei/app_store_transition
If you have any suggestion, feel free to feedback or create a pull request. :)

AnimatedContainer Rotation gives unwanted rotation

I have been struggling with Flutter with something that in my head seems to simple. I want a container that spins when pressing a button and this should be animated. I have a container in the center of my screen. I have 2 buttons, 1 with "+" and with a "-". When I press the "+" I want the container to rotate 180 degrees in a clockwise rotation, If I press the "+" again I want it to perform another clockwise rotation of 180 degrees. When pressing "-" I want it to spin counter clockwise for 180 degrees.
Currently I have it build so that it will rotate container however the axis is on the top left point of the container instead of the center. I have tried to tackle this but nothing seems to change this behaviour and I found this issue but it's since been closed
https://github.com/flutter/flutter/issues/27419
It's mind boggling to me that I cannot perform such a simple operation and was wondering if someone knows where I'm going wrong.
Some code:
import 'package:flutter/material.dart';
import 'dart:math';
void main() => runApp(Spinner());
class Spinner extends StatefulWidget {
#override
_SpinnerState createState() => _SpinnerState();
}
class _SpinnerState extends State<Spinner> {
double _angle = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: AnimatedContainer(
alignment: FractionalOffset.center,
height: 200,
width: 200,
transform: Matrix4.rotationZ(_angle),
decoration: BoxDecoration(
color: Colors.blue
),
duration: Duration(seconds: 2),
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
FloatingActionButton(onPressed: () {
setState(() {
_angle += 180 * pi / 180;
});
},
child: const Icon(Icons.add),
),
Container(
height: 20,
),
FloatingActionButton(onPressed: () {
setState(() {
_angle -= 180 * pi / 180;
});
},
child: const Icon(Icons.remove),
)
],
)
)
);
}
}
Edit:
I did find this post however when using it the container instantly snaps to the new position and I want this to be animted.
How can I rotate a Container widget in 2D around a specified anchor point?
I found a solution to tackle this problem, instead of using the animatedContained I decided to use an Animation Controller.
my SpinnerState class now looks like this:
class _SpinnerState extends State<RadialMenu> with SingleTickerProviderStateMixin {
AnimationController controller;
#override
void initState() {
super.initState();
controller = AnimationController(duration: Duration(milliseconds: 900), vsync: this);
}
#override
Widget build(BuildContext context) {
return RadialAnimation(controller: controller);
}
}
I then created a stateless widget which contains the animations etc.
class SpinnerAnimation extends StatelessWidget {
SpinnerAnimation({Key key, this.controller})
: rotation = Tween<double>(
begin: 0.0,
end: 180.0,
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.0,
0.75,
curve: Curves.linear,
),
),
),
super(key: key);
final AnimationController controller;
final Animation<double> rotation;
build(context) {
return AnimatedBuilder(
animation: controller,
builder: (context, builder) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FloatingActionButton(
child: Icon(Icons.add),
onPressed: _open,
backgroundColor: Colors.green),
FloatingActionButton(
child: Icon(Icons.remove),
onPressed: _close,
backgroundColor: Colors.red),
Transform.rotate(
angle: radians(rotation.value),
child: Column(
children: [
Container(
width: 200,
height: 200,
decoration: new BoxDecoration(
image: DecorationImage(
image: AssetImage('images/CRLogo.png'))),
)
],
))
],
);
});
}
_open() {
controller.forward(from: 0);
}
_close() {
controller.reverse(from: 1);
}
}
This has at least solved this problem for me and I hope it helps someone else in the future.