Flutter: how to control animation from parent - animation

I need to start an animation of a child widget from a parent widget. How can I do this?
I've tried giving the parent the controller, but then how do you replace vsync: this?
This is the basic code (I haven't actually tested this code yet, but I shows what I mean):
import 'package:flutter/material.dart';
class ParentWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
ChildText(),
FlatButton(
child: Text('start the animation'),
onPressed: () {
// start the animation!!!????????
},
)
],
);
}
}
class ChildText extends StatefulWidget {
#override
_ChildTextState createState() => _ChildTextState();
}
class _ChildTextState extends State<ChildText> with TickerProviderStateMixin {
AnimationController _controller;
Animation _animation;
#override
void initState() {
super.initState();
// actual animation is much more complex, this is just a random demo
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 2));
_animation = Tween(begin: -1.0, end: 100.0).animate(CurvedAnimation(
parent: _controller,
curve: Curves.fastOutSlowIn,
));
}
#override
Widget build(BuildContext context) {
return Transform.translate(
offset: Offset(0, _animation.value),
child: Text('Text with fancy animation'));
}
}

You can try this:
class ParentWidget extends StatefulWidget {
#override
_ParentWidget createState() => _ParentWidget();
}
class _ParentWidget extends State<ParentWidget> with TickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
super.initState();
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 2));
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
ChildText(_controller),
FlatButton(
child: Text('start the animation'),
onPressed: () {
// start the animation!!!
_controller.forward();
},
)
],
);
}
}
class ChildText extends StatefulWidget {
ChildText(this._controller);
final AnimationController _controller;
#override
_ChildTextState createState() => _ChildTextState();
}
class _ChildTextState extends State<ChildText> with TickerProviderStateMixin {
Animation _animation;
#override
void initState() {
super.initState();
_animation = Tween(begin: -1.0, end: 100.0).animate(CurvedAnimation(
parent: widget._controller,
curve: Curves.fastOutSlowIn,
));
}
#override
Widget build(BuildContext context) {
return Transform.translate(
offset: Offset(0, _animation.value),
child: Text('Text with fancy animation'));
}
}

Related

Flutter WidgetsBindingObserver's didChangeAppLifecycleState not being called on Windows Desktop

The print functions should be called according to the lifecycle transitions, but none of them are being called. To test this, I'm running the app in debug mode and moving it to the background/foreground (i.e. changing to another app and then returning to this app).
What am I doing wrong?
import 'package:flutter/material.dart';
class StopwatchVw extends StatefulWidget {
const StopwatchVw({Key? key}) : super(key: key);
#override _StopwatchVwState createState() => _StopwatchVwState();
}
class _StopwatchVwState extends State<StopwatchVw> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);
}
#override
Future<void> didChangeAppLifecycleState(AppLifecycleState state) {
print('Changed');
switch (state) {
case AppLifecycleState.inactive:
print("Inactive");
break;
case AppLifecycleState.paused:
print("Paused");
break;
case AppLifecycleState.resumed:
print("Resumed");
break;
case AppLifecycleState.detached:
print("Suspending");
break;
}
}
#override
void dispose() {
WidgetsBinding.instance!.removeObserver(this);
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(body: Text('HEY'));
}
This is my main.dart:
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:clocker/stopwatch_vw.dart';
import 'package:flutter/material.dart';
import 'package:flutter_acrylic/flutter_acrylic.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Window.initialize();
await Window.hideWindowControls();
runApp(const MyApp());
doWhenWindowReady(() {
const initialSize = Size(350, 200);
appWindow
..size = initialSize
..minSize = initialSize
..maxSize = initialSize
..alignment = Alignment.bottomRight
..show();
});
Window.setEffect(
effect: WindowEffect.acrylic,
color: const Color.fromARGB(29, 250, 250, 227),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MoveWindow(child: const StopwatchVw())
);
}
}
Flutter currently doesn't support LifeCycle events on the desktop, But there is an existing issue which is being tracked here #30735.
Currently, you can determine the status of your application with the window_manager package
class Windows extends StatefulWidget {
const Windows({super.key});
#override
State<Windows> createState() => _WindowsState();
}
class _WindowsState extends State<Windows> with WindowListener {
#override
void onWindowClose() {
// do something
}
#override
void onWindowFocus() {
// do something
}
#override
void onWindowMinimize() {
// do something
}
#override
void initState() {
windowManager.addListener(this);
super.initState();
}
#override
void dispose() {
windowManager.removeListener(this);
super.dispose();
}

How to auto start animated icon

My application has a stateful widget which is the profile area, I would like to display an animated icon indicating to the user that is possible to scroll the screen, I want to use the animated icon, how to auto animate an AnimatedIcon as soon the Profile screen loads, thanks.
Obs.: The play_pause is just a placeholder for an animated icon
import 'package:flutter/material.dart';
void main() {
runApp(Profile());
}
class Profile extends StatefulWidget {
#override
ProfileState createState() {
return ProfileState();
}
}
class ProfileState extends State<Profile> with TickerProviderStateMixin {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: AnimatedIcon(
progress: AnimationController(
vsync: this,
duration: Duration(milliseconds: 400),
reverseDuration: Duration(milliseconds: 400),
),
icon: AnimatedIcons.play_pause,
),
)
)
);
}
}
You have to create an instance of AnimationController and initialize it on the initState. Then you can start the animation by calling animateTo method.
import 'package:flutter/material.dart';
void main() {
runApp(Profile());
}
class Profile extends StatefulWidget {
#override
ProfileState createState() {
return ProfileState();
}
}
class ProfileState extends State<Profile> with TickerProviderStateMixin {
AnimationController _animationController;
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 400),
);
_animationController.animateTo(1.0);
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: AnimatedIcon(
progress: _animationController,
icon: AnimatedIcons.play_pause,
),
),
),
);
}
}
you could do that using the animationController
_animationController.forward(); /// to start the animation
You can start your animation after the build method executes by calling it like this.
#override
void initState(){
super.initState();
WidgetsBinding.instance.addPostFrameCallback((​_){
_animationController.forward();
});
}

