How Animate Like Button in Flutter - flutter

I would like to ask you how to animate the size of an icon when you click on an image like on Instagram, Tiktok...
This is what I tried (and many other things) but without success.
GestureDetector(
onDoubleTap: (){
setState(() {
_showLikeAnimation = true;
_sizeFavoriteAnimation = 60.0; //old value is 20.0
});
},
child: Stack(
alignment: AlignmentDirectional.center,
children: [
_imagePost(),
AnimatedSize(curve: Curves.easeIn, duration: const Duration(seconds: 2), child: Icon(Icons.favorite, size: _sizeFavoriteAnimation))
],
)),
Would you have an idea?

One way you can achieve this is by using ScaleTransition and a CurvedAnimation. Below is a simple example.
When the user taps the icon, I change the look of the icon so it shows the latest state (active/not active) and I make it a little smaller. When this transition ends I make the icon big again. This is similar to how a button behaves in the real world when you press it. I hope this is what you had in mind.
class LikeButton extends StatefulWidget {
const LikeButton({Key? key}) : super(key: key);
#override
State<LikeButton> createState() => _LikeButtonState();
}
class _LikeButtonState extends State<LikeButton>
with SingleTickerProviderStateMixin {
late final AnimationController _controller = AnimationController(
duration: const Duration(milliseconds: 200), vsync: this, value: 1.0);
bool _isFavorite = false;
#override
void dispose() {
super.dispose();
_controller.dispose();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
_isFavorite = !_isFavorite;
});
_controller
.reverse()
.then((value) => _controller.forward());
},
child: ScaleTransition(
scale: Tween(begin: 0.7, end: 1.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOut)),
child: _isFavorite
? const Icon(
Icons.favorite,
size: 30,
color: Colors.red,
)
: const Icon(
Icons.favorite_border,
size: 30,
),
),
);
}
}

You can use https://pub.dev/packages/lottie to animate,
try this;
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return const MaterialApp(title: 'Material App', home: LottieLearn());
}
}
class LottieLearn extends StatefulWidget {
const LottieLearn({Key? key}) : super(key: key);
#override
State<LottieLearn> createState() => _LottieLearnState();
}
class _LottieLearnState extends State<LottieLearn> with TickerProviderStateMixin {
#override
Widget build(BuildContext context) {
final AnimationController darkmodeController =
AnimationController(vsync: this, duration: const Duration(seconds: 2));
final AnimationController favoriteController =
AnimationController(vsync: this, duration: const Duration(seconds: 1));
bool isLight = false;
bool isFavorite = false;
const String favoriteButton = "https://assets10.lottiefiles.com/packages/lf20_slDcnv.json";
const String darkMode = "https://assets10.lottiefiles.com/packages/lf20_tbyegho2.json";
return Scaffold(
appBar: AppBar(
actions: [
InkWell(
onTap: () async {
await darkmodeController.animateTo(isLight ? 0.5 : 1);
// controller.animateTo(0.5);
isLight = !isLight;
},
child: Lottie.network(darkMode, repeat: false, controller: darkmodeController)),
InkWell(
onTap: () async {
await favoriteController.animateTo(isFavorite ? 1 : 0);
isFavorite = !isFavorite;
},
child: Lottie.network(favoriteButton, repeat: false, controller: favoriteController)),
],
),
);
}
}
this must give you an idea, just "flutter pub add lottie" then copy paste code

Related

How can use AnimationController in Cubit or Bloc?

