I'm trying to animate a container in a FutureBuilder widget which makes a request to a node js api on localhost. There seems to have no issues with the API, but I've noticed that the Container renders normally when I don't call controller.repeat(). i'm guessing that controller.repeat() doesn't work well asynchronously, but I haven't found any alternatives.
This is the code I've got so far:
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> animation;
void initState() {
super.initState();
controller = AnimationController(
duration: const Duration(milliseconds: 3000), vsync: this);
animation = Tween<double>(begin: 10, end: 300).animate(controller);
animation.addListener(() {
setState(() {});
});
}
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final url = Uri.parse("http://localhost:8080");
return FutureBuilder(
future: http.get(url),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
final themeList =
Map<String, dynamic>.from(jsonDecode(snapshot.data.body));
final keys = List.from(themeList.keys);
controller.repeat(); // Container shows when I comment this line out
return Center(
child: Container(
color: Colors.red,
width: animation.value,
height: animation.value,
),
);
} else {
return Center(child: CircularProgressIndicator());
}
});
}
}
Please separate your animated widget. In your code, every setState(...) call will re-request the http.get(...).
Do something like this...
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: const Scaffold(
body: Center(
child: Home(),
),
),
);
}
}
class Home extends StatelessWidget {
const Home();
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: Future<void>.delayed(const Duration(seconds: 2), () {}),
builder: (context, snapshot) =>
snapshot.connectionState == ConnectionState.done
? const Center(child: AnimatedContainer())
: const Center(child: CircularProgressIndicator()),
);
}
}
class AnimatedContainer extends StatefulWidget {
const AnimatedContainer();
#override
_AnimatedContainer createState() => _AnimatedContainer();
}
class _AnimatedContainer extends State<AnimatedContainer>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<double> _animation;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 3000),
vsync: this,
);
_animation = Tween<double>(begin: 10, end: 300).animate(_controller);
_animation.addListener(() {
setState(() {});
});
// _controller.repeat();
_controller.forward();
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
width: _animation.value,
height: _animation.value,
);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
}
Related
I want to show skeleton widget while the data still loading so I used if-else in FutureBuilder widget.
Here the skeleton code
class Skeleton extends StatefulWidget {
final double height;
final double width;
Skeleton({Key key, this.height = 20, this.width = 200 }) : super(key: key);
createState() => SkeletonState();
}
class SkeletonState extends State<Skeleton> with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation gradientPosition;
#override
void initState() {
super.initState();
_controller = AnimationController(duration: Duration(milliseconds: 1500), vsync: this);
gradientPosition = Tween<double>(
begin: -3,
end: 10,
).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.linear
),
)..addListener(() {
setState(() {});
});
_controller.repeat();
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
#override
Widget build(BuildContext context) {
return Container(
width: widget.width,
height: widget.height,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment(gradientPosition.value, 0),
end: Alignment(-1, 0),
colors: [Colors.black12, Colors.black26, Colors.black12]
)
),
);
}
}
and here I tried to use the skeleton widget
FutureBuilder(
future: CategoryService.list(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
print(snapshot.error);
return snapshot.hasData
? SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: CategoryList(
categories: snapshot.data,
),
)
: snapshot.hasError
? Text(
snapshot.error.toString(),
)
: Skeleton(
height: 200,
width: 200,
);
},
),
Then I got this Error
Exception caught by animation library
'package:flutter/src/widgets/framework.dart': Failed assertion: line
4182 pos 12: '_debugLifecycleState != _ElementLifecycle.defunct': is
not true.
════════════════════════════════════════════════════════════════════════════════
Another exception was thrown:
'package:flutter/src/widgets/framework.dart': Failed assertion: line
4182 pos 12: '_debugLifecycleState != _ElementLifecycle.defunct': is
not true.
I got this Error but the App still running without problem but this Error shows in Debug console and always repeat the Error all time while the screen is running.
I did reload and I stop it and re run it but that was useless, I think the problem in the dispose of Skeleton widget.
You can copy paste run full code below
You can move _controller.dispose(); before super.dispose();
code snippet
#override
void dispose() {
_controller.dispose();
super.dispose();
}
working demo
full code
import 'package:flutter/material.dart';
class Skeleton extends StatefulWidget {
final double height;
final double width;
Skeleton({Key key, this.height = 20, this.width = 200}) : super(key: key);
createState() => SkeletonState();
}
class SkeletonState extends State<Skeleton>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation gradientPosition;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 1500), vsync: this);
gradientPosition = Tween<double>(
begin: -3,
end: 10,
).animate(
CurvedAnimation(parent: _controller, curve: Curves.linear),
)..addListener(() {
setState(() {});
});
_controller.repeat();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container(
width: widget.width,
height: widget.height,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment(gradientPosition.value, 0),
end: Alignment(-1, 0),
colors: [Colors.black12, Colors.black26, Colors.black12])),
);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class CategoryService {
static Future<String> list() async {
await Future.delayed(Duration(seconds: 5), () {});
return Future.value("123");
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: FutureBuilder(
future: CategoryService.list(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
print(snapshot.error);
return snapshot.hasData
? SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Text(
snapshot.data,
),
)
: snapshot.hasError
? Text(
snapshot.error.toString(),
)
: Skeleton(
height: 200,
width: 200,
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
I'm working on a custom animation button. I want to repeat the animation every time the user taps on it. So when the user clicks on it, the container scales bigger. And returns to the normal size. And when the user clicks on it again it does it again. Right now the animation just scales up to the defined sized and stops. It doesn't do anything after that.
class CustomAnimation extends StatefulWidget {
#override
_CustomAnimationState createState() => _CustomAnimationState();
}
class _CustomAnimationState extends State<CustomAnimation> with SingleTickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
// TODO: implement initState
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
);
_controller.addListener(() {
setState(() {
//do something
});
});
_controller.forward();
super.initState();
}
#override
void dispose() {
// TODO: implement dispose
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: AnimatedBuilder(
animation: _controller.view,
builder: (context,child){
return Transform.scale(scale: _controller.value *.9,
child: Container(
width: 200,
height: 200,
color: Colors.lightGreen[200],
child: Center(
child: Text('Animation test'),
),
),
);
},
),
)
);
}
}
You can copy paste run full code below
You can listen AnimationStatus.completed and call _controller.reverse()
And use InkWell call _controller.forward();
animation = Tween<double>(begin: 1.0, end: 1.2).animate(_controller)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reverse();
}
});
...
return Transform.scale(
scale: animation.value,
child: InkWell(
onTap: () {
_controller.forward();
},
working demo
full code
import 'package:flutter/material.dart';
class CustomAnimation extends StatefulWidget {
#override
_CustomAnimationState createState() => _CustomAnimationState();
}
class _CustomAnimationState extends State<CustomAnimation>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<double> animation;
#override
void initState() {
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 2));
_controller.addListener(() {
setState(() {
//do something
});
});
_controller.forward();
animation = Tween<double>(begin: 1.0, end: 1.2).animate(_controller)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reverse();
}
});
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Transform.scale(
scale: animation.value,
child: InkWell(
onTap: () {
_controller.forward();
},
child: Container(
width: 200,
height: 200,
color: Colors.lightGreen[200],
child: Center(
child: Text('Animation test'),
),
),
),
);
},
),
));
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: CustomAnimation(),
);
}
}
I have a screen that creates a widget.
How can I add a bloc to my widget?
class UserView extends StatelessWidget {
final AnimationController aController;
final Animation animation;
#override
Widget build(BuildContext context) {
// Add Scafold here?
return AnimationBuilder(
animation: aController;
builder: (BuildContext context, Widget child) {
...
},
);
}
}
The bloc
class UserBloc extends Bloc<UserEvent, UserState> {
final UserRepo userRepo;
UserBloc({#required this.userRepo}) : assert(userRepo != null);
}
If I add a Scaffold() then I get an error saying "object was given an infinite size during layout".
I am using this https://bloclibrary.dev/#/ for bloc.
I can show more code if necessary, I am trying to keep it light for easy reading. Please ask and I can add more.
App
void main() async {
final UserRepo userRepo = UserRepo();
BlocSupervisor.delegate = SimpleBlocDelegate();
WidgetsFlutterBinding.ensureInitialized();
await SystemChrome.setPreferredOrientations(<DeviceOrientation>[
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown
]).then((_) => runApp(MultiBlocProvider(
providers: [
BlocProvider<UserBloc>(
create: (context) => UserBloc(userRepo: userRepo),
)
],
child: MyApp(),
)));
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test App',
theme: ThemeData(
primarySwatch: Colors.blue,
textTheme: AppTheme.textTheme,
platform: TargetPlatform.iOS,
),
home: HomeScreen(),
);
}
}
Home Screen
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen>
with TickerProviderStateMixin {
AnimationController animationController;
Widget tabBody = Container(
color: AppTheme.background,
);
#override
void initState() {
animationController = AnimationController(
duration: const Duration(milliseconds: 800), vsync: this);
tabBody = DashboardScreen(animationController: animationController);
super.initState();
}
#override
void dispose() {
animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container(
color: AppTheme.background,
child: Scaffold(
backgroundColor: Colors.transparent,
body: FutureBuilder<bool>(
future: getData(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (!snapshot.hasData) {
return const SizedBox();
} else {
return Stack(
children: <Widget>[
tabBody
],
);
}
},
),
),
);
}
}
Dashboard
class DashboardScreen extends StatefulWidget {
const DashboardScreen({Key key, this.animationController}) : super(key: key);
final AnimationController animationController;
#override
_DashboardScreenState createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen>
with TickerProviderStateMixin {
Animation<double> topBarAnimation;
List<Widget> listViews = <Widget>[];
final ScrollController scrollController = ScrollController();
double topBarOpacity = 0.0;
#override
void initState() {
listViews.add(
UserView(
animation: Tween<double>(begin: 0.0, end: 1.0).animate(CurvedAnimation(
parent: widget.animationController,
curve:
Interval((1 / count) * 1, 1.0, curve: Curves.fastOutSlowIn))),
animationController: widget.animationController,
),
);
super.initState();
}
}
I will suppose that UserBloc must be available to the whole app, if not, just change the level of the provider below to be just above the widgets it should cover:
Here you provide the bloc to be above MaterialApp widget to be order to use it later in any descendant of this widget:(inside App file)
return BlocProvider(
create: (_)=>UserBloc(userRepo:UserRep()),
child: MaterialApp(
title: 'Test App',
theme: ThemeData(
primarySwatch: Colors.blue,
textTheme: AppTheme.textTheme,
platform: TargetPlatform.iOS,
),
home: HomeScreen(),
),
);
now if you want to use your bloc to emit events and listen to states in any descendant widget of MaterialApp, you just wrap that widget with a BlocListener or BlocConsumer or BlocBuilder (see difference between them here):
I will suppose you want to do that in HomeScreen:
class _HomeScreenState extends State<HomeScreen>
with TickerProviderStateMixin {
AnimationController animationController;
Widget tabBody = Container(
color: AppTheme.background,
);
#override
void initState() {
animationController = AnimationController(
duration: const Duration(milliseconds: 800), vsync: this);
tabBody = DashboardScreen(animationController: animationController);
super.initState();
}
#override
void dispose() {
animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container(
color: AppTheme.background,
child: Scaffold(
backgroundColor: Colors.transparent,
body: FutureBuilder<bool>(
future: getData(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (!snapshot.hasData) {
return const SizedBox();
} else {
return Stack(
children: <Widget>[
//tabBody
//Don't save widgets as fields, just create them on the fly
BlocBuilder<UserBloc,UserState>(
builder: (ctx,state){
//return widget that depends on state and which should rebuild when state changes
return DashboardScreen(animationController: animationController);
},
)
],
);
}
},
),
),
);
}
}
and that's it.
Check the link above for more documentation.
I am currently working on a hit/damage animation in Flutter. I want that each time the screen is tapped, it throws an animation showing an integer. I could not find a way to make it work. For now each time I tap the screen, the animation starts over stopping the previous one. I use the BLoC pattern inside the project so this animation is thrown by a streambuilder.
Here is my current code:
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final StreamController<int> streamController = StreamController<int>();
#override
Widget build(BuildContext context) {
return MaterialApp(
home: GestureDetector(
onTap: () {
streamController.sink.add(Random().nextInt(5));
},
child: Scaffold(
body: Center(
child: StreamBuilder<int>(
stream: streamController.stream,
builder: (context, snapshot) {
if (snapshot.hasData) {
return DamageAnimated(snapshot.data);
}
return Container();
},
),
),
),
),
);
}
}
class DamageAnimated extends StatefulWidget {
const DamageAnimated(this.damage);
final int damage;
#override
_DamageAnimatedState createState() => _DamageAnimatedState();
}
class _DamageAnimatedState extends State<DamageAnimated> with SingleTickerProviderStateMixin {
AnimationController animationController;
#override
void initState() {
super.initState();
animationController = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
);
}
#override
void dispose() {
animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
animationController.forward(from: 0.0);
return AnimatedBuilder(
animation: animationController,
builder: (BuildContext context, Widget child) {
return Transform.translate(
offset: Offset(0, -100 * animationController.value),
child: Opacity(
opacity: 1 - animationController.value,
child: Text(
'${widget.damage}',
),
),j
);
},
);
}
}
This displays an integer translating upward and fading away at same time but I can't figure out how to have the same animation running multiple time concurrently.
You're only ever returning 1 DamageAnimated. Look at the example below - you'll need to implement something similar. Btw, the ... syntax to expand a list is new in dart, so you need to update sdk version in pubspec.yaml
environment:
sdk: ">=2.2.2 <3.0.0"
I've tested this on iOS, not Android, but no reason it shouldn't work.
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(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Widget> _anims = [];
int _count = 0;
int _animationsRunning = 0;
void animationEnded() {
_animationsRunning--;
if (_animationsRunning == 0) {
setState(() {
_anims = [];
});
print('all animations completed - removing widget from stack (now has ${_anims.length} elements)');
}
}
void _startAnimation() {
setState(() {
_anims.add(DamageAnimated(_count, animationEnded));
_count++;
_animationsRunning++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Stack(
children: <Widget>[
..._anims,
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _startAnimation,
tooltip: 'Start animation',
child: Icon(Icons.add),
),
);
}
}
class DamageAnimated extends StatefulWidget {
const DamageAnimated(this.damage, this.endedCallback);
final int damage;
final VoidCallback endedCallback;
#override
_DamageAnimatedState createState() => _DamageAnimatedState();
}
class _DamageAnimatedState extends State<DamageAnimated> with SingleTickerProviderStateMixin {
AnimationController animationController;
#override
void initState() {
super.initState();
animationController = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
)..addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.completed)
if (mounted) {
widget.endedCallback();
}
});
}
#override
void dispose() {
animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
animationController.forward(from: 0.0);
return AnimatedBuilder(
animation: animationController,
builder: (BuildContext context, Widget child) {
return Transform.translate(
offset: Offset(0, -100 * animationController.value),
child: Opacity(
opacity: 1 - animationController.value,
child: Text(
'${widget.damage}',
),
),
);
},
);
}
}
Maybe you could try adding StatusListeners to the animation or you could, instead, reset and start the animation each time you tap the screen such as:
onTap: () {
streamController.sink.add(Random().nextInt(5));
animationController.reset();
animationController.forward(from: 0.0);
},
I couldn't try this on my own at the moment, but I think it's a way to do it.
Sorry for waisting your time if it doesn't work.
I am trying to generate a list in flutter which delays every item after some delay.
I was tried using FutureBuilder and AnimatedList but i failed to get it.
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: Example(),
);
}
}
class Example extends StatefulWidget {
#override
_ExampleState createState() => new _ExampleState();
}
class _ExampleState extends State<Example> with TickerProviderStateMixin{
AnimationController listViewController;
final Animation listView;
Duration duration = new Duration(seconds: 3);
Timer _timer;
#override
void initState() {
listViewController = new AnimationController(
duration: new Duration(seconds: 2),
vsync: this
);
super.initState();
}
FlutterLogoStyle _logoStyle = FlutterLogoStyle.markOnly;
item() {
_timer = new Timer(const Duration(seconds: 1), () {
return Text("cdscs");
});
}
#override
void dispose() {
listViewController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("hello"),
),
// ListView Builder
body: AnimatedList(
initialItemCount: 10,
itemBuilder: (BuildContext context,
int index,
Animation<double> animation){
return item();
},
)
);
}
}
You could use the AnimationController and an Animation for every child like this example:
class Example extends StatefulWidget {
#override
_ExampleState createState() => new _ExampleState();
}
class _ExampleState extends State<Example> with TickerProviderStateMixin {
AnimationController _animationController;
double animationDuration = 0.0;
int totalItems = 10;
#override
void initState() {
super.initState();
final int totalDuration = 4000;
_animationController = AnimationController(
vsync: this, duration: new Duration(milliseconds: totalDuration));
animationDuration = totalDuration/(100*(totalDuration/totalItems));
_animationController.forward();
}
FlutterLogoStyle _logoStyle = FlutterLogoStyle.markOnly;
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("hello"),
),
// ListView Builder
body: ListView.builder(
itemCount: totalItems,
itemBuilder: (BuildContext context, int index) {
return new Item(index: index, animationController: _animationController, duration: animationDuration);
},
));
}
}
class Item extends StatefulWidget {
final int index;
final AnimationController animationController;
final double duration;
Item({this.index, this.animationController, this.duration});
#override
_ItemState createState() => _ItemState();
}
class _ItemState extends State<Item> {
Animation _animation;
double start;
double end;
#override
void initState() {
super.initState();
start = (widget.duration * widget.index ).toDouble();
end = start + widget.duration;
print("START $start , end $end");
_animation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(
CurvedAnimation(
parent: widget.animationController,
curve: Interval(
start,
end,
curve: Curves.easeIn,
),
),
)..addListener((){
setState(() {
});
});
}
#override
Widget build(BuildContext context) {
return Opacity(
opacity: _animation.value,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: new Text("New Sample Item ${widget.index}"),
),
);
}
}
You can change the animation, in this case I was using opacity to simulate fade animation.
Maybe you can Check this response: https://stackoverflow.com/a/59121771/9481448
for (var i = 0; i < fetchedList.length; i++) {
future = future.then((_) {
return Future.delayed(Duration(milliseconds: 100), () {
// add/remove item
});
});