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
Related
I would like to create a widget with an animation composed of showing a sequence of images fading in and out with a repeat after complete. Something like this attached animation:
Since I'm new to Flutter, I would like to know what is the best approach to this.
This can be done with AnimatedSwitcher widget. It's one of Flutter's easy-to-use implicit animation widgets. Its main job is to automatically create a cross-fade transition when its child widget changes.
You can see it in action by changing the string below, and do a hot reload. You will see a cross fade transition for 200 ms:
AnimatedSwitcher(
duration: Duration(milliseconds: 200),
child: Text(
'Hello', // manually change the text here, and hot reload
key: UniqueKey(),
),
)
Once you understand how AnimatedSwitcher works, you can decide how to loop through the list images. For simplicity, I'm giving you an example using texts, but the idea is the same.
Full source code:
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late final Timer timer;
final values = ['A', 'B', 'C', 'D'];
int _index = 0;
#override
void initState() {
super.initState();
timer = Timer.periodic(Duration(seconds: 1), (timer) {
setState(() => _index++);
});
}
#override
void dispose() {
timer.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Demo'),
),
body: Center(
child: AnimatedSwitcher(
duration: Duration(milliseconds: 200),
child: Text(
values[_index % values.length],
key: UniqueKey(),
),
),
),
);
}
}
You can do this with AnimationController. After setup animation controller, you just call the repeat function. The animation will be going to a limitless loop. With AnimationStatusListener you can change color and title text.
I don't believe I am asking such a simple question.
I try to change opacity in initState() but it does not have an effect.
How do I trigger setState if I don't use a button?
Here is the DARTPAD.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
/// This is the main application widget.
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: LogoFade(),
);
}
}
class LogoFade extends StatefulWidget {
#override
createState() => LogoFadeState();
}
class LogoFadeState extends State<LogoFade> {
double opacityLevel = 0.0;
void changeOpacity() {
setState(() => opacityLevel = 1.0);
}
#override
void initState() {
changeOpacity();
super.initState();
}
#override
Widget build(BuildContext context) {
// changeOpacity();
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedOpacity(
opacity: opacityLevel,
duration: Duration(seconds: 6),
child: FlutterLogo(
size: 200,
),
),
],
);
}
}
I just want a simple FadeIn effect like Animate.css library. Like we make in web with the example below.
.fade-in-image {
animation: fadeIn 6s;
}
#keyframes fadeIn {
0% {opacity:0;}
100% {opacity:1;}
}
<div class="fade-in-image">
<img src="https://www.innodeed.com/wp-content/uploads/2017/11/flutter-logo.png">
</div>
In flutter app life cycle intiState() called before the Widget build(BuildContext context) called.So technically your opacity varriable always initialize with 1.You can use FutureDelay to see the effect of this animation.
void changeOpacity() async{
await Future.delayed(const Duration(milliseconds: 2000));
setState(() => opacityLevel = 1.0);
}
#override
void initState() {
changeOpacity();
super.initState();
}
Part of my Flutter app has a filter function which filters through a list of items. This being part of a UX should indicate the current operation to the user.
The issue I am facing is displaying the current item being filtered (evaluated) - the images simply won't be displayed or will "get stuck" on 1 - reason most likely being the run loop set the state too fast for the redraw to take place.
Here is a rough example of what I am trying to accomplish
import 'dart:async';
import 'package:flutter/material.dart';
void main() async {
runApp(MaterialApp(home: MyApp()));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int i = 0;
List<String> _images = [
"assets/sample/sample1.jpg",
"assets/sample/sample2.jpg",
"assets/sample/sample3.jpg",
"assets/sample/sample4.jpg",
];
#override
void initState() {
super.initState();
Timer(Duration(), () {
while (true) {
i++;
setState(() {
i = i % 4;
});
}
});
}
#override
Widget build(BuildContext context) {
Widget content = Container(
child: ClipRRect(
clipBehavior: Clip.antiAlias,
child: CircleAvatar(
radius: 24,
backgroundColor: Colors.transparent,
child: Image.asset(_images[i], gaplessPlayback: true,),
),
));
return MaterialApp(
home: Scaffold(
body: content,
),
);
}
Question:
To simplify things, how can I display a few images rapidly (and repeatedly) i.e. 1, 2, 3, 4, 1, 2, 3... with a few milliseconds delay between (at most)?
Don't use while loop because it will block the thread and does not let other tasks be executed
you could use something like Timer.periodic and it gives you the option to control the frame rate that images should change
and also don't use setState because it causes the entire widget to rebuilt you can use ValueNotifer to notify the specific widget direct with changes
and also remember to cancel the timer when your widget gets disposed
here is example
class _PageAState extends State<PageA> {
ValueNotifier<String> currentImage = ValueNotifier("0.png");
int counter=0;
Timer imageTimer;
#override
void initState() {
super.initState();
this.imageTimer=Timer.periodic(Duration(milliseconds: 100), (timer) {
counter++;
currentImage.value="${counter%3}.png";
});
}
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<String>(
valueListenable: currentImage,
builder: (context, value, child) {
return Image.asset(
value,
width: 30,
height: 30,
color: Colors.red,
);
},
);
}
#override
void dispose() {
super.dispose();
if(imageTimer!=null)imageTimer.cancel();
}
}
I am a JavaScript developer and I am new to Flutter. I just want to animate a set of images on mouse hover like this using Flutter for Web. It includes Scaling, Opacity and Grayscale transformations. How to accomplish this in Flutter?
Thanks in advance.
Other than the animation part of your question. The onHover argument of the InkWell only works if you specify the onTap argument first.
InkWell(
child: SomeWidget(),
onTap: () {
//You can leave it empty, like that.
}
onHover: (isHovering) {
if (isHovering) {
//The mouse is hovering.
} else {
//The mouse is no longer hovering.
}
}
)
From the documentation, here's the benefit of the boolean, which is passed to the onHover callback:
The value passed to the callback is true if a pointer has entered this part of
the material and false if a pointer has exited this part of the material.
This is just a demo to show that you can use onHover of Inkwell widget to accomplish the task. You will have to come up with the logic to decide how much offset and scale should be used and how to position the widget. In my example I have used a grid view. You can perhaps use a stack to set the currently active widget based on the hover.
Here is the example with a grid view. The live version of this is available in this dartpad.
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GridView.count(
crossAxisCount: 3,
children: <Widget>[ImageHover(),ImageHover(),ImageHover(),ImageHover(),ImageHover(),ImageHover(),ImageHover(),],
);
}
}
class ImageHover extends StatefulWidget {
#override
_ImageHoverState createState() => _ImageHoverState();
}
class _ImageHoverState extends State<ImageHover> {
double elevation = 4.0;
double scale = 1.0;
Offset translate = Offset(0,0);
#override
Widget build(context) {
return InkWell(
onTap: (){},
onHover: (value){
print(value);
if(value){
setState((){
elevation = 20.0;
scale = 2.0;
translate = Offset(20,20);
});
}else{
setState((){
elevation = 4.0;
scale = 1.0;
translate = Offset(0,0);
});
}
},
child: Transform.translate(
offset: translate ,
child: Transform.scale(
scale: scale,
child: Material(
elevation: elevation,
child: Image.network(
'https://i.ytimg.com/vi/acm9dCI5_dc/maxresdefault.jpg',
),
),
),
),
);
}
}
Just create an extension
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
extension HoverExtension on Widget {
Widget get translateOnHover {
return kIsWeb ? TranslateOnHover(child: this) : ThisContainer(child: this);
}
}
class ThisContainer extends StatelessWidget {
ThisContainer({this.child});
final child;
#override
Widget build(BuildContext context) {
return Container(child: child);
}
}
class TranslateOnHover extends StatefulWidget {
final Widget child;
TranslateOnHover({required this.child});
#override
_TranslateOnHoverState createState() => _TranslateOnHoverState();
}
class _TranslateOnHoverState extends State<TranslateOnHover> {
double scale = 1.0;
#override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (e) => _mouseEnter(true),
onExit: (e) => _mouseEnter(false),
child: TweenAnimationBuilder(
duration: const Duration(milliseconds: 200),
tween: Tween<double>(begin: 1.0, end: scale),
builder: (BuildContext context, double value, _) {
return Transform.scale(scale: value, child: widget.child);
},
),
);
}
void _mouseEnter(bool hover) {
setState(() {
if (hover)
scale = 1.03;
else
scale = 1.0;
});
}
}
And use it anywhere by calling
yourWidget.translateOnHover
It's sad that there's not a built-in feature already, given that Flutter extended to web also, but luckily there is a package for this: hovering 1.0.4
You just need to install the package running this command flutter pub add hovering or adding hovering: ^1.0.4 to your dependencies. Then you can use HoverWidget,HoverContainer, HoverAnimatedContainer, and some more. It's not perfect, but it's an easy way to do it, specially for not complicated animations.
You can check the official docs of the package for more info: https://pub.dev/packages/hovering
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.