I want to use AnimationController in the StatelessWidget class with Cubit or Bloc in a flutter, if anyone can help me with an example link to explain?
I think the general practice is to avoid using presentation layer components in Blocs. I would use Bloc to manage my state value, but run the animation components in a stateful widget.
Blocs
part 'side_bloc.freezed.dart';
typedef StateEmitter = Emitter<SideState>;
class SideBloc extends Bloc<SideEvent, SideState> {
SideBloc() : super(const SideState(20)) {
on<SideIncrement>(onIncrement);
on<SideDecrement>(onDecrement);
}
void onIncrement(SideIncrement event, StateEmitter emit) {
emit(state.copyWith(side: state.side + 10));
}
void onDecrement(SideDecrement event, StateEmitter emit) {
if (state.side <= 10) {
return;
}
emit(state.copyWith(side: state.side - 10));
}
}
#freezed
class SideEvent with _$SideEvent {
const factory SideEvent.increment() = SideIncrement;
const factory SideEvent.decrement() = SideDecrement;
}
#freezed
class SideState with _$SideState {
const factory SideState(int side) = _SideState;
}
Animated component
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<int> _animation;
late CurvedAnimation _curvedAnimation;
#override
void initState() {
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
);
_curvedAnimation = CurvedAnimation(
parent: _controller,
curve: Curves.elasticOut,
);
_animation = IntTween(begin: 20, end: 20).animate(_curvedAnimation);
_controller.forward();
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
void _animateTo(int value) {
int old = _animation.value;
_controller.reset();
_animation = IntTween(begin: old, end: value).animate(
_curvedAnimation,
);
_controller.forward();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: BlocListener<SideBloc, SideState>(
listener: (context, state) {
_animateTo(state.side);
},
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return SizedSquare(side: _animation.value);
},
),
),
floatingActionButton: const SideChangeButtons(),
);
}
}
class SideChangeButtons extends StatelessWidget {
const SideChangeButtons({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () =>
context.read<SideBloc>().add(const SideEvent.increment()),
child: const Icon(Icons.add),
),
const SizedBox(height: 8),
FloatingActionButton(
onPressed: () =>
context.read<SideBloc>().add(const SideEvent.decrement()),
child: const Icon(Icons.remove),
),
],
);
}
}
class SizedSquare extends StatelessWidget {
final int side;
const SizedSquare({
Key? key,
required this.side,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: SizedBox.fromSize(
size: Size.square(side.toDouble()),
child: Container(color: Colors.red),
),
);
}
}
AnimationController works with TickerProviderStateMixin which means that you MUST use a statefulWidget somewhere in your widget tree.

Animating title change in ExpansionTile

I have an ExpansionTile that have different titles in expanded\collapsed state.
class _ExpandablePaneState extends State<ExpandablePane>
with SingleTickerProviderStateMixin {
bool isExpanded = false;
AnimationController _controller;
Animation<double> _iconTurns;
static final Animatable<double> _easeInTween =
CurveTween(curve: Curves.easeIn);
static final Animatable<double> _halfTween =
Tween<double>(begin: 0.0, end: 0.5);
Duration _kExpand = Duration(milliseconds: 250);
Widget _myAnimatedWidget;
#override
void initState() {
super.initState();
_controller = AnimationController(duration: _kExpand, vsync: this);
_iconTurns = _controller.drive(_halfTween.chain(_easeInTween));
_controller.value = 0.0;
_myAnimatedWidget = widget.collapsedTitle;
}
#override
Widget build(BuildContext context) {
return Theme(
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
child: ExpansionTile(
onExpansionChanged: (value) {
if (value) {
_controller.forward();
} else {
_controller.reverse();
}
setState(() {
isExpanded = value;
_myAnimatedWidget =
isExpanded ? widget.expandedTitle : widget.collapsedTitle;
});
},
title: Expanded(
child: Stack(children: [
AnimatedSwitcher(
duration: Duration(milliseconds: 2500),
transitionBuilder: (child, animation) => ScaleTransition(
child: child,
scale: animation,
),
child: _myAnimatedWidget,
),
Positioned.fill(
child: Align(
alignment: Alignment.centerRight,
child: RotationTransition(
turns: _iconTurns,
child: const Icon(Icons.expand_more),
),
),
)
]),
),
children: widget.content,
),
);
}
}
I want to make an animation between these states, how I can achieve it?
I tried AnimatedSwitcher, but it didn't work. I'm totally don't see an animation.
You can copy paste run full code below
You can wrap _myAnimatedWidget with Container and provide key: ValueKey<bool>(isExpanded)
From official example https://api.flutter.dev/flutter/widgets/AnimatedSwitcher-class.html
This key causes the AnimatedSwitcher to interpret this as a "new"
child each time the count changes, so that it will begin its animation
when the count changes.
I also remove Expanded in title
code snippet
child: Container(
key: ValueKey<bool>(isExpanded), child: _myAnimatedWidget),
working demo
full code
import 'package:flutter/material.dart';
class ExpandablePane extends StatefulWidget {
Widget expandedTitle;
Widget collapsedTitle;
List<Widget> content;
ExpandablePane({this.expandedTitle, this.collapsedTitle, this.content});
#override
_ExpandablePaneState createState() => _ExpandablePaneState();
}
class _ExpandablePaneState extends State<ExpandablePane>
with SingleTickerProviderStateMixin {
bool isExpanded = false;
AnimationController _controller;
Animation<double> _iconTurns;
static final Animatable<double> _easeInTween =
CurveTween(curve: Curves.easeIn);
static final Animatable<double> _halfTween =
Tween<double>(begin: 0.0, end: 0.5);
Duration _kExpand = Duration(milliseconds: 250);
Widget _myAnimatedWidget;
#override
void initState() {
super.initState();
_controller = AnimationController(duration: _kExpand, vsync: this);
_iconTurns = _controller.drive(_halfTween.chain(_easeInTween));
_controller.value = 0.0;
_myAnimatedWidget = widget.collapsedTitle;
}
#override
Widget build(BuildContext context) {
return Theme(
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
child: ExpansionTile(
onExpansionChanged: (value) {
if (value) {
_controller.forward();
} else {
_controller.reverse();
}
setState(() {
isExpanded = value;
_myAnimatedWidget =
isExpanded ? widget.expandedTitle : widget.collapsedTitle;
});
},
title: Stack(children: [
AnimatedSwitcher(
duration: Duration(milliseconds: 2500),
transitionBuilder: (child, animation) => ScaleTransition(
child: child,
scale: animation,
),
child: Container(
key: ValueKey<bool>(isExpanded), child: _myAnimatedWidget),
),
Positioned.fill(
child: Align(
alignment: Alignment.centerRight,
child: RotationTransition(
turns: _iconTurns,
child: const Icon(Icons.expand_more),
),
),
)
]),
children: widget.content,
),
);
}
}
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(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> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
ExpandablePane(
expandedTitle: Text("expand"),
collapsedTitle: Text("collapsed"),
content: [Text("1"), Text("2"), Text("3")],
),
],
),
),
);
}
}

