Animations with curve property in flutter - 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:

Related

Add custom dropdown on stack flutter

I'm trying to add a custom dropdown menu whose items are just links to other pages
I tried using DropdownButton
But I failed to make its elements as a link and it requires a value, and I do not have a value to pass to it
thank you
You can use OverlayEntry for this case. Below is a simple working example of a dropdown using OverlayEntry:
class TestDropdownWidget extends StatefulWidget {
TestDropdownWidget({Key? key}) : super(key: key);
#override
_TestDropdownWidgetState createState() => _TestDropdownWidgetState();
}
class _TestDropdownWidgetState extends State<TestDropdownWidget>
with TickerProviderStateMixin {
final LayerLink _layerLink = LayerLink();
late OverlayEntry _overlayEntry;
bool _isOpen = false;
//Controller Animation
late AnimationController _animationController;
late Animation<double> _expandAnimation;
#override
void dispose() {
super.dispose();
_animationController.dispose();
}
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 200),
);
_expandAnimation = CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
);
}
#override
Widget build(BuildContext context) {
return CompositedTransformTarget(
link: _layerLink,
child: InkWell(
onTap: _toggleDropdown,
child: Text('Click Me'), //Define your child here
),
);
}
OverlayEntry _createOverlayEntry() {
return OverlayEntry(
builder: (context) => GestureDetector(
onTap: () => _toggleDropdown(close: true),
behavior: HitTestBehavior.translucent,
// full screen container to register taps anywhere and close drop down
child: SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Stack(
children: [
Positioned(
left: 100,
top: 100.0,
width: 250,
child: CompositedTransformFollower(
//use offset to control where your dropdown appears
offset: Offset(0, 20),
link: _layerLink,
showWhenUnlinked: false,
child: Material(
elevation: 2,
borderRadius: BorderRadius.circular(6),
borderOnForeground: true,
color: Colors.white,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
border: Border.all(color: Colors.grey),
),
child: SizeTransition(
axisAlignment: 1,
sizeFactor: _expandAnimation,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
//These are the options that appear in the dropdown
Text('Option 1'),
Text('Option 2'),
Text('Option 3'),
Text('Option 4'),
Text('Option 5'),
],
),
),
),
),
),
),
],
),
),
),
);
}
void _toggleDropdown({
bool close = false,
}) async {
if (_isOpen || close) {
_animationController.reverse().then((value) {
_overlayEntry.remove();
if (mounted) {
setState(() {
_isOpen = false;
});
}
});
} else {
_overlayEntry = _createOverlayEntry();
Overlay.of(context)!.insert(_overlayEntry);
setState(() => _isOpen = true);
_animationController.forward();
}
}
}
Here's a gif to show the ui:

Animating the front card to the back in a stack view of cards in flutter