How to dismiss any open dialog in Flutter app, when app goes to background

In my Flutter app I have lots of pop up dialogs, but I need to dismiss any open dialog when app goes to background (user switches to another app).
I know about Lifecycle, but it's not easy to implement it for all dialogs.
Did you try Navigator.popUntil on the life cycle?.
Navigator.popUntil(context, (route) => !(route is PopupRoute));
Example:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
#override
void initState() {
WidgetsBinding.instance.addObserver(this);
super.initState();
}
void _showDialogs() {
showDialog(
context: context,
builder: (context) => AlertDialog(title: Text("Dialog 1")),
);
showDialog(
context: context,
builder: (context) => AlertDialog(title: Text("Dialog 2")),
);
showDialog(
context: context,
builder: (context) => AlertDialog(title: Text("Dialog 3")),
);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text("show multiple dialogs"),
onPressed: _showDialogs,
),
),
);
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.paused)
Navigator.popUntil(context, (route) => !(route is PopupRoute));
}
}
Navigator.of(context,rootNavigator:true).pop();

Start a SlideTransition's animation from method

I've been trying to make a Container move from the bottom of the screen to the middle when I click a button. However, I can't figure out how to properly execute the animation only when I press the button. Furthermore, it seems that when I call the _customWidget.startAnimation() method an error stating I called the function on void always happens. Here are my questions:
How do I access startAnimation() inside MyCustomWidget properly and how should I structure the code to only run the animation when I press the button?
Main.dart:
import 'package:flutter/material.dart';
import 'MyCustomWidget.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
MyCustomWidget _customWidget;
#override
void initState() {
super.initState();
_customWidget = MyCustomWidget();
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: MyCustomWidget(),
),
FloatingActionButton(
onPressed: () => {} //_customWidget.startAnimation() gives an error,
)
],
);
}
}
MyCustomWidget.dart:
import 'package:flutter/material.dart';
class MyCustomWidget extends StatefulWidget {
#override
_MyCustomWidgetState createState() => _MyCustomWidgetState();
startAnimation() {
debugPrint('is this where the function should be?');
}
}
class _MyCustomWidgetState extends State<MyCustomWidget>
with SingleTickerProviderStateMixin {
AnimationController _animationController;
Animation<Offset> _offsetAnimation;
void startAnimation() {
debugPrint('or is it here?');
}
#override
void initState() {
super.initState();
//Following code should go in startAnimation() rather than letting it execute
//every time the object is created
_animationController = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
)..repeat(reverse: true);
_offsetAnimation = Tween<Offset>(
begin: Offset.zero,
end: const Offset(1.5, 0.0),
).animate(CurvedAnimation(
parent: _animationController,
curve: Curves.elasticIn,
));
}
#override
Widget build(BuildContext context) {
return SlideTransition(
position: _offsetAnimation,
child: Container(
color: Colors.white,
height: 50,
width: 50,
),
);
}
#override
void dispose() {
super.dispose();
_animationController.dispose();
}
}
In Flutter, you access the child widgets using key. You also have to make the state public (remove the underscore from _MyCustomWidgetState)
So in the child:
class MyCustomWidget extends StatelessWidget {
final Key key;
MyCustomWidget(this.key) : super(key: key);
[...]
in the parent:
GlobalKey<MyCustomWidgetState> key = GlobalKey<MyCustomWidgetState>();
[...]
Center(
child: MyCustomWidget(key)
)
and calling the child (from parent):
key.currentState.startAnimation();
Tien's answer got my widget working correctly, here's how the two files look after the modifications:
Main.dart
import 'package:flutter/material.dart';
import 'MyCustomWidget.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
GlobalKey<MyCustomWidgetState> _customWidgetKey =
GlobalKey<MyCustomWidgetState>();
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: MyCustomWidget(_customWidgetKey),
),
FloatingActionButton(
onPressed: () => _customWidgetKey.currentState.show(),
)
],
);
}
}
MyCustomWidget.dart:
import 'package:flutter/material.dart';
class MyCustomWidget extends StatefulWidget {
final Key key;
MyCustomWidget(this.key) : super(key: key);
#override
MyCustomWidgetState createState() => MyCustomWidgetState();
}
class MyCustomWidgetState extends State<MyCustomWidget>
with SingleTickerProviderStateMixin {
AnimationController _animationController;
Animation<Offset> _offsetAnimation;
void show() {
setState(() {
_animationController.animateTo(1.0);
});
}
void hide() {
setState(() {
_animationController.animateTo(0.0);
});
}
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
);
_offsetAnimation = Tween<Offset>(
begin: Offset(0.0, 40),
end: Offset.zero,
).animate(CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOutQuint,
));
}
#override
Widget build(BuildContext context) {
return SlideTransition(
position: _offsetAnimation,
child: Container(
child: FloatingActionButton(
backgroundColor: Colors.white,
onPressed: () => hide(),
),
),
);
}
#override
void dispose() {
super.dispose();
_animationController.dispose();
}
}
This gets you a button which brings up a button when pressed. Clicking the second button sends it down. This whole thing is going to be useful to make custom widgets pop up and down!