Flutter Reworked question: Issue sharing states between widget with button and widget with countdown timer

I am trying since some days to connect an animated stateful child widget with a countdown timer to the parent stateful widget with the user interaction. I found this answer from Andrey on a similar question (using Tween which I do not) that already helped a lot, but I still don't get it to work. My assumption is, the child's initState could be the reason. The timer's code comes from here.
I have removed quite some code including some referenced functions/classes. This should provide a clearer picture on the logic:
In MainPageState I declare and init the _controller of the animation
In MainPageState I call the stateless widget CreateKeypad hosting among others the "go" key
When go is clicked, this event is returned to MainPageState and _controller.reverse(from: 1.0); executed
In MainPageState I call the stateful widget CountDownTimer to render the timer
In _CountDownTimerState I am not sure if my initState is correct
In _CountDownTimerState I build the animation with CustomTimerPainter from the timer code source
The animation shall render a white donut and a red, diminishing arc on top. However, I only see the white donut, not the red timer's arc. Any hint is highly appreciated.
class MainPage extends StatefulWidget {
MainPage({Key key, this.title}) : super(key: key);
final String title;
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> with TickerProviderStateMixin {
AnimationController _controller;
var answer="0", correctAnswer = true, result = 0;
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: Duration(seconds: 7));
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
),
child: SafeArea(
child: Container(
child: Column(
children: <Widget>[
CreateKeypad( // creates a keypad with a go button. when go is clicked, countdown shall start
prevInput: int.parse((answer != null ? answer : "0")),
updtedInput: (int val) {
setState(() => answer = val.toString());
},
goSelected: () {
setState(() {
if (answer == result.toString()) {
correctAnswer = true;
}
final problem = createProblem();
result = problem.result;
});
_controller.reverse(from: 1.0); // start the countdown animation
Future.delayed(const Duration(milliseconds: 300,),
() => setState(() => correctAnswer = true));
},
),
CountDownTimer(_controller), // show countdown timer
]
),
),
)
);
}
}
// CREATE KEYPAD - all keys but "1! and "go" removed
class CreateKeypad extends StatelessWidget {
final int prevInput;
final VoidCallback goSelected;
final Function(int) updtedInput;
CreateKeypad({#required this.prevInput, #required this.updtedInput, this.goSelected});
#override
Widget build(BuildContext context) {
return Row(
children: <Widget> [
Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(2.0),
child: SizedBox(
width: 80.0, height: 80.0,
child: CupertinoButton(
child: Text("1", style: TextStyle(color: CupertinoColors.black)),
onPressed: () {
updtedInput(1);
},
),
),
),
Padding(
padding: const EdgeInsets.all(2.0),
child: SizedBox(
width: 80.0, height: 80.0,
child: CupertinoButton(
child: Text("Go!", style: TextStyle(color: CupertinoColors.black)),
onPressed: () => goSelected(),
),
),
),
],
),
]
);
}
}
// CREATE COUNTDOWN https://medium.com/flutterdevs/creating-a-countdown-timer-using-animation-in-flutter-2d56d4f3f5f1
class CountDownTimer extends StatefulWidget {
CountDownTimer(this._controller);
final AnimationController _controller;
#override
_CountDownTimerState createState() => _CountDownTimerState();
}
class _CountDownTimerState extends State<CountDownTimer> with TickerProviderStateMixin {
#override
void initState() {
super.initState(); // here I have some difference to Andrey's answer because I do not use Tween
}
String get timerString {
Duration duration = widget._controller.duration * widget._controller.value;
return '${duration.inMinutes}:${(duration.inSeconds % 60)
.toString()
.padLeft(2, '0')}';
}
#override
Widget build(BuildContext context) {
return Container(
child: AnimatedBuilder(
animation: widget._controller,
builder:
(BuildContext context, Widget child) {
return CustomPaint(
painter: CustomTimerPainter( // this draws a white donut and a red diminishing arc on top
animation: widget._controller,
backgroundColor: Colors.white,
color: Colors.red,
));
},
),
);
}
}
You can copy paste run full code below
Step 1: You can put controller inside CountDownTimerState
Step 2: Use GlobalKey
CountDownTimer(key: _key)
Step 3: Call function start() inside _CountDownTimerState with _key.currentState
goSelected: () {
setState(() {
...
_controller.reverse(from: 10.0); // start the countdown animation
final _CountDownTimerState _state = _key.currentState;
_state.start();
...
class _CountDownTimerState extends State<CountDownTimer>
with TickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 7));
super
.initState(); // here I have some difference to Andrey's answer because I do not use Tween
}
...
void start() {
setState(() {
_controller.reverse(from: 1.0);
});
}
working demo
full code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'dart:math' as math;
class MainPage extends StatefulWidget {
MainPage({Key key, this.title}) : super(key: key);
final String title;
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> with TickerProviderStateMixin {
AnimationController _controller;
var answer = "0", correctAnswer = true, result = 0;
GlobalKey _key = GlobalKey();
#override
void initState() {
super.initState();
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 7));
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
//navigationBar: CupertinoNavigationBar(),
child: SafeArea(
child: Container(
color: Colors.blue,
child: Column(children: <Widget>[
CreateKeypad(
// creates a keypad with a go button. when go is clicked, countdown shall start
prevInput: int.parse((answer != null ? answer : "0")),
updtedInput: (int val) {
setState(() => answer = val.toString());
},
goSelected: () {
setState(() {
if (answer == result.toString()) {
correctAnswer = true;
}
/*final problem = createProblem();
result = problem.result;*/
});
print("go");
_controller.reverse(from: 10.0); // start the countdown animation
final _CountDownTimerState _state = _key.currentState;
_state.start();
/* Future.delayed(
const Duration(
milliseconds: 300,
),
() => setState(() => correctAnswer = true));*/
},
),
Container(
height: 400,
width: 400,
child: CountDownTimer(key: _key)), // show countdown timer
]),
),
));
}
}
// CREATE KEYPAD - all keys but "1! and "go" removed
class CreateKeypad extends StatelessWidget {
final int prevInput;
final VoidCallback goSelected;
final Function(int) updtedInput;
CreateKeypad(
{#required this.prevInput, #required this.updtedInput, this.goSelected});
#override
Widget build(BuildContext context) {
return Row(children: <Widget>[
Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(2.0),
child: SizedBox(
width: 80.0,
height: 80.0,
child: CupertinoButton(
child:
Text("1", style: TextStyle(color: CupertinoColors.black)),
onPressed: () {
updtedInput(1);
},
),
),
),
Padding(
padding: const EdgeInsets.all(2.0),
child: SizedBox(
width: 80.0,
height: 80.0,
child: CupertinoButton(
child:
Text("Go!", style: TextStyle(color: CupertinoColors.black)),
onPressed: () => goSelected(),
),
),
),
],
),
]);
}
}
// CREATE COUNTDOWN https://medium.com/flutterdevs/creating-a-countdown-timer-using-animation-in-flutter-2d56d4f3f5f1
class CountDownTimer extends StatefulWidget {
CountDownTimer({Key key}) : super(key: key);
//final AnimationController _controller;
#override
_CountDownTimerState createState() => _CountDownTimerState();
}
class _CountDownTimerState extends State<CountDownTimer>
with TickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 7));
super
.initState(); // here I have some difference to Andrey's answer because I do not use Tween
}
String get timerString {
Duration duration = _controller.duration * _controller.value;
return '${duration.inMinutes}:${(duration.inSeconds % 60).toString().padLeft(2, '0')}';
}
void start() {
setState(() {
_controller.reverse(from: 1.0);
});
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.green,
child: AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
return CustomPaint(
painter: CustomTimerPainter(
// this draws a white donut and a red diminishing arc on top
animation: _controller,
backgroundColor: Colors.green,
color: Colors.red,
));
},
),
);
}
}
class CustomTimerPainter extends CustomPainter {
CustomTimerPainter({
this.animation,
this.backgroundColor,
this.color,
}) : super(repaint: animation);
final Animation<double> animation;
final Color backgroundColor, color;
#override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()
..color = backgroundColor
..strokeWidth = 10.0
..strokeCap = StrokeCap.butt
..style = PaintingStyle.stroke;
canvas.drawCircle(size.center(Offset.zero), size.width / 2.0, paint);
paint.color = color;
double progress = (1.0 - animation.value) * 2 * math.pi;
//print("progress ${progress}");
canvas.drawArc(Offset.zero & size, math.pi * 1.5, -progress, false, paint);
}
#override
bool shouldRepaint(CustomTimerPainter old) {
//print(animation.value);
return true;
}
}
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: MainPage(),
);
}
}

