Flutter Animating listview elements is not working - flutter

I am trying to do a glow effect on the container. I am using streambuilder to build list items and when item price changes I want to show container glow effect. So far I have done this but the widget doesn't show the effect at all. didUpdateWidget doesn't show any update and when it does, all of the listview items animates themselves for infinite amount of time. Is there something I am missing?
AnimationController _animationController;
#override
void initState() {
super.initState();
_animationController = new AnimationController(
vsync: this, duration: new Duration(milliseconds: 500));
}
#override
void didUpdateWidget(CustomContainer oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.price != widget.price) {
print("changed");
_animationController.repeat(
reverse: true, period: Duration(milliseconds: 1000));
}
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animationController,
builder: (context, _) {
return Container(
height:100,
width:double.infinity,
decoration: BoxDecoration(
color: Theme.of(context).canvasColor,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
blurRadius: 4 * _animationController.value,
color: primaryColor.withOpacity(0.7),
),
],
),
);
});

AnimationController.repeat() will start running the animation and repeat it an infinite amount of times. This is why the items are animating themselves for infinitely. You would have to use .forward method instead:
_animationController = AnimationController(
duration: Duration(milliseconds: 500),
vsync: this);
_animationController.forward(); //tell the animation to start without repeating
From Flutter Docs:
AnimationController is a special Animation object that generates a new
value whenever the hardware is ready for a new frame. By default, an
AnimationController linearly produces the numbers from 0.0 to 1.0
during a given duration.
AnimationController derives from Animation, so it can be used
wherever an Animation object is needed. However, the
AnimationController has additional methods to control the animation.
For example, you start an animation with the .forward() method. The
generation of numbers is tied to the screen refresh, so typically 60
numbers are generated per second. After each number is generated, each
Animation object calls the attached Listener objects. To create a
custom display list for each child, see RepaintBoundary.
Here is the docs for AnimationController.forward(): https://api.flutter.dev/flutter/animation/AnimationController/forward.html

Related

How do I animate Color using an AnimatedBuilder widget? Flutter

I am trying to animate color using the AnimatedBuilder widget in Flutter.
I can use this builder widget to animate scale, translate, rotate by returning a Transform object. But I am lost to as how I can do this to animate color.
Note, I can animate color using the AnimatedContainer widget but I want to be able to do more complex animation using Intervals (e.g. in this case, fade in a color, wait a period of time, then fade back out).
Is this possible?
Thanks!
class _AnimatedColorState extends State<AnimatedColor>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Color?> _animFadeIn, _animFadeOut;
#override
void initState() {
_controller = AnimationController(
duration: Duration(milliseconds: 1000),
vsync: this,
);
_animFadeIn = ColorTween(begin: Colors.pinkAccent, end: Colors.green)
.animate(CurvedAnimation(
parent: _controller,
curve: Interval(0, 0.20, curve: Curves.fastOutSlowIn)));
_animFadeOut = ColorTween(begin: Colors.green, end: Colors.pinkAccent)
.animate(CurvedAnimation(
parent: _controller,
curve: Interval(0.80, 1.0, curve: Curves.fastOutSlowIn)));
_controller.forward();
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Container(
height: 300,
width: 100,
decoration: BoxDecoration(
color: // animate color here?
));
});
}
}
You can use the .lerp() method on Color to specify two colors and a value from your animationController.
https://api.flutter.dev/flutter/dart-ui/Color/lerp.html

Creating a widget upon clicking a button in flutter using animation

I'm new to flutter I have a search field and upon clicking, it should expand to another input field. I have done it but the widget gets re-created upon clicking the search filed.
As you can see, I want these fields to open up nicely with animation so that it looks good. Do you have any suggestions?
You can wrap the second TextField in a AnimatedContainer (or in a SizeTransition, but you will have to use a AnimationController).
If you use a AnimatedContainer you can simply change the height of the widget by a bool and the widget will animate itself. Eg:
AnimatedContainer(
width: MediaQuery.of(context).size.width,
height: showSecondField ? 40 : 0,
child: ...
)
or with the SizeTransition:
class YourWidget extends State<...> with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<double> _anim;
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 200),
);
final CurvedAnimation curve = CurvedAnimation(parent: _controller, curve: Curves.fastOutSlowIn);
_anim = Tween<double>(begin: 0.0, end: 1.0).animate(curve);
}
#override
Widget build(BuildContext context) {
return SizeTransition(
sizeFactor: _anim,
child: TextField()
);
}
}
And you can open or close with: _controller.forward() and _controller.reverse()

