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.
Related
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
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();
}
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:
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)
I have a widget that defines an animation. This animation progresses from 0.0 to 1.0. This widget also has a child widget, whose opacity I'd like to control in sync with the animation progress. However, I don't see how I can have the child widget's state "track" the state of the parent's animation progress. Whatever I give from the parent to the child ends up being final and, thus, immutable.
I tried passing the Animation directly, but then the app crashes with error "The getter value was called on null".
Edit: with code. My problem is that the MenuItem class can't be made aware of the states of the Animation values.
import 'package:flutter/material.dart';
class MenuItem extends StatefulWidget {
final Function() onPressed;
final String tooltip;
final String helper;
final IconData icon;
MenuItem({this.onPressed, this.tooltip, this.helper, this.icon});
#override
_MenuItemState createState() => _MenuItemState();
}
class _MenuItemState extends State<MenuItem> {
bool isOpened = true;
double _elevateButtonValue = 0.0;
#override
Widget build(BuildContext context) {
return Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
AnimatedOpacity(
opacity: isOpened ? 1.0 : 0.0,
duration: Duration(milliseconds: 500),
child: new Container(
child: Text(widget.helper),
decoration: new BoxDecoration (
borderRadius: new BorderRadius.all(const Radius.circular(8.0)),
color: Colors.white,
boxShadow: [new BoxShadow(
color: Colors.black.withOpacity(0.3),
offset: Offset(0.0, 6.0),
blurRadius: 16.0,
),
],
),
padding: new EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 8.0),
),
),
SizedBox(width: 12.0),
FloatingActionButton(
onPressed: () {},
tooltip: widget.tooltip,
child: Icon(widget.icon),
backgroundColor: Colors.deepOrange,
elevation: _elevateButtonValue,
),
],
),
);
}
}
class MenuFabs extends StatefulWidget {
#override
_MenuFabsState createState() => _MenuFabsState();
}
class _MenuFabsState extends State<MenuFabs>
with SingleTickerProviderStateMixin {
bool isOpened = false;
AnimationController _animationController;
Animation<Color> _buttonColor;
Animation<double> _animateIcon;
Animation<double> _translateButton;
Animation<double> _elevateButton;
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: Colors.deepOrange,
end: Colors.black45,
).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,
),
));
_elevateButton = Tween<double>(
begin: 0.0,
end: 6.0,
).animate(CurvedAnimation(
parent: _animationController,
curve: Interval(
0.25,
1.0,
curve: _curve,
),
));
super.initState();
}
#override
dispose() {
_animationController.dispose();
super.dispose();
}
animate() {
if (!isOpened) {
_animationController.forward();
} else {
_animationController.reverse();
}
isOpened = !isOpened;
}
Widget goal() {
return Container(
child: new MenuItem(
onPressed: () {},
tooltip: 'Geolocate',
helper: 'Geolocate',
icon: Icons.radio_button_checked,
),
);
}
Widget invite() {
return Container(
child: new MenuItem(
onPressed: () {},
tooltip: 'Invite friends',
helper: 'Invite friends',
icon: Icons.person_add,
),
);
}
Widget toggle() {
return Container(
child: FloatingActionButton(
backgroundColor: _buttonColor.value,
onPressed: animate,
tooltip: 'Toggle',
child: AnimatedIcon(
icon: AnimatedIcons.menu_close,
progress: _animateIcon,
),
),
);
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Transform(
transform: Matrix4.translationValues(
0.0,
_translateButton.value * 2.0,
0.0,
),
child: goal(),
),
Transform(
transform: Matrix4.translationValues(
0.0,
_translateButton.value,
0.0,
),
child: invite(),
),
Transform(
transform: Matrix4.translationValues(
168.0,
0.0,
0.0,
),
child: toggle(),
),
],
);
}
}
Adding with SingleTickerProviderStateMixin to the child Widget made the error go away!