I have got a stack of cards and scroll wheel. I am trying to animate the front card, move it to the right, then bring it back in a way it goes to the back of the cards.
I have used a future function to specify witch part of code should be done first. But what I get is; it changes the index of the card first the animation takes place. Here is my code:
class AnimationsPractice extends StatefulWidget {
static const String id = 'test_screen';
#override
_AnimationsPracticeState createState() => _AnimationsPracticeState();
}
class _AnimationsPracticeState extends State<AnimationsPractice>
with SingleTickerProviderStateMixin {
FixedExtentScrollController _scrollController =
FixedExtentScrollController(initialItem: 0);
AnimationController _controller;
Animation<Offset> _offsetAnimation;
int selected;
List<Widget> sampleCard;
Animation<Offset> _offsetAnimation2;
bool halfWayAnimation;
#override
void initState() {
super.initState();
_controller =
AnimationController(duration: const Duration(seconds: 1), vsync: this)
..repeat();
_offsetAnimation = Tween<Offset>(
begin: Offset.zero,
end: const Offset(1.5, 0.0),
).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(
0.0,
0.5,
curve: Curves.elasticIn,
),
),
);
_offsetAnimation2 = Tween<Offset>(
begin: const Offset(1.5, 0.0),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(
0.5,
1.0,
curve: Curves.elasticIn,
),
),
);
halfWayAnimation = false;
_controller.stop();
sampleCard = [
Container(
height: 60,
width: 40,
color: Colors.red,
),
Transform.rotate(
angle: 10 * (pi / 180),
child: Container(
height: 60,
width: 40,
color: Colors.blueGrey,
)),
SlideTransition(
position: halfWayAnimation ? _offsetAnimation2 : _offsetAnimation,
child: Container(
height: 60,
width: 40,
color: Colors.yellow,
),
),
];
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
Future<void> _playAnimation() async {
try {
await _controller.forward().orCancel;
await siftingIndex();
await _controller.reverse().orCancel;
} on TickerCanceled {
// the animation got canceled, probably because it was disposed of
}
}
Future<void> siftingIndex() {
return Future.delayed(const Duration(microseconds: 200), () {
sampleCard.insert(0, sampleCard[sampleCard.length - 1]);
sampleCard.removeLast();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
preferredSize: const Size.fromHeight(180.0),
child: SafeArea(
child: TextButton(
child: Text('back to login'),
onPressed: () {
Navigator.pushNamed(context, LoginScreen.id);
},
),
),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Stack(children: sampleCard),
CustomScrollWheel(
onItemChange: (x) {
setState(() {
_playAnimation();
halfWayAnimation = true;
});
},
scrollController: _scrollController,
),
],
),
);
}
}
enter image description here

Tween animation 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();
}

Animating a Card's height