Animation on gridview item in flutter

So I'm trying to add animation when adding item to gridview. the animation work, but had a litle problem. the way i insert the data is like a stack so last item inserted is the first item in list and every time an item added, the the animation of items that already in the list keep restarting
class _MainListItemState extends State<MainListItem>
with SingleTickerProviderStateMixin {
AnimationController animationController;
Animation<double> opacityAnimation;
#override
void initState() {
super.initState();
animationController = AnimationController(duration: const Duration(milliseconds: 500), vsync: this);
opacityAnimation = Tween<double>(begin: 0, end: 1).animate(animationController);
animationController.forward();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animationController,
builder: (BuildContext buiild, Widget mine) => Opacity(
opacity: opacityAnimation.value,
child: ItemWidget()
),
);
}
}

Laggy SizeTransition Animation in Flutter Column?

I'm trying to animate a panel coming up from the bottom of the screen. My setup is to have a column with two Containers, then animate the size of the lower Container to get a "sliding up" panel effect. The panel shouldn't be on top of the other so I can't use a stack.
Anyway, the problem is using the SizeTransition. I create an AnimationController to control the sizeFactor of the SizeTransition. The problem is that using any Curves in the animation makes it horribly laggy and I don't know why. Obviously, there must be a way to make this run smoothly but I'm at a loss. Here is a minimal example of what I mean:
class WelcomePage extends StatefulWidget {
#override
_WelcomePageState createState() => _WelcomePageState();
}
class _WelcomePageState extends State<WelcomePage>
with SingleTickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
);
// Setting to curve: Curves.linear here makes it run smoothly but anything else brings us to choppy town
_controller.animateTo(210.0, curve: Curves.ease);
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Expanded(child: Container(color: Colors.blue)),
SizeTransition(
sizeFactor: _controller,
child: Container(
height: 200,
color: Colors.green,
))
],
);
}
}

Flutter SlideTransition begin with Offset OFF SCREEN

