How to start same animation multiple times concurrently - flutter

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.

Related

Custom Widget revising animation in gridview

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.

fade slide animation flutter

I saw many webs when scroll down they have slide fade animations on their widgetI am wondering how they work! Any example with fade slide animation will be appreciated
New to flutter wondering how to do where to start
Try this code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
/// This Widget is the main application widget.
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: _title,
home: FadeTransitionExample(),
);
}
}
class FadeTransitionExample extends StatefulWidget {
#override
State<StatefulWidget> createState() => _Fade();
}
class _Fade extends State<FadeTransitionExample> with TickerProviderStateMixin {
AnimationController? animationController;
Animation<double>? _animationValue;
#override
void initState() {
super.initState();
animationController = AnimationController(vsync: this, duration: const Duration(seconds: 2),);
_animationValue = Tween<double>(begin: 0.0, end: 0.5).animate(animationController!);
animationController!.addStatusListener((status){
if(status == AnimationStatus.completed){
animationController!.reverse();
}
else if(status == AnimationStatus.dismissed){
animationController!.forward();
}
});
animationController!.forward();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(
child: FadeTransition(
opacity: _animationValue!,
child: Container(
color: Colors.blue,
width: 150,
height: 150,
),
),
),
),
);
}
}

Animate Text widget to Appbar

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),
),
),
],
),
),
);
}
}

Unable to 'forward ' the flutter animation

I have this code which uses an AnimationController and Tween to rotate a container along Z-axis when Start/Stop Button is pressed. The problem is it forwards the animation only first time after that no matter how many times I press the button it does nothing and the container remains stationary .
curvesanimation.dart which has all the animation logic
import 'package:flutter/material.dart';
import 'dart:math' as math;
class CurvedAnimationExample extends StatefulWidget {
CurvedAnimationExample({Key key}) : super(key: key);
#override
CurvedAnimationExampleState createState() => CurvedAnimationExampleState();
}
class CurvedAnimationExampleState extends State<CurvedAnimationExample>
with SingleTickerProviderStateMixin {
Animation<double> _animation;
AnimationController _animationController;
#override
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: Duration(seconds: 5))
..addListener(() {
setState(() {});
});
final Animation curve =
CurvedAnimation(parent: _animationController, curve: Curves.easeIn);
_animation = Tween<double>(begin: 0, end: math.pi * 2).animate(curve);
}
void startStopAnimation() {
print(_animationController.isAnimating.toString());
if (_animationController.isAnimating)
_animationController.stop();
else {
_animationController.forward();
}
}
#override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Transform(
alignment: Alignment.center,
transform: Matrix4.identity()..rotateZ(_animation.value),
child: Container(
color: Colors.pinkAccent,
width: 200,
height: 200,
),
),
RaisedButton(
child: const Text("Start/Stop Animation"),
elevation: 15,
color: Colors.blueGrey,
onPressed: () => startStopAnimation(),
)
],
));
}
#override
void dispose() {
_animationController.dispose();
print("Curved Animation Example : dispose is called");
super.dispose();
}
}
main.dart file
import 'package:flutter/material.dart';
import 'curvesanimations.dart';
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: MyHomeScaffold(
title: "Curved Animation Example",
),
);
}
}
class MyHomeScaffold extends StatelessWidget {
final String title;
MyHomeScaffold({this.title});
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text(title),
),
body: CurvedAnimationExample(),
),
);
}
}
This is because your animation has already reached its endpoint. You can't keep going forward once the end point is reached. An easy fix is to check if the animation is at the endpoint already and reset it to the beginning if it is:
void startStopAnimation() {
print(_animationController.isAnimating.toString());
if (_animationController.isAnimating)
_animationController.stop();
else {
print("forward");
if(_animationController.isCompleted) {//Check if animation is at endpoint
_animationController.value = 0;
}
_animationController.forward();
}
}

How to directly add bloc to the view widget

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.