I have a simple Card with an "Expand" button that toggles visibility of an _expandedCardBody method that returns a Column.
bool expandedCard = false;
Widget _expandedCardBody() {
return Column(
children: <Widget>[
Row(...),
Row(...),
Row(...),
]);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(10),
child: ListView(
children: <Widget>[
Text("Setup my budget", style: kTitleStyle),
Card(
color: Colors.white,
child: Column(
children: <Widget>[
ListTile(
leading: SvgPicture.asset(
"assets/images/icon-eating-out.svg",
width: 65),
title: Text('Eating out', style: kNormalStyle),
subtitle:
Text('AED 1,245 this month', style: kSmallStyle),
trailing: SizedBox(
width: 75,
child: TextFormField(
keyboardType: TextInputType.number,
decoration: InputDecoration(hintText: "AED.."),
style: kNormalStyle,
),
),
),
expandedCard ? _expandedCardBody() : SizedBox(),
Divider(),
Container(
alignment: Alignment.topLeft,
child: FlatButton(
child: Text(expandedCard ? 'COLLAPSE' : 'EXPAND'),
onPressed: () {
setState(() {
expandedCard = !expandedCard;
});
},
),
),
],
),
)
],
)));
}
It works, but this is what it looks like:
Instead of simply showing/hiding _expandedCardBody, I'd like to animate it's height.
I've tried using AnimatedContainer like so, but it requires knowing the height of the _expandedCardBody (which I do not).
AnimatedContainer(
child: _expandedCardBody(),
height: expandedCard ? 200 : 0, // 🤔 don't know what the height is.
duration: Duration(milliseconds: 250),
),
How can I animate the height of the Card body?
Use SingleTickerProvider with your state class
for eg like this :
class YourClass extends State<YourClass>
with SingleTickerProviderStateMixin {
AnimationController _animationController;
Animation<double> animation;
var isDetailOpened = false;
#override
void initState() {
_animationController =
AnimationController(duration: Duration(milliseconds: 200), vsync: this);
_animationController.addListener(() {
setState(() {});
});
seeMoreEnabled = false;
animation =
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut);
super.initState();
}
#override
dispose() {
_animationController.dispose();
super.dispose();
}
then Replace your Text (Expand or collapse) with flatButton or CupertinoButton
after doing that onPress or onClick method
onPressed: () {
if (isDetailOpened) {
_animationController.reverse();
} else {
_animationController.forward();
}
isDetailOpened = !isDetailOpened;
},
after that Put your widgets in SizeTransition
SizeTransition(
axisAlignment: 1.0,
sizeFactor: animation,
(your other widgets)
This is an example for an ideal purpose (in simply way)

BoxConstraints has a negative minimum width

in this class as an widget which i use animation to show and hide, when i multiple show or hiding by click, sometimes i get this error:
BoxConstraints has a negative minimum width.The offending constraints were: BoxConstraints(w=-0.8, 0.0<=h<=Infinity; NOT NORMALIZED)
this part of code has problem
child: Row( //<--- problem cause on this line of code
children: <Widget>[
...
],
),
My class code:
class FollowingsStoryList extends StatefulWidget {
final List<Stories> _stories;
FollowingsStoryList({#required List<Stories> stories}) : _stories = stories;
#override
_FollowingsStoryListState createState() => _FollowingsStoryListState();
}
class _FollowingsStoryListState extends State<FollowingsStoryList> with TickerProviderStateMixin {
List<Stories> get stories => widget._stories;
bool _isExpanded = false;
AnimationController _barrierController;
#override
void initState() {
super.initState();
_barrierController = AnimationController(duration: const Duration(milliseconds: 400), vsync: this);
}
#override
Widget build(BuildContext context) {
return AnimatedContainer(
curve: _isExpanded ? Curves.elasticOut : Curves.elasticIn,
duration: Duration(milliseconds: 800),
child: Row(
children: <Widget>[
AnimatedContainer(
curve: _isExpanded ? Curves.elasticOut : Curves.elasticIn,
duration: Duration(milliseconds: 800),
width: _isExpanded ? 90 : 1,
color: Colors.white,
padding: EdgeInsets.only(bottom: 65.0),
child: Column(
children: <Widget>[
],
),
),
GestureDetector(
onTap: _toggleExpanded,
child: StoriesShapeBorder(
alignment: Alignment(1, 0.60),
icon: Icons.adjust,
color: Colors.white,
barWidth: 4,
),
),
],
),
);
}
_toggleExpanded() {
setState(() {
_isExpanded = !_isExpanded;
if (_isExpanded) {
_barrierController.forward();
} else {
_barrierController.reverse();
}
});
}
}
Because Curves.elasticOut and Curves.elasticIn are
oscillating curves that grows/shrinks in magnitude while overshooting
its bounds.
When _isExpanded is false, you are expecting the width to go from 90 to 1 however Curves.elasticOut overshoots and becomes much less than 1 (hence a negative width) for a short period of time.
This is what is causing the error imho. Plus for what reason do you have nested AnimatedContainers? Top AnimatedContainer seems unnecessary.
Try getting rid of the top AnimatedContainer and changing all 4 of the Curves to Curves.linear and I think your error will be gone. Actually you can use any Curves which don't overshoot.
If you really need Elastic Curve, then maybe this would work too:
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
AnimatedContainer(
curve: _isExpanded ? Curves.elasticOut : Curves.easeInSine,
duration: Duration(milliseconds: 800),
width: _isExpanded ? 90 : 1,
color: Colors.white,
padding: EdgeInsets.only(bottom: 65.0),
child: Column(
children: <Widget>[
],
),
),
GestureDetector(
onTap: _toggleExpanded,
child: StoriesShapeBorder(
alignment: Alignment(1, 0.60),
icon: Icons.adjust,
color: Colors.white,
barWidth: 4,
),
),
],
);
}
Try it and let me know.
For example, you can use AnimatedController, to (oddly enough) control your animation value and set minimum value "0".
import 'dart:math' as math;
double getValue(...) {
// some logic / curves
// ....
return math.max(0, value);
}