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.
Related
I want to keep child widget state using GlobalKey after parent's state is changed. There is a workaround by using Opacity in order to solve the problem, but I wonder why GlobalKey doesn't work as expected in this scenario.
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Retrieve Text Input',
home: MainScreen(),
);
}
}
class MainScreen extends StatefulWidget {
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
final _key = GlobalKey();
bool _showTimer = true;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Title'),
centerTitle: false,
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextButton(
onPressed: () => setState(() {
_showTimer = !_showTimer;
}),
child: Text('show/hide')),
_showTimer ? TimerWidget(key: _key) : Container()
],
),
));
}
}
class TimerWidget extends StatefulWidget {
const TimerWidget({Key key}) : super(key: key);
#override
_TimerWidgetState createState() => _TimerWidgetState();
}
const int TIME_REMINDING_SECONDS = 480;
class _TimerWidgetState extends State<TimerWidget> {
Timer _timer;
int _start = TIME_REMINDING_SECONDS;
#override
Widget build(BuildContext context) {
return Text(
'${(_start ~/ 60).toString().padLeft(2, '0')}:${(_start % 60).toString().padLeft(2, '0')}',
style: TextStyle(
color: _start > 10 ? Colors.amber : Colors.red, fontSize: 20));
}
#override
initState() {
super.initState();
_startTimer();
}
#override
void dispose() {
_timer.cancel();
super.dispose();
}
_startTimer() {
const oneSec = const Duration(seconds: 1);
_timer = new Timer.periodic(
oneSec,
(Timer timer) => setState(
() {
if (_start < 1) {
timer.cancel();
} else {
_start = _start - 1;
}
},
),
);
}
}
You will see the timer restarts to initial value every times the parent's state is changed. I tried with the solutions here but didn't work.
as an option you can skip GlobalKey and simple use Offstage widget
Offstage(offstage: !_showTimer, child: TimerWidget()),
another answer mentioned Visibility with maintainState parameter.
This is pointless because it uses Offstage under the hood.
By Every time in the previous code every time the state changes it creates a new instance of timer so GlobalKey won't take effect there since its new instance.
Global keys uniquely identify elements. Global keys provide access to
other objects that are associated with those elements, such as
BuildContext. For StatefulWidgets, global keys also provide access to
State.
https://api.flutter.dev/flutter/widgets/GlobalKey-class.html
By the Above statement, the global key is used to access the state within the widgget.
So in your case when TimerWidget() switches it's disposed of its state and not gonna preserve that's why its timer getting reset every time you change state.
--- Update ---
Instead of _showTimer ? TimerWidget(key: _key) : Container()
Use below code:
Visibility(
visible: _showTimer,
maintainState: true,
child: page
)
Here, maintain state is keeping the state of the widget.
Update
The following code moves the scope of a globally unique key so that it will maintain its state while the app lives. When adding this key to an Offset widget, you can show/hide the timer while retaining its state. Without this step, the timer widget would continue to reset as the timer widget is removed and re-added to the rendering tree. I also added the late modifier to the state class _timer variable.
Removing the timer widget from the tree will normally call the dispose method; so one alternative is to use Offstage which is designed to temporarily remove widgets based on state. This seems to be precisely what you are attempting to do. However, the Visibility widget does this same behavior without having to maintain a Global Key (but your focus seemed to be on wanting to leverage a key). Note the other widgets discussed in Visibility notes may provide other alternatives.
Some important considerations:
Animations continue to run when using Offstage widget.
From the docs (on the Offstage widget):
A widget that lays the child out as if it was in the tree, but without
painting anything, without making the child available for hit testing,
and without taking any room in the parent.
Offstage children are still active: they can receive focus and have
keyboard input directed to them.
Animations continue to run in offstage children, and therefore use
battery and CPU time, regardless of whether the animations end up
being visible.
Offstage can be used to measure the dimensions of a widget without
bringing it on screen (yet). To hide a widget from view while it is
not needed, prefer removing the widget from the tree entirely rather
than keeping it alive in an Offstage subtree.
From the docs (on the Visibility widget):
By default, the visible property controls whether the child is
included in the subtree or not; when it is not visible, the
replacement child (typically a zero-sized box) is included instead.
A variety of flags can be used to tweak exactly how the child is
hidden. (Changing the flags dynamically is discouraged, as it can
cause the child subtree to be rebuilt, with any state in the subtree
being discarded. Typically, only the visible flag is changed
dynamically.)
These widgets provide some of the facets of this one:
Opacity, which can stop its child from being painted. Offstage, which can stop its child from being laid out or painted.
TickerMode, which can stop its child from being animated. ExcludeSemantics, which can hide the child from accessibility tools. IgnorePointer, which can disable touch interactions with
the child. Using this widget is not necessary to hide children. The
simplest way to hide a child is just to not include it, or, if a
child must be given (e.g. because the parent is a StatelessWidget)
then to use SizedBox.shrink instead of the child that would
otherwise be included.
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
//create a key that will persist in app scope
var timerKey = GlobalKey();
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Retrieve Text Input',
home: MainScreen(),
);
}
}
class MainScreen extends StatefulWidget {
const MainScreen({Key? key}) : super(key: key);
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
bool _showTimer = true;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Title'),
centerTitle: false,
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextButton(
onPressed: () => {
setState(() {
_showTimer = !_showTimer;
})
},
child: Text('show/hide')),
//reuse the current timer logic to show/hide the time
Offstage(
offstage: _showTimer,
child: TimerWidget(
key: (timerKey),
),
)
],
),
));
}
}
class TimerWidget extends StatefulWidget {
const TimerWidget({Key? key}) : super(key: key);
#override
_TimerWidgetState createState() => _TimerWidgetState();
}
const int TIME_REMINDING_SECONDS = 480;
class _TimerWidgetState extends State<TimerWidget> {
late Timer _timer;
int _start = TIME_REMINDING_SECONDS;
#override
Widget build(BuildContext context) {
return Text(
'${(_start ~/ 60).toString().padLeft(2, '0')}:${(_start % 60).toString().padLeft(2, '0')}',
style: TextStyle(
color: _start > 10 ? Colors.amber : Colors.red, fontSize: 20));
}
#override
initState() {
super.initState();
_startTimer();
}
#override
void dispose() {
_timer.cancel();
super.dispose();
}
_startTimer() {
const oneSec = const Duration(seconds: 1);
_timer = new Timer.periodic(
oneSec,
(Timer timer) => setState(
() {
if (_start < 1) {
timer.cancel();
} else {
_start = _start - 1;
}
},
),
);
}
}
Nota Bene
Visibility does not require a key at all.
Visibility(
visible: _showTimer,
maintainState: true,
child: TimerWidget(),
),
Original
Review my related question here. You will want to ensure that a Unique Key is available to the parent widget before you start to use the child. My example is pretty in-depth; let me know if you have follow-up issues.
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
I have tackeled without any success with problem...
My main page is stateful class with tabbar with two tabs. First tab has some text from global variables and couple of textfields that also are prefilled with global variables.
Second tab has a button and ontap it calls setstate that changes variables, that are used on first tab and then animates to first tab.
My problem is that first tabs text doesnt change to new value. At the same time textfields will have new values. If i add print command before returning text on first tab, code will print out new values, but state for text is not set, at the same time textfields state will be set.
Its not possible at moment to add code, but i hope i described mu problem good enough.
Thank You!
I tryed many things and now i got strange working solution that makes what i want.
If i just set new variables and after that let tabcontroller to animate dirst page, pages state will not be set, but if i add small delay, then it works like i want. If anyone could explain why, i would be really thankful.
onPressed: () {
setProduct();
Timer(Duration(milliseconds: 100), animateToFirstPage);
}
There is a really elaborate explanation in this answer.
Bottom line, there is a race condition between setState and animateTo, and he suggests breaking it so:
onPressed: () {
setProduct();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
animateToFirstPage;
})
}
Verified it worked for me, and without creepy .sleep solutions
Use a simple state management solution. Where both tabs can listen and modify the values you want. Without code is hard to demonstrate. But you can't simply change the state of a widget from another widget, using provider would be easier.
To update and listen to the change, use StreamController and StreamBuilder. You can put the first tab in a widget combined with SingleTickerProviderStateMixin to prevent it from reloading as well. I created a simple app for demonstration:
Full example:
main.dart
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(home: MyApp()));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
final StreamController _streamController = StreamController<String>();
TabController _tabController;
#override
void initState() {
_tabController = TabController(length: 2, vsync: this);
super.initState();
}
#override
void dispose() {
_streamController.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Sample App'),
),
bottomNavigationBar: TabBar(
controller: _tabController,
labelColor: Colors.red,
tabs: [Tab(text: 'Tab 1'), Tab(text: 'Tab 2')],
),
body: TabBarView(
controller: _tabController,
children: [
FirstTab(_streamController),
Container(
child: Center(
child: TextButton(
child: Text('Press me'),
onPressed: () {
final _someText =
'Random number: ' + Random().nextInt(100).toString();
_streamController.add(_someText);
_tabController.animateTo(0);
},
),
),
),
],
),
);
}
}
class FirstTab extends StatefulWidget {
final StreamController _streamController;
FirstTab(this._streamController);
#override
_FirstTabState createState() => _FirstTabState();
}
class _FirstTabState extends State<FirstTab>
with AutomaticKeepAliveClientMixin {
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
return Container(
child: Center(
child: StreamBuilder<String>(
initialData: 'Empty text',
stream: widget._streamController.stream,
builder: (context, snapshot) {
return Text(snapshot.data);
}),
),
);
}
}
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 have a ToggleButtons Widget in my application and I want to switch between two different StatefulWidget classes (both of which are fairly complicated) when a user clicks on the toggle. I have tried using an animatedCrossFade however I was not able to switch between the two screens.
AnimatedCrossFade(
duration: const Duration(seconds: 3),
firstChild: AppointmentMain(),
secondChild: RequestPage(),
crossFadeState:
_first ? CrossFadeState.showFirst : CrossFadeState.showSecond,
)
Try using AnimatedSwitcher to switch between two different Widget.
AnimatedSwitcher(
duration: Duration(seconds: 3),
child: _first? AppointmentMain() : RequestPage(),
)
Have One Main Widget that uses a button to load the separate widgets.
void main() {
runApp(MaterialApp(
home: myApp(),
));
class myApp extends StatefulWidget {
#override
_myAppState createState() => _myApp();
}
class _myAppState extends State<Home> {
int counter = 1;
return Scaffold(
body: RaisedButton(
on pressed loadFirstWidget
),
);
}
Widget loadWidget() {
setState() {
counter += 1;
}
if (counter % 2 == 0) {
return firstWidget();
}
return secondWidget();
}
Hopefully this helps!