How to slow down a fling animation in Flutter? - flutter

I was playing with the fling animation based on the grid demo in Flutter Gallery. I made the example below work, but the animation plays very fast. I could barely see it unless I slow it down by using timeDilation. Changing the value of velocity doesn't seem to have much effect. Should I look at something else? Thanks!
import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
const kLogoUrl =
"https://raw.githubusercontent.com/dart-lang/logos/master/logos_and_wordmarks/dart-logo.png";
class LogoWidget extends StatelessWidget {
// Leave out the height and width so it fills the animating parent
build(BuildContext context) {
return new Container(
margin: new EdgeInsets.symmetric(vertical: 10.0),
child: new Image.network(kLogoUrl));
}
}
class TranslateTransition extends StatelessWidget {
TranslateTransition({this.child, this.animation});
Widget child;
Animation<Offset> animation;
Widget build(BuildContext context) {
return new AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget child) {
return new Center(
child: new Transform(
transform: new Matrix4.identity()
..translate(animation.value.dx, animation.value.dy),
child: new Container(
height: 100.0,
width: 100.0,
child: child,
),
),
);
},
child: child);
}
}
class LogoApp extends StatefulWidget {
LogoAppState createState() => new LogoAppState();
}
class LogoAppState extends State<LogoApp> with TickerProviderStateMixin {
Animation<Offset> _flingAnimation;
AnimationController _controller;
initState() {
super.initState();
timeDilation = 5.0;
_controller = new AnimationController(
vsync: this,
);
_flingAnimation = new Tween<Offset>(
begin: new Offset(-150.0, -150.0),
end: new Offset(150.0, 150.0),
)
.animate(_controller);
_controller
..value = 0.0
..fling(velocity: 0.1)
..addListener(() {
// print(_flingAnimation.value);
});
}
Widget build(BuildContext context) {
return new TranslateTransition(
child: new LogoWidget(), animation: _flingAnimation);
}
#override
dispose() {
_controller.dispose();
}
}
void main() {
runApp(new LogoApp());
}

fling uses a SpringSimulation with default parameters, one of which is the spring constant. Even if you start with velocity zero, a spring will spring at a speed determined by the spring constant. So what's happening is that you're going from 0.0 to 1.0 with a pretty tight critically-damped string.
Also, because you're using a NetworkImage, you don't see anything because the image takes longer to load than the animation takes to run.
If you replace LogoWidget with FlutterLogo, you'll see what's happening better.
If you want it to go slower, you can use animateWith instead of fling to pass it a specific SpringSimulation with your own custom parameters.
The existence of fling is a bit of a historical accident. It's designed to be used primarily with AnimationControllers with a lowerBound and upperBound given in pixels, rather than over the 0.0...1.0 default range.

Related

how to make icon and text translation animation on flutter

how to make this animation on flutter
icon and text
the default state is the icon is shown and the text is disappears
when click the icon: the icon goes up and text goes under icon and appears
otherwise the icon goes in center and the text disappears
like this video
https://i.imgur.com/S0LXr3o.mp4
https://drive.google.com/file/d/1nwpgjOM_6TUaaVaSdsIZp0oYi4CdWSMR/view?usp=sharing
you can use AnimationController and AnimationBuilder combined with Stack + Positioned
or you can even use the Transform Widget with the same concept!
I've write an example to make the animation
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class AnimationExerciseScreen extends StatefulWidget {
const AnimationExerciseScreen({Key? key}) : super(key: key);
#override
_AnimationExerciseScreenState createState() =>
_AnimationExerciseScreenState();
}
class _AnimationExerciseScreenState extends State<AnimationExerciseScreen>
with SingleTickerProviderStateMixin {
#override
void initState() {
super.initState();
animationController = AnimationController(
vsync: this,
duration: Duration(seconds: 3),
);
animationController.forward();
}
#override
void dispose() {
animationController.dispose();
super.dispose();
}
late final AnimationController animationController;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: 100,
child: AnimatedBuilder(
animation: animationController,
builder: (context, child) => Stack(
children: [
Positioned(
top: 0 + (40 * animationController.value),
child: Icon(Icons.cloud),
),
Positioned(
bottom: 0 + (40 * animationController.value),
child: Opacity(
opacity: 1 - animationController.value,
child: Text('Cloud'),
),
),
],
),
),
),
],
),
),
);
}
}
video link:
https://imgur.com/RJg5PWw
Explanation:
the animation controller has a value of 0 to 1 with the type of double, which will represent the amount percentage of the animation
in the above example, I'm using 3 seconds duration of the animation controller so the animation will be visible to our eyes easily, so I use animationController.forward to play the animation at the initState
note: the placement of the animation is not optimized for performance, this example is just for example to understand how the animation works
if you want to optimize the animation, you can put your widget to child attribute of the AnimationBuilder for more info you can read them here and here and here and so much more! you can explore tons of articles to improve your flutter app's performance!

How to load stateful widget in the background

