Animate FAB button like Gmail Compose Button in Flutter - 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

Related

Flutter animatedOpacity onEnd not being called

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,
),
),
]));
}
}

In Flutter, what's the easiest, simplest way to make a container flashing once without involving animation widgets?

Imagine Facebook mobile app, where you tap on the notification about someone like your comment. The app will open the appropriate screen, scroll you down to the comment, and after you arrive there, the comment row will flash yellow for a while, rapidly turn transparent, and then it's done.
I just want to make the same flashing animation to a ListView/Column element to let users know that something is happening there as a result of their action. But from what I gathered, to create just a simple animation like that needs a complex elaborate contraption with Animation widgets.
There's a widget that does a much appreciated fade animation called FadeInImage. I just need to provide destination URL, placeholder image asset, and the widget will handle the rest. I'm wondering if there's such alternative where I can just provide a key to a widget, and then call from anywhere: rowKey.currentState.flash(color: Colors.yellow). Or perhaps a way to let me tell the ListView or Column to flash certain row like listViewKey.currentState.items[5].flash(color: Colors.yellow).
There is no a Widget like you are looking for, but you can create a custom widget if you know the Flutter basics. You will be able to build from simple animations to the most advanced ones.
I made a simple example, a list of elements where you can select any element from the list (index).
When you open the screen, you will see the scroll animation, after that, the blink animation will start.
class FlashingHome extends StatelessWidget {
const FlashingHome({Key? key}) : super(key: key);
void _goToWidget(BuildContext context, int index) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => FlashingList(index: index),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
MaterialButton(
color: Colors.greenAccent,
child: const Text('Go to element 5'),
onPressed: () => _goToWidget(context, 5),
),
MaterialButton(
color: Colors.greenAccent,
child: const Text('Go to element 10'),
onPressed: () => _goToWidget(context, 10),
),
],
),
),
);
}
}
class FlashingList extends StatefulWidget {
const FlashingList({required this.index, Key? key}) : super(key: key);
final int index;
#override
State<FlashingList> createState() => _FlashingListState();
}
class _FlashingListState extends State<FlashingList>
with SingleTickerProviderStateMixin {
final _scrollController = ScrollController();
late final AnimationController _animationController;
final _itemSize = 150.0;
Timer? _timer;
Future<void> _startScrolling() async {
await _scrollController.animateTo(
_itemSize * widget.index,
duration: const Duration(seconds: 1),
curve: Curves.easeOut,
);
// after the scroll animation finishes, start the blinking
_animationController.repeat(reverse: true);
// the duration of the blinking
_timer = Timer(const Duration(seconds: 3), () {
setState(() {
_animationController.stop();
_timer?.cancel();
});
});
}
#override
void initState() {
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
WidgetsBinding.instance!.addPostFrameCallback((_) => _startScrolling());
super.initState();
}
#override
void dispose() {
_timer?.cancel();
_animationController.dispose();
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flashing List'),
),
body: ListView.builder(
controller: _scrollController,
itemCount: 15,
itemExtent: 150,
itemBuilder: (context, index) {
final item = Padding(
padding: const EdgeInsets.all(20.0),
child: Text('My Item :$index'),
);
return Padding(
padding: const EdgeInsets.all(4.0),
child: FittedBox(
child: index == widget.index && _animationController.isDismissed
? FadeTransition(
opacity: _animationController,
child: Container(
color: Colors.yellow,
child: item,
),
)
: Container(
color: Colors.grey[200],
child: item,
),
),
);
},
),
);
}
}
Result:
Now that you know how to create an automatic scrolling list, animated item, you can customize this with a more complex animation and extract into a custom widget that you can reuse in your projects.
Reference: https://docs.flutter.dev/development/ui/animations
Try shimmer
While the data is being fetched, you can create a simple animation which will give the feel that something's loading. Here's a simple example.
I am using FAB onPress to reflect the changes.
bool isApiCallProcess = false;
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
isApiCallProcess = !isApiCallProcess;
});
},
),
backgroundColor: Colors.white,
body: (isApiCallProcess == false)
? CircleAvatar(
backgroundColor:
Colors.black12,
radius: 40,
backgroundImage: AssetImage(
'images/dosa.jpg',
),
):
Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Wrap(
children: [
Column(
children: [
const CircleAvatar(
radius: 40,
backgroundImage: AssetImage(
'',
),
),
const SizedBox(
height: 10,
),
Container(
decoration: ShapeDecoration(
color: Colors.grey[400]!,
shape: const
RoundedRectangleBorder(),
),
height: 12,
width: 60,
),
],
),
],
),
),
),
);
Here's the screenshots :

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.
);
}
}

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)