I was trying to add a button to this page that will (play or pause) the waves animation in the background.
code link: https://github.com/apgapg/flutter_profile/blob/master/lib/page/intro_page.dart
I've tried many things but since I still bad with flutter animations I still without result.
Thanks in advance.
Short answer:
AnimationController _controller = ...;
// To stop animation
_controller.stop();
// To start from beginning
_controller.forward();
// To start from a point other than the very beginning.
_controller.forward(from: 0.8);
Long answer:
I wasn't aware of that code, here is how I did. All you need is Controller.reset() to stop the animation and Controller.repeat() to start it.
However if you need to start the animation just once, use Controller.forward() and Controller.reverse().
void main() => runApp(MaterialApp(home: Scaffold(body: HomePage())));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
AnimationController _controller;
bool _isPlaying = true;
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
lowerBound: 0.3,
duration: Duration(seconds: 3),
)..repeat();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Animation")),
body: Stack(
alignment: Alignment.center,
children: <Widget>[
_buildCircularContainer(200),
_buildCircularContainer(250),
_buildCircularContainer(300),
Align(child: CircleAvatar(backgroundImage: AssetImage("assets/images/profile.png"), radius: 72)),
Align(
alignment: Alignment(0, 0.5),
child: RaisedButton(
child: Text(_isPlaying ? "STOP" : "START"),
onPressed: () {
if (_isPlaying) _controller.reset();
else _controller.repeat();
setState(() => _isPlaying = !_isPlaying);
},
),
),
],
),
);
}
Widget _buildCircularContainer(double radius) {
return AnimatedBuilder(
animation: CurvedAnimation(parent: _controller, curve: Curves.fastLinearToSlowEaseIn),
builder: (context, child) {
return Container(
width: _controller.value * radius,
height: _controller.value * radius,
decoration: BoxDecoration(color: Colors.black54.withOpacity(1 - _controller.value), shape: BoxShape.circle),
);
},
);
}
}
When you directly have access to the AnimationController this snippet will start/stop the animation, wherever it left off.
animationController.isAnimating
? animationController.stop()
: animationController.forward();
Here the .isAnimating property is of type bool and is true when the animationController is animating at the moment. Depending on the result .stop()/.forward() will stop/start the animation respectively.
Related
I'm trying to reproduce the same contextual menu of Pinterest, in Flutter. So I have an overlay, which displays my contextual menu with a background, and I'd like to keep also the current element in front of the overlay.
Here are 2 videos: the Pinterest menu vs My Flutter menu.
To make an overlay I'm using flutter_portal package.
Here is the code of the blue card widget, with the overlay stuff:
import 'package:flutter/material.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:vector_math/vector_math.dart' show radians;
class CardWidget extends StatefulWidget {
#override
_CardWidgetState createState() => _CardWidgetState();
}
class _CardWidgetState extends State<CardWidget> with SingleTickerProviderStateMixin {
late AnimationController controller;
late Animation<double> degreeAnimation;
late Animation<double> opacityAnimation;
bool isMenuOpen = false;
double menuPosX = 0;
double menuPosY = 0;
#override
void initState() {
controller = AnimationController(vsync: this, duration: Duration(milliseconds: 200))
..addListener(() {
setState(() {});
})
..addStatusListener((status) {
if (status == AnimationStatus.forward) {
isMenuOpen = true;
} else if (status == AnimationStatus.dismissed) {
isMenuOpen = false;
}
});
degreeAnimation =
Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(parent: controller, curve: Curves.easeOutBack));
opacityAnimation =
Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(parent: controller, curve: Curves.easeOutExpo));
super.initState();
}
#override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 63.5 / 88.9,
child: PortalEntry(
visible: isMenuOpen,
portal: Container(
color: Colors.black.withOpacity(opacityAnimation.value / 2),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
controller.reverse();
},
),
),
child: PortalEntry(
visible: isMenuOpen,
child: GestureDetector(
onTapDown: (details) {
if (controller.isCompleted) {
controller.reverse();
} else {
menuPosX = details.globalPosition.dx;
menuPosY = details.globalPosition.dy;
controller.forward();
}
},
// Blue card content
child: Container(
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.all(
Radius.circular(8),
),
),
child: Center(
child: Text(
'Card Widget',
style: TextStyle(color: Colors.white),
),
),
),
),
// Current contextual Menu
portal: ContextualMenuWidget(),
),
),
);
}
}
Thank you for your help!
Maybe instead of an trying to add an overlay, you can change the color of all the other cards to the same as the background. By that you would achieve the same effect.
You can check if isMenuOpen is true and save the index of the card on which it was clicked, and in your grid builder function you can change the color of all the cards to the same as the background.
Thanks to orotype, instead of making a black overlay, I finally wrapped my cards with an AnimatedOpacity, and passed a callback when the contextual menu is opened/closed to trig the animation.
Result here!
I don't know if this is the best solution, but I like the result.
I make simple animation to change color Appbar using Tween[Color] , NotificationListener[ScrollNotification] and Appbar. I want change color Appbar and Action Icon while scrolling screen with this condition :
If user scroll == minScrollExtent => Change Color to Transparant
Else Change Color to Primary Color.
Logic
if (notification.metrics.pixels == notification.metrics.minScrollExtent) {
Future.delayed(Duration(seconds: 0), () => controllerAppbar.reverse());
} else {
Future.delayed(Duration(seconds: 0), () => controllerAppbar.forward());
}
When i scrolled the screen and reached both condition , i get this error but i got the result what i want.
Error
════════ Exception caught by widgets library ═══════════════════════════════════
Class 'Color' has no instance method '-'.
Receiver: Instance of 'Color'
Tried calling: -(Instance of 'Color')
The relevant error-causing widget was
AnimatedBuilder
How i can fixed it ?
AppBarAnimation Widget
class AppBarAnimationColor extends StatefulWidget {
final Duration duration;
final AnimationController appBarAnimationController;
AppBarAnimationColor({
#required this.appBarAnimationController,
this.duration = const Duration(seconds: 1),
});
#override
AppBarAnimationColorState createState() => AppBarAnimationColorState();
}
class AppBarAnimationColorState extends State<AppBarAnimationColor>
with SingleTickerProviderStateMixin {
Animation<Color> appbarColor, iconColor;
#override
void initState() {
super.initState();
appbarColor = Tween<Color>(begin: Colors.transparent, end: colorPallete.primaryColor)
.animate(widget.appBarAnimationController);
iconColor = Tween<Color>(begin: colorPallete.primaryColor, end: colorPallete.white)
.animate(widget.appBarAnimationController);
}
#override
void dispose() {
widget.appBarAnimationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: appbarColor,
builder: (_, __) => AppBar(
actions: [
IconButton(
icon: Icon(FontAwesomeIcons.bars),
onPressed: () => '',
color: iconColor.value,
)
],
elevation: 0,
backgroundColor: appbarColor.value,
),
);
}
}
Welcome Screen
class _WelcomeScreenState extends State<WelcomeScreen>
with TickerProviderStateMixin {
AnimationController _appbarAnimationController;
#override
void initState() {
super.initState();
_appbarAnimationController =
AnimationController(vsync: this, duration: kThemeAnimationDuration);
}
#override
void dispose() {
_appbarAnimationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
onNotification: (notification) => commonF.handleScrollNotification(
notification,
controllerAppbar: _appbarAnimationController,
),
child: SafeArea(
child: Scaffold(
extendBodyBehindAppBar: true,
body: Stack(
fit: StackFit.expand,
children: [
SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 1000),
],
),
),
Positioned(
child: AppBarAnimationColor(
appBarAnimationController: _appbarAnimationController,
),
top: 0,
left: 0,
right: 0,
),
],
)
),
),
);
}
}
Logic
bool handleScrollNotification(
ScrollNotification notification, {
AnimationController controllerAppbar,
}) {
if (notification.metrics.pixels == notification.metrics.minScrollExtent) {
Future.delayed(Duration(seconds: 0), () => controllerAppbar.reverse());
} else {
Future.delayed(Duration(seconds: 0), () => controllerAppbar.forward());
}
return false;
}
You are using Tween, insted of that use ColorTween. Tween is use for value changes like int, float.
appbarColor = ColorTween(begin: Colors.transparent, end: colorPallete.primaryColor)
.animate(widget.appBarAnimationController);
iconColor = ColorTween(begin: colorPallete.primaryColor, end: colorPallete.white)
.animate(widget.appBarAnimationController);
I want to make a simple recurring animation to an image. It should keep animating until the user closes the screen. What I want it that the image slowly gets bigger and then smaller.
I've checked the animatedContainer, but that doesn't seem to do this dynamically. Here is the code I used with AnimatedContainer:
#override
void didChangeDependencies() {
increaseSize(widget.seconds);
super.didChangeDependencies();
}
void increaseSize(int toSize) {
for (i = 0; i > toSize; i++) {
setState(() {
_width += i;
_height += i;
});
}
}
#override
Widget build(BuildContext context) {
return AnimatedContainer(
// Use the properties stored in the State class.
width: _width,
height: _height,
decoration: BoxDecoration(
color: _color,
borderRadius: _borderRadius,
),
// Define how long the animation should take.
duration: Duration(seconds: 30),
// Provide an optional curve to make the animation feel smoother.
curve: Curves.easeInOutBack,
);
;
}
Has anyone come across this before, any help would be appreciated.
Many thanks
Try something like this:
It's an example of a container which grows and shrinks from 50 to 100 and back to 50.
class SizeWidget extends StatefulWidget {
final int seconds;
const SizeWidget({this.seconds = 3});
#override
_SizeWidgetState createState() => _SizeWidgetState();
}
class _SizeWidgetState extends State<SizeWidget> with TickerProviderStateMixin {
AnimationController _animationController;
Animation _sizeAnimation;
bool reverse = false;
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this, duration: Duration(seconds: widget.seconds))
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_animationController.repeat(reverse: !reverse);
reverse = !reverse;
}
});
_sizeAnimation =
Tween<double>(begin: 50.0, end: 100.0).animate(_animationController);
_animationController.forward();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _sizeAnimation,
builder: (context, child) => Container(
width: _sizeAnimation.value,
height: _sizeAnimation.value,
color: Colors.red,
),
);
}
}
Another option would be to use a TweenSequence and tell the animation controller to repeat itself:
class SizeWidget extends StatefulWidget {
final int seconds;
const SizeWidget({this.seconds = 3});
#override
_SizeWidgetState createState() => _SizeWidgetState();
}
class _SizeWidgetState extends State<SizeWidget> with TickerProviderStateMixin {
AnimationController _animationController;
Animation _sizeAnimation;
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this, duration: Duration(seconds: widget.seconds));
_sizeAnimation = TweenSequence<double>(
[
TweenSequenceItem<double>(
tween: Tween<double>(
begin: 50,
end: 100,
),
weight: 50,
),
TweenSequenceItem<double>(
tween: Tween<double>(
begin: 100,
end: 50,
),
weight: 50,
),
],
).animate(_animationController);
_animationController.repeat(reverse: true);
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _sizeAnimation,
builder: (context, child) => Container(
width: _sizeAnimation.value,
height: _sizeAnimation.value,
color: Colors.red,
),
);
}
}
I would like to rotate an Image indefinitely.
This container is one of the widget within the stack and would like this to be rotating continuously non stop.
final AnimationController animation = AnimationController(
duration: const Duration(milliseconds: 1800),
vsync: const NonStopVSync(),
)..repeat();
final Tween tween = Tween(begin: 0.0, end: math.pi);
var square = Container(
width: 100,
height: 100,
transform: Matrix4.identity(),
color: Colors.amber,
);
...
class Foo extends State<Bar> {
...
animation.addListener((){
square.transform = Matrix4.rotationZ(tween.evaluate(animation));
});
Widget build(BuildContext context) {
return Stack(
children: [
...
Center(
child: square
)
]
)
}
}
and I get this error: 'transform' can't be used as a setter because it's final. (assignment_to_final at [digital_clock] lib/digital_clock.dart:139)
How would I do what I'm trying to do?
Try something like this:
class InfiniteAnimation extends StatefulWidget {
final Widget child;
final int durationInSeconds;
InfiniteAnimation({#required this.child, this.durationInSeconds = 2,});
#override
_InfiniteAnimationState createState() => _InfiniteAnimationState();
}
class _InfiniteAnimationState extends State<InfiniteAnimation>
with SingleTickerProviderStateMixin {
AnimationController animationController;
Animation<double> animation;
#override
void initState() {
super.initState();
animationController = AnimationController(
vsync: this,
duration: Duration(seconds: widget.durationInSeconds),
);
animation = Tween<double>(
begin: 0,
end: 12.5664, // 2Radians (360 degrees)
).animate(animationController);
animationController.forward();
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
animationController.repeat();
}
});
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animationController,
builder: (context, child) => Transform.rotate(
angle: animation.value,
child: widget.child,
),
);
}
#override
void dispose() {
animationController?.dispose();
super.dispose();
}
}
You basically need to create a StatefulWidget that mixes in (with keyword) the SingleTickerProviderStateMixin, provide an AnimationController, start the animation, then when the animation completes, repeat.
The AnimationBuilder is a better way of telling the widget to update on every frame without having to listen to the animationController and call setState explicitly.
You can use it like this:
InfiniteAnimation(
durationInSeconds: 2, // this is the default value
child: Icon(
Icons.expand_more,
size: 50.0,
color: Colors.white,
),
)
in this simple sample code i want to have fadeIn and fadeOut animation together, but in this code fadeIn work only and reverse not work, how can i have both of them together?
import 'package:flutter/material.dart';
void main()=>runApp(MaterialApp(home: FadeTransitionSample(),));
class FadeTransitionSample extends StatefulWidget {
#override
State<StatefulWidget> createState() => _Fade();
}
class _Fade extends State<FadeTransitionSample> with TickerProviderStateMixin {
AnimationController animation;
Animation<double> _fadeInFadeOut;
#override
void initState() {
super.initState();
animation = AnimationController(vsync: this, duration: Duration(seconds: 3),);
_fadeInFadeOut = Tween<double>(begin: 0.0, end: 0.1).animate(animation);
animation.addListener((){
if(animation.isCompleted){
animation.reverse();
}else{
animation.forward();
}
});
animation.repeat();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(
child: FadeTransition(
opacity: animation,
child: Container(
color: Colors.green,
width: 100,
height: 100,
),
),
),
),
);
}
}
Alright, I am assuming, you are looking to get a FadeIn & FadeOut animation on your container.
There are a few things you need to change.
The FadeTransition class should not take the animation for opacity, rather it should be the _fadeInFadeOut variable
The animation starts, when you call the animation.forward() rather than animation.repeat() (Since in your case, you also need to reverse the animation, always start with the animation.forward() call).
Make sure to use the addStatusListener() method instead of addListener() since you get much better control over your states.
So, all of this put together, below is the working code to make your animation work.
import 'package:flutter/material.dart';
void main()=>runApp(MaterialApp(home: FadeTransitionSample(),));
class FadeTransitionSample extends StatefulWidget {
#override
State<StatefulWidget> createState() => _Fade();
}
class _Fade extends State<FadeTransitionSample> with TickerProviderStateMixin {
AnimationController animation;
Animation<double> _fadeInFadeOut;
#override
void initState() {
super.initState();
animation = AnimationController(vsync: this, duration: Duration(seconds: 3),);
_fadeInFadeOut = Tween<double>(begin: 0.0, end: 0.5).animate(animation);
animation.addStatusListener((status){
if(status == AnimationStatus.completed){
animation.reverse();
}
else if(status == AnimationStatus.dismissed){
animation.forward();
}
});
animation.forward();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(
child: FadeTransition(
opacity: _fadeInFadeOut,
child: Container(
color: Colors.green,
width: 100,
height: 100,
),
),
),
),
);
}
}
Option #1
Simple solution and straight forward with less code, the idea is to combine FadeOut inside FadeIn and give the FadeOut delay amount greater than the duration of the FadeIn, just copy and paste then just change the Image.asset widget to anything you want to fade in/out
1- Add this package.
2- Import it in your page import 'package:animate_do/animate_do.dart';
3- Add this Widget:
Widget _animateLogo() {
return Container(
child: FadeIn(
animate: true,
duration: Duration(seconds: 2),
child: FadeOut(
animate: true,
delay: Duration(seconds: 2),
duration: Duration(seconds: 1),
// Just change the Image.asset widget to anything you want to fade in/out:
child: Image.asset(
"assets/images/logo.png",
height: 150,
width: 150,
fit: BoxFit.contain,
), //Image.asset
) // FadeOut
),
);
}
Option #2:
Use native class AnimatedOpacity class with setState:
// 1- Declare
bool _shouldFade = false;
// 2- Animation fucntion
Widget _animateLogo() {
return AnimatedOpacity(
duration: Duration(milliseconds: 200),
opacity: _shouldFade ? 1 : 0,
child: Container(
// Whatever you want to fadein fadeout
),
);
}
// 3- Animate whenever needed
setState(() {
// Fade in
_shouldFade = true;
});
// Fadeout after 3 seconds
Future.delayed(
Duration(seconds: 3),
() => setState(() {
_shouldFade = false;
}),
);