I got a stateful widget that changes the background image every 10 seconds, I've noticed that it flickers each time it changes the background image.
It cycles through a list of images to use for the background.
When it reaches the end of the list and comes back to load the first image, it will not flicker anymore.
I did some googling on this and the flicker is caused by rebuilding the entire widget in setState.
However, after it loads all the possible widgets with different images, it will not flicker.
My hypothesis is that it will store the previous widget either in cache or will figure out what changed in the previous setState therefore it won't flicker as it knows only to change the background.
I'm not sure if this is right or not.
My question is, how do you load multiple widgets but not show them on the screen and in the background.
So basically during the 10 seconds it takes to switch from the first background image to the second.
There will be a Future that loads all the different possible background images.
Thanks in advance!
EDIT: (Code to replicate issue)
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Title',
theme: ThemeData(
backgroundColor: Colors.white,
primarySwatch: Colors.blue,
),
home: MyHome(),
);
}
}
class MyHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ImageRotater(
child: Text("Some text"),
);
}
}
class ImageRotater extends StatefulWidget {
final Widget child;
ImageRotater({required this.child});
#override
_ImageRotaterState createState() => _ImageRotaterState();
}
class _ImageRotaterState extends State<ImageRotater>
with TickerProviderStateMixin {
late final subtree;
static List<String> imageNames = [
"1.png",
"2.png",
"3.png",
];
int _pos = 0;
late Timer _timer;
late AnimationController _animationController;
#override
void initState() {
subtree = this.widget.child;
//Setting up Animation
_animationController = AnimationController(
vsync: this,
duration: Duration(seconds: 3),
upperBound: 255.0,
lowerBound: 0.0,
value: 255.0,
);
_animationController.reverse();
//Setting up Timer for Casaroul
_timer = Timer.periodic(Duration(seconds: 10), (timer) async {
_animationController.forward();
await Future.delayed(Duration(milliseconds: 3000)).then((_) {
setState(() {
_pos = (_pos + 1) % imageNames.length;
});
_animationController.reverse();
});
});
super.initState();
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return AnimatedBuilder(
animation: _animationController,
builder: (BuildContext context, _) {
return Container(
width: size.width,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("images/" + imageNames[_pos]),
alignment: Alignment.centerLeft,
fit: BoxFit.cover,
colorFilter: ColorFilter.mode(
Colors.black.withAlpha(_animationController.value.toInt()),
BlendMode.multiply,
),
),
),
child: subtree,
);
});
}
}
Here is some code,
If you try any 3 images, you will notice the flicker from
Img 1 -> Img 2
Img 2 -> Img 3
HOWEVER FROM,
Img 3 -> Img 1
and onwards, all the transitions will have no flicker.
I think the correct way for reach what you want is use the bloc pattern Bloc library
With this pattern you can separate the state from the ui and you can manage easily state everywhere in your app
So the technical term I was looking for was 'preloading an image'.
I found a post that does exactly what I wanting, it solved the flickering problem.
Here is the link:
Flutter Preloading

Widgets present outside the Consumer are also getting rebuilt while using ChangeNotifierProvider in Flutter

So I am just playing around the provider widgets. Below is my Entry main function.
void main() async {
runApp(
ChangeNotifierProvider<GameProvider>(
create: (_) => GameProvider(),
builder: (context,child) => MaterialApp(home: GameScene()),
)
);
}
As seen in the code above, I've added the provider widget above MaterialApp so that it can be available across whole app.
Below is my GameScene widget :
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:river_crossing_game/Providers/GameProvider.dart';
class GameScene extends StatefulWidget {
#override
_GameSceneState createState() => _GameSceneState();
}
class _GameSceneState extends State<GameScene> with SingleTickerProviderStateMixin{
double height, width;
bool pseudoInitState = true;
AnimationController _animationController;
Animation _animation;
GameProvider _provider;
#override
Widget build(BuildContext context) {
height = MediaQuery.of(context).size.height;
width = MediaQuery.of(context).size.width;
if(pseudoInitState){
_provider = Provider.of<GameProvider>(context); /// Added this just to use the methods present inside the provider class
_animationController = AnimationController(vsync: this,duration: Duration(seconds: 5));
_animation = Tween<double>(begin: 0,end: 60 * 5.0).animate(_animationController);
_provider.initPlankPos(Offset(0,height * 0.4)); /// Ignore this method
_animation.addListener(() {
_provider.translatePlank(_animation.value, Offset(1,0)); /// This one is just updating the position of the widget inside the Consumer
});
_animationController.forward();
pseudoInitState = false;
}
return Material(
child: Stack(
children: [
Container(height: height, width: width, color: Colors.lightBlue,),
Align(
alignment: Alignment.bottomCenter,
child: Builder(builder: (context) {print("Background Build ${Random.secure().nextDouble().toString()}");return Image.asset('assets/grass_sprite.png',height: 90,width: width,fit: BoxFit.cover);}),
),
Consumer<GameProvider>(
child: Image.asset('assets/wood_plank.png',height: width * 0.15,width: width * 0.25,fit: BoxFit.cover,),
builder: (context,value, child) {
return Transform.translate(
offset: Offset(value.plankPosX,value.plankPosY),
child: child,
);
}
)
],
),
);
}
}
As seen in the above code I've wrapped on Image widget with Consumer because it's the only widget that is going to be moved to a new position everytime the provider a new value and rest all the other widgets(grass sprite image and blue container above stack) will remain the same irrespective of the provider's updates.
To check if it's working just the way I said above, I wrapped the grass sprite image widget with a Builder (as seen in the above code) to check if it's getting rebuilt even if it is outside the Consumer widget and sadly it is getting rebuilt on every update.
Till now I thought that only the widgets inside the builder function of Consumer will get rebuilt while new value is provided by the provider but here the widgets outside the Consumer are also getting rebuilt.
Am I not using the provider correctly or have I get the wrong information about working of providers ?
Thankyou in advance for the help.
_provider = Provider.of<GameProvider>(context);
Change this to this:
_provider = Provider.of<GameProvider>(context,listen: false );

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;