My app is a simple Column with 3 Text widgets wrapped in SlideTransitions. My goal is for the app to load with nothing on the screen, and then animate these Text widgets from the bottom (off screen) up into the screen (settling in the middle).
return Column(
children: <Widget>[
SlideTransition(
position:_curve1,
child: Text('Hello 1')
),
SlideTransition(
position:_curve1,
child: Text('Hello 2')
),
SlideTransition(
position:_curve1,
child: Text('Hello 3')
),
]
);
The problem is that when defining the animation, you have to specify their initial Offset.
#override
void initState(){
...
Offset beginningOffset = Offset(0.0,20.0);
_curve1 = Tween<Offset>(begin: beginningOffset, end: Offset.zero).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut)));
...
}
Here you can see that beginningOffset is specified as Offset(0.0, 20.0) which does successfully position the widgets off screen. But what happens if I suddenly run this on a tablet? Since Offset is defined as units of the height of the widget, then this is obviously not a good way to position a widget off screen.
Since the animation is defined in "initState()" ... I see no way to use something like MediaQuery.of(context) ... since we don't have context there.
In native iOS this would be simple. Within "viewDidLoad" you would get the frame from self.view. You'd position each text field at the edge of the frame explicitly. You'd then animate a constraint change, positioning them where you want them. Why must this be so hard in Flutter?
I find it especially curious that this exact scenario (starting an animation just off screen) is totally not covered in any of the examples I could find. For instance:
https://github.com/flutter/website/blob/master/examples/_animation/basic_staggered_animation/main.dart
Seems like almost every type of animation is featured here EXCEPT an explicit off screen animation on load... Perhaps I'm just missing something.
EDIT: ThePeanut has solved it! Check his "Second Update".
You might want to try using the addPostFrameCallback method in your initState.
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_){
// Schedule code execution once after the frame has rendered
print(MediaQuery.of(context).size.toString());
});
}
Flutter Api Docs Link
OR you can also use a Future for this:
#override
void initState() {
super.initState();
new Future.delayed(Duration.zero, () {
// Schedule a zero-delay future to be executed
print(MediaQuery.of(context).size.toString());
});
}
Hope this helps.
UPDATED
A bit of a unusual way to do it, but it really does the thing you need.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
Animation<Offset> animation;
AnimationController animationController;
#override
void initState() {
super.initState();
animationController = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
);
animation = Tween<Offset>(
begin: Offset(0.0, 1.0),
end: Offset(0.0, 0.0),
).animate(CurvedAnimation(
parent: animationController,
curve: Curves.fastLinearToSlowEaseIn,
));
Future<void>.delayed(Duration(seconds: 1), () {
animationController.forward();
});
}
#override
void dispose() {
// Don't forget to dispose the animation controller on class destruction
animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Stack(
alignment: Alignment.center,
fit: StackFit.expand,
children: <Widget>[
SlideTransition(
position: animation,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircleAvatar(
backgroundImage: NetworkImage(
'https://pbs.twimg.com/media/DpeOMc3XgAIMyx_.jpg',
),
radius: 50.0,
),
],
),
),
],
),
);
}
}
It works this way:
1. We create a Stack of items with options to expand any child inside it.
2. Wrap each slide you want to display in to a Column with center alignment of children. The Column will take 100% of the size of the Stack.
3. Set the beginning Offset for animation to Offset(0.0, 1.0).
Keep in mind that dx and dy in Offset are not pixels or something like that, but the ratio of Widget's width or height. For example: if your widget's width is 100.0 and you put 0.25 as dx - it will result in moving your child to the right by 25.0 points.
So setting offset to (0.0, 1.0) will move the Column offscreen to the bottom by it's 100% height (this is how many page transitions work in Flutter).
4. Animate the Column back to it's original position after a 1 second delay.
SECOND UPDATE
This code calculates the offset based on the screen size and widget size.
PS. There might be a better way of doing this that I don't know of.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Page(),
);
}
}
class Page extends StatefulWidget {
#override
State<StatefulWidget> createState() => _PageState();
}
class _PageState extends State<Page> with SingleTickerProviderStateMixin {
// Set the initial position to something that will be offscreen for sure
Tween<Offset> tween = Tween<Offset>(
begin: Offset(0.0, 10000.0),
end: Offset(0.0, 0.0),
);
Animation<Offset> animation;
AnimationController animationController;
GlobalKey _widgetKey = GlobalKey();
#override
void initState() {
super.initState();
// initialize animation controller and the animation itself
animationController = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
);
animation = tween.animate(animationController);
Future<void>.delayed(Duration(seconds: 1), () {
// Get the screen size
final Size screenSize = MediaQuery.of(context).size;
// Get render box of the widget
final RenderBox widgetRenderBox =
_widgetKey.currentContext.findRenderObject();
// Get widget's size
final Size widgetSize = widgetRenderBox.size;
// Calculate the dy offset.
// We divide the screen height by 2 because the initial position of the widget is centered.
// Ceil the value, so we get a position that is a bit lower the bottom edge of the screen.
final double offset = (screenSize.height / 2 / widgetSize.height).ceilToDouble();
// Re-set the tween and animation
tween = Tween<Offset>(
begin: Offset(0.0, offset),
end: Offset(0.0, 0.0),
);
animation = tween.animate(animationController);
// Call set state to re-render the widget with the new position.
this.setState((){
// Animate it.
animationController.forward();
});
});
}
#override
void dispose() {
// Don't forget to dispose the animation controller on class destruction
animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
fit: StackFit.loose,
children: <Widget>[
SlideTransition(
position: animation,
child: CircleAvatar(
key: _widgetKey,
backgroundImage: NetworkImage(
'https://pbs.twimg.com/media/DpeOMc3XgAIMyx_.jpg',
),
radius: 50.0,
),
),
],
);
}
}
I found this solution, to have the exact value of off-screen.
Screen size / 2 / widget size + half of 1 offset
Without the half offset, only half of the widget is off-screen.
final widgetHeight = 190;
final offsetScreen = MediaQueryData.fromWindow(WidgetsBinding.instance!.window).size.height / 2 / widgetHeight + 0.5;