Flutter: Spinning sync icon in AppBar

How can I animate an IconButton placed in a AppBar? The sync icon should spinning while a database synchronisation is running.
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Dashboard"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.sync), // <-- Icon
onPressed: () {
print("sync");
// start spinning
syncDatabaseFull(); // Returns future and resolves when sync is finish
},
)
],
),
body: Center(
child: RaisedButton(
child: Text('HOME screen'),
onPressed: () {
},
),
),
);
}
}
You can copy paste run full code below
You can extend AnimatedWidget and pass callback
example code below simulate syncDatabaseFull run for 5 seconds
code snippet
class AnimatedSync extends AnimatedWidget {
VoidCallback callback;
AnimatedSync({Key key, Animation<double> animation, this.callback})
: super(key: key, listenable: animation);
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return Transform.rotate(
angle: animation.value,
child: IconButton(
icon: Icon(Icons.sync), // <-- Icon
onPressed: () => callback()),
);
}
}
actions: <Widget>[
AnimatedSync(
animation: rotateAnimation,
callback: () async{
controller.forward();
await syncDatabaseFull();
controller.stop();
controller.reset();
},
),
],
working demo
full code
import 'package:flutter/material.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: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class AnimatedSync extends AnimatedWidget {
VoidCallback callback;
AnimatedSync({Key key, Animation<double> animation, this.callback})
: super(key: key, listenable: animation);
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return Transform.rotate(
angle: animation.value,
child: IconButton(
icon: Icon(Icons.sync), // <-- Icon
onPressed: () => callback()),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation colorAnimation;
Animation rotateAnimation;
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
Future<bool> syncDatabaseFull() async{
await Future.delayed(Duration(seconds: 5), () {
});
return Future.value(true);
}
#override
void initState() {
controller =
AnimationController(vsync: this, duration: Duration(seconds: 200));
rotateAnimation = Tween<double>(begin: 0.0, end: 360.0).animate(controller);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: <Widget>[
AnimatedSync(
animation: rotateAnimation,
callback: () async{
controller.forward();
await syncDatabaseFull();
controller.stop();
controller.reset();
},
),
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
CustomedSpinningIconButton class
import 'package:flutter/material.dart';
class SpinningIconButton extends AnimatedWidget {
final VoidCallback onPressed;
final IconData iconData;
final AnimationController controller;
SpinningIconButton({Key key, this.controller, this.iconData, this.onPressed})
: super(key: key, listenable: controller);
Widget build(BuildContext context) {
final Animation<double> _animation = CurvedAnimation(
parent: controller,
// Use whatever curve you would like, for more details refer to the Curves class
curve: Curves.linearToEaseOut,
);
return RotationTransition(
turns: _animation,
child: IconButton(
icon: Icon(iconData),
onPressed: onPressed,
),
);
}
}
How to use it:
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
AnimationController _animationController;
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(seconds: 1)
);
}
#override
Widget build(BuildContext context) {
...
actions: <Widget>[
SpinningIconButton(
controller: _animationController,
iconData: Icons.sync,
onPressed: () async {
// Play the animation infinitely
_animationController.repeat();
// Sleep 1.5 seconds or await the Async method
print('Something has finished.');
// Complete current cycle of the animation
_animationController.forward(from: _animationController.value);
},
)
],
...
}

How to have a custom animation as a Flutter progress / loading indicator?

I am trying to replace the default CircularProgressIndicator with my own animation. I created a spinning widget based on the example here How to rotate an image using Flutter AnimationController and Transform? , but when replacing CircularProgressIndicator with "MyIconSpinner", for some reason it is not appearing. Any tips please?
Here are the contents of MyIconSpinner
import 'package:flutter/material.dart';
import 'package:flutter_icons/flutter_icons.dart';
class MyIconSpinner extends StatefulWidget {
MyIconSpinner({Key key, this.title}) : super(key: key);
final String title;
#override
_MyIconSpinnerState createState() => _MyIconSpinnerState();
}
class _MyIconSpinnerState extends State<MyIconSpinner>
with TickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
_controller = AnimationController(
duration: const Duration(milliseconds: 5000),
vsync: this,
);
super.initState();
}
#override
Widget build(BuildContext context) {
_controller.forward();
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RotationTransition(
turns: Tween(begin: 0.0, end: 1.0).animate(_controller),
child: Icon(
Icons.star,
size: 40,
),
),
],
),
),
);
}
}
I am placing it in a widget like this
return Scaffold(
appBar: AppBar(
title: Text("Appbar"),
backgroundColor: Colors.black,
automaticallyImplyLeading: false,
),
body: Center(
child: Column(children: <Widget>[
StreamBuilder(
stream: doSomething(withSomeData),
builder: (BuildContext context,
AsyncSnapshot<List<DocumentSnapshot>> asyncSnapshot) {
if (!asyncSnapshot.hasData) return MyIconSpinner();
I think you shouldn't wrap MyIconSpinner in Scaffold. You should give MyIconSpinner color parameter and also repeat animation after it is completed. Here is the edited version of MyIconSpinner.
class MyIconSpinner extends StatefulWidget {
MyIconSpinner({Key key, this.title, this.color = Colors.blue}) : super(key: key);
final String title;
final Color color;
#override
_MyIconSpinnerState createState() => _MyIconSpinnerState();
}
class _MyIconSpinnerState extends State<MyIconSpinner>
with TickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
_controller = AnimationController(
duration: const Duration(milliseconds: 5000),
vsync: this,
);
_controller.addListener((){
if(_controller.isCompleted){
_controller.repeat();
}
});
super.initState();
}
#override
Widget build(BuildContext context) {
_controller.forward();
return RotationTransition(
turns: Tween(begin: 0.0, end: 1.0).animate(_controller),
child: Icon(
Icons.star,
size: 40,
color: widget.color,
),
);
}
}