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.
Related
I have a custom widget that changes color when tapped inside a gridview. When I scroll to the bottom and scroll back up to the top selected widget its animation is reversed.
I'm pretty sure that it has something to do with the widget being disposed of when out of view but I don't have a solution to overcome it. See my code below:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Thirty Seconds',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
// Page with the gridview
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: GridView.count(
crossAxisCount: 2,
children: List.generate(20, (index) {
return MyCustomWidget(
key: GlobalKey(),
index: index + 1,
);
}),
),
);
}
}
// Custom Widget
class MyCustomWidget extends StatefulWidget {
const MyCustomWidget({
super.key,
required this.index,
});
final int index;
#override
State<MyCustomWidget> createState() => _MyCustomWidgetState();
}
class _MyCustomWidgetState extends State<MyCustomWidget>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<Color?> _colorAnimation;
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
);
_colorAnimation = ColorTween(begin: Colors.white, end: Colors.yellow)
.animate(_animationController)
..addListener(() => setState(() {}));
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
void _toggleAnimation() {
if (_animationController.isCompleted) {
_animationController.reverse();
} else {
_animationController.forward();
}
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
_toggleAnimation();
},
child: Container(
color: _colorAnimation.value,
child: Center(
child: Text("Custom Widget ${widget.index}"),
),
),
);
}
}
GridView dispose the widget that aren't visible on UI. You can use cacheExtent(not suitable for this case) or AutomaticKeepAliveClientMixin on _MyCustomWidgetState.
class _MyCustomWidgetState extends State<MyCustomWidget>
with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin {
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
You may prefer handing it parent widget and passing a bool to check active state or state-management or project level depends on scenario.
How to navigate the main page after the Lottie animation completed in a flutter when I run the code it shows the Lottie animation repeatedly.
You can copy paste run full code below
You can use AnimationController and in Lottie onLoaded call AnimationController forward().whenComplete then you can do Navigator.push
code snippet
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
}
...
Lottie.asset(
'assets/LottieLogo1.json',
controller: _controller,
onLoaded: (composition) {
_controller
..duration = composition.duration
..forward().whenComplete(() => Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondPage()),
));
},
),
To run working demo, you need this file https://github.com/xvrh/lottie-flutter/blob/master/example/assets/LottieLogo1.json
working demo
full code
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: [
Lottie.asset(
'assets/LottieLogo1.json',
controller: _controller,
onLoaded: (composition) {
_controller
..duration = composition.duration
..forward().whenComplete(() => Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondPage()),
));
},
),
],
),
);
}
}
class SecondPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(body: Center(child: Text("Second Page")));
}
}
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();
}
}
Is there any way to Animate a Text widget which is placed in the center of screen to the Title of Appbar ?
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(child:Text('Animate Me!')),
);
}
How shall I approach to animate it in Flutter?
Edit :
I was imagining a Text fly and hover from Center of the screen to title of Appbar.
Yes, you can animate the Text widget within the AppBar of your app. Your approach to animating the Text widget should be pretty much like any other animation. You can apply almost all types of animations available in Flutter. Please the code below :
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
class MyHomePage extends StatefulWidget {
#override
State createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
AnimationController _controller;
//String text = "";
Future myFuture;
//Animation<int> _charInt;
Animation<Offset> position;
final String title = 'Flutter Demo Project';
Future myAnimation() async {
_controller = AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
);
setState(() {
// _charInt = StepTween(begin: 0, end: title.length)
// .animate(CurvedAnimation(parent: _controller, curve: Curves.easeIn));
position = Tween<Offset>(
begin: const Offset(0.0, 0.0),
end: const Offset(0.0, -9.0),
).animate(CurvedAnimation(curve: Curves.bounceOut, parent: _controller));
});
await _controller.forward();
}
#override
void initState() {
super.initState();
myFuture = myAnimation();
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
#override
Widget build(BuildContext context) {
// final AnimatedBuilder animatedBuilder = _charInt == null
// ? null
// : AnimatedBuilder(
// animation: _charInt,
// builder: (BuildContext context, Widget child) {
// final String text = title.substring(0, _charInt.value);
// return Text(text);
// },
// );
return Scaffold(
appBar: AppBar(title: Text(title)), //animatedBuilder),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
//animatedBuilder,
SlideTransition(
position: position,
child: Text(
title,
style: const TextStyle(fontSize: 32),
),
),
],
),
),
);
}
}
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.