How can I control animations in Flutter from outside?

Passing state down to widgets is easy. I have a StatefulWidget that contains an animation with its controller. I need to be able to trigger the animation from another widget higher in my widget tree.
My MainApp should trigger the animation using a button.
As I understand AnimationController only has an imperative API. I can call controller.forward() or controller.reverse(). But to do this I need to expose the controller to my MainApp.
What I currently do is to keep a global variable of my state around.
class MainApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
...
body: new LogoWidget(),
);
}
_startAnimation() {
_state.restartAnimation();
}
}
_LogoWidgetState _state; // yuk!
class LogoWidget extends StatefulWidget {
_LogoWidgetState createState() {
_state = _LogoWidgetState();
return _state;
}
}
class _LogoWidgetState extends State<LogoWidget>
with SingleTickerProviderStateMixin {
Animation<double> animation;
AnimationController controller;
restartAnimation() {
controller.value == 1.0 ? controller.reverse() : controller.forward();
}
...
}
(full sourcecode here)
What is a better way to deal with this?
You don't need _LogoWidgetState _state; // yuk! out in the middle of nowhere, but you can try:
create LogoWidget _myBody = LogoWidget(), and use that for your body:
similarily, apply with final _LogoWidgetState _state = _LogoWidgetState()
then call it as _myBody._state.restartAnimation()
Your sample, modified:
class MainApp extends StatelessWidget {
LogoWidget _myBody = LogoWidget(); //<---
#override
Widget build(BuildContext context) {
return new Scaffold(
...
body: _myBody, //<---
);
}
_startAnimation() {
_myBody._state.restartAnimation(); //<---
}
}
class LogoWidget extends StatefulWidget {
final _LogoWidgetState _state = _LogoWidgetState(); //<---
_LogoWidgetState createState() {
return _state;
}
}
But if you think _myBody._state.restartAnimation() is too long, you can shorten it with:
class LogoWidget extends StatefulWidget {
final _LogoWidgetState _state = _LogoWidgetState(); //<---
void restartAnimation() { //<---
_state.restartAnimation();
}
_LogoWidgetState createState() {
return _state;
}
}
Then just use _myBody.restartAnimation()
Here's some relevant posts:
call method in one stateful widget from another stateful widget - Flutter
Flutter: Call a function on a child widget's state

Resources