Replaying the same Flare animation in Flutter - flutter

I'm trying to re-play a Flare animation in Flutter. Not loop the animation when it has complete. I want an animation to be played on demand, the same animation.
When I switch between animations it works fine when just swapping the string and calling setState. Is there a simple way to do this.
Here's what I'm currently doing.
class _FlareDemoState extends State<FlareDemo> {
String animationToPlay = 'activate';
#override
Widget build(BuildContext context) {
print('Animation to play: $animationToPlay');
return Scaffold(
backgroundColor: Colors.purple,
body: GestureDetector(
onTap: () {
setState(() {
});
},
child: FlareActor('assets/button-animation.flr',
animation: animationToPlay)));
}
}
My logs result when I tap the animation
I/flutter (18959): Animation to play: activate
I/flutter (18959): Animation to play: activate
I/chatty (18959): uid=10088(com.example.flare_tutorial) Thread-2 identical 2 lines
I/flutter (18959): Animation to play: activate
I/flutter (18959): Animation to play: activate
I/chatty (18959): uid=10088(com.example.flare_tutorial) Thread-2 identical 7 lines
I/flutter (18959): Animation to play: activate
I/flutter (18959): Animation to play: activate
Reloaded 2 of 495 libraries in 672ms.
I/flutter (18959): Animation to play: activate
Everything is called, it plays the first time, but after that the animation doesn't replay.

A cleaner way to do this is using a custom FlareController. There's a concrete FlareControls implementation that fits this use case nicely.
class _MyHomePageState extends State<MyHomePage> {
// Store a reference to some controls for the Flare widget
final FlareControls controls = FlareControls();
void _playSuccessAnimation() {
// Use the controls to trigger an animation.
controls.play("success");
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: FlareActor("assets/Teddy.flr",
animation: "idle",
fit: BoxFit.contain,
alignment: Alignment.center,
// Make sure you use the controls with the Flare Actor widget.
controller: controls),
floatingActionButton: FloatingActionButton(
onPressed: _playSuccessAnimation,
tooltip: 'Play',
child: Icon(Icons.play_arrow),
),
);
}
}
Note that this example also plays an idle background animation which loops. Any call to FlareControls.play will mix the incoming animation over this background idle animation. You simply omit the animation: "idle" argument if you don't want/need a background animation.
Full example here: https://github.com/luigi-rosso/flare_controls

Based on the answer from #Eugene I came up with a temporary solution. I set the value to empty, start a timer for 50ms and then set the value back to the animation I wanted to play again.
class _FlareDemoState extends State<FlareDemo> {
String animationToPlay = 'activate';
#override
Widget build(BuildContext context) {
print('Animation to play: $animationToPlay');
return Scaffold(
backgroundColor: Colors.purple,
body: GestureDetector(
onTap: () {
_setAnimationToPlay('activate');
},
child: FlareActor('assets/button-animation.flr',
animation: animationToPlay)));
}
}
void _setAnimationToPlay(String animation) {
if (animation == _animationToPlay) {
_animationToPlay = '';
Timer(const Duration(milliseconds: 50), () {
setState(() {
_animationToPlay = animation;
});
});
} else {
_animationToPlay = animation;
}
}
It's messy workaround but it gets the job done. Thank you #Eugene for planting the seed.

FlareActor(
"assets/animation/day_night.flr",
alignment: Alignment.center,
fit: BoxFit.cover,
animation: _animation,
callback: (string) {
if(string=="switch_night"){
setState(() {
_animation = _animationNight;
});
}else if(string=="switch_day"){
setState(() {
_animation = _animationDay;
});
}
},
)

Related

fade in only animation in flutter

I have a home screen and I want to apply fade in animation in image and text I have a image in center and then two line of text and I just want them to fade in for the one time only I have gone through some various examples but they all contains fade in and fade out i just want to reveal thosse image and text to user and the pause.
Try with this it will fade in from initState also i delayed 1 second you can modify it.
bool _visible = false;
#override
void initState() {
Future.delayed(const Duration(seconds: 1), () {
setState(() {
_visible = !_visible;
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: AnimatedOpacity(
// If the widget is visible, animate to 0.0 (invisible).
// If the widget is hidden, animate to 1.0 (fully visible).
opacity: _visible ? 1.0 : 0.0,
duration: const Duration(milliseconds: 500),
// The green box must be a child of the AnimatedOpacity widget.
child:Column(
children:[
Image.network("https://cdn0.iconfinder.com/data/icons/business-startup-10/50/5-256.png")
,
const Text("Your text")
])
),
),
);
}

Updating a Slider based on video position

Summarize the problem
I need help finding a Flutter construct which will allow me to repeatedly check the return value of an API function call for getting the current time of a playing video (position). My goal is to update the Slider() to the position of the current video. I am using plugin flutter_vlc_player for playing back videos.
The most important part of the code below is the videoPlayerController.getPosition() function call. I need a way to repeatedly call this function and get the latest value. This is what I am struggling with.
The SeekBar class instantiated is the Slider() I am updating.
I think I am close to a solution as StreamBuilder is meant to update based on events. Also if I perform hot refresh of app after playing a video, the Slider updates once.
What I am seeing is the stream function is called twice but returns null each time because the video isn't playing yet. I need the stream function to be called while the video is playing.
I/flutter (29465): snapshot: null
I/flutter (29465): snapshot: null
One last thing: videoPlayerController.getPosition() is a Future.
Describe what you've tried:
I tried using StreamBuilder() and FutureBuilder() but I got the same results. The current position is only fetched twice when I need it to be continuously fetched during video playback. I checked the Flutter documentation on StreamBuilder but their example only shows when there is one item to be grabbed and not multiple. I need to rebuild the Slider() widget based on the value returned from function repeatedly.
Show some code:
VlcPlayerController videoPlayerController = VlcPlayerController.network(
'rtsp://ip_addr:8554/test',
hwAcc: HwAcc.FULL,
autoPlay: true,
options: VlcPlayerOptions(
video: VlcVideoOptions(),
rtp: VlcRtpOptions(['--rtsp-tcp'],),
extras: ['--h264-fps=30']
),
);
await Navigator.push(context,
MaterialPageRoute(builder: (context) =>
Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
backgroundColor: Colors.blueAccent,
title: const Text("Playback")),
body: Center(
child: Column(
children: [
VlcPlayer(
controller: videoPlayerController,
aspectRatio: 16/9,
placeholder: const Center(child: CircularProgressIndicator()),
),
StatefulBuilder(builder: (context, setState) {
return Row(
children: [
TextButton(
child: Icon(isPlaying ? Icons.play_arrow : Icons.pause),
style: ButtonStyle(backgroundColor: MaterialStateProperty.all<Color>(Colors.blueAccent),
foregroundColor: MaterialStateProperty.all<Color>(Colors.white)),
onPressed: () {
setState(() {
if(videoPlayerController.value.isPlaying)
{
isPlaying = true;
videoPlayerController.pause();
}
else {
isPlaying = false;
videoPlayerController.play();
}
});
}
),
Text("${videoPlayerController.value.position.inMinutes}:${videoPlayerController.value.position.inSeconds}",
style: const TextStyle(color: Colors.white)),
],
);
}),
StreamBuilder<Duration>(
stream: Stream.fromFuture(videoPlayerController.getPosition()),
builder: (BuildContext context, AsyncSnapshot <Duration> snapshot) {
Duration position = snapshot.data ?? const Duration();
print('snapshot: ${snapshot.data?.inSeconds.toDouble()}');
return Column(
children: [
SeekBar(
duration: const Duration(seconds: 5),
position: position,
onChangeEnd: (newPosition)
{
videoPlayerController.seekTo(newPosition);
},
)
],
);
}
),
]
)
),
)
)
);
Thank you for reading and help. I am still learning Flutter/Dart so any references to helpful classes will be great.
Is there a reason you want to use StreamBuilder in this case? You can use a StatefulWidget and add a listener to the controller. Then update the position inside that listener.
Use this to add listener:
videoPlayerController.addListener(updateSeeker);
Also make sure to remove the listener in dispose method:
videoPlayerController.removeListener(updateSeeker);
Here is the updateSeeker method:
Future<void> updateSeeker() async {
final newPosition = await videoPlayerController.getPosition();
setState(() {
position = newPosition;
});
}
Here is an example of a widget that plays the video and shows its position in a Text widget:
class VideoPlayer extends StatefulWidget {
const VideoPlayer({Key? key}) : super(key: key);
#override
_VideoPlayerState createState() => _VideoPlayerState();
}
class _VideoPlayerState extends State<VideoPlayer> {
final videoPlayerController = VlcPlayerController.network(url);
var position = Duration.zero;
#override
void initState() {
super.initState();
videoPlayerController.addListener(updateSeeker);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
VlcPlayer(
aspectRatio: 16 / 9,
controller: videoPlayerController,
),
Text(position.toString()),
],
),
);
}
Future<void> updateSeeker() async {
final newPosition = await videoPlayerController.getPosition();
setState(() {
position = newPosition;
});
}
#override
void dispose() {
videoPlayerController.removeListener(updateSeeker);
super.dispose();
}
}

Flutter - Play 2 or more Lottie files in sequence

I want to play to 2 Lottie files in sequence, i.e. after one Lottie has completed its animation it should play the second Lottie file.
I tried to achieve this by adding a statuslistener (via AnimationController) to the Lottie widget and calling setstate() on the asset file after first Lottie has completed its animation. It did work but there was a lag while switching to the next Lottie file.
void statusListener(AnimationStatus status) {
if (status == AnimationStatus.completed) {
setState(() {
asset = asset2;
});
controller.reset();
controller.forward();
}
}
Can anyone help me figure it out?
Thanks.
Define two different controller for both the animations.
then play the first animation and hide the second animation for now.
After the first animation gets completed, hide it through visibility.
for example :
Visibility(
child: Text("Gone"),
visible: false,
),
Refer this for more detail : stackoverflow : how to hide widget programmatically
then play the second animation and hide the first animation.
for the time delay, use Future.delayed.
this will execute the code after specific time which you chosed.
example :
Let's say your first animation completes in 2 seconds then, you will play the next animation after 2 seconds so that you will execute the next line of code after 2 seconds.
Future.delayed(const Duration(seconds: 2), () {
setState(() {
_controller.forward();
});
});
There is an example in the lottie repo.
I effectively spent an entire day figuring out a solution, so posting this to calm the mind.
Repo Example that plays many different lottie files in sequence:
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
class App extends StatefulWidget {
const App({Key? key}) : super(key: key);
#override
State<App> createState() => _AppState();
}
class _AppState extends State<App> with TickerProviderStateMixin {
int _index = 0;
late final AnimationController _animationController;
#override
void initState() {
super.initState();
_animationController = AnimationController(vsync: this)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
setState(() {
++_index;
});
}
});
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
color: Colors.lightBlue,
home: Scaffold(
backgroundColor: Colors.lightBlue,
appBar: AppBar(
title: Text('$_index'),
),
body: SingleChildScrollView(
child: Center(
child: Column(
children: [
Lottie.asset(files[_index % files.length],
controller: _animationController, onLoaded: (composition) {
_animationController
..duration = composition.duration
..reset()
..forward();
}),
],
),
),
),
),
);
}
}

Flutter [animations] OpenContainer how to detect the animation is finished

I'm using OpenContainer animation to open a screen that could display alert dialog upon the opening of the screen - the case of the item the screen is trying to display is no longer valid or deleted.
Because OpenContainer renders the screen during the animation, the alert dialog is displayed several times.
My attempt to address the issue was to modify the OpenContainer buildPage method to return animation status to openBuilder callback. Is there better way to do without modifying OpenContainer code?
child: AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget child) {
if (animation.isCompleted) {
return SizedBox.expand(
child: Material(
color: openColor,
elevation: openElevation,
shape: openShape,
child: Builder(
key: _openBuilderKey,
builder: (BuildContext context) {
return openBuilder(context, closeContainer, false); // added false
},
),
),
);
}
Code to reproduce the issue - https://gist.github.com/MartinJLee/0992a986ad641ef5b4f477fb1ce69249
You can add a listener to your AnimationController something like this
Consider you have an AnimationController like this -
AnimationController _animationController = AnimationController(
vsync: this,
duration: Duration(seconds: 5),
);
Then you can add a Status Listener to this _animationController using the addStatusListener method, something like this -
_animationController.addStatusListener((status) {
if(status == AnimationStatus.completed){
//Do something here
}
});
This listener will be called everytime the animation state changes.
Hope this helps!
I was able to do using the following code on the container for openBuilder.
void initState() {
super.initState();
WidgetsBinding.instance
.addPostFrameCallback((_) => yourFunction(context));
}
see original answers - Flutter: Run method on Widget build complete

How to play the same Flare animation on every tap?

I'm only able to run the animation when the object is tapped in this way?
class _MyHomePageState extends State<MyHomePage> {
bool isOpen = false;
#override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
onTap: () {
setState(() {
isOpen = !isOpen;
});
},
child: FlareActor('assets/heart.flr',
animation: isOpen ? 'run' : 'x'),
),
);
}
}
but what I need is to run the same animation on every tap, this doesn't work:
GestureDetector(
onTap: () {
setState(() {
});
},
child: FlareActor('assets/heart.flr',
animation: 'run'),
)
I also tried this:
GestureDetector(
onTap: () {
setState(() {
isOpen = !isOpen;
});
},
child: FlareActor('assets/heart.flr',
animation: isOpen ? 'run' : 'run'),
)
I recently had the same problem when I was implementing some things. And I got my question answered, You can use the FlareControls and just call play instead of using the keys to play the animation. Original post.
class _MyHomePageState extends State<MyHomePage> {
// Store a reference to some controls for the Flare widget
final FlareControls controls = FlareControls();
void _playSuccessAnimation() {
// Use the controls to trigger an animation.
controls.play("run");
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: FlareActor("assets/heart.flr",
animation: "run",
fit: BoxFit.contain,
alignment: Alignment.center,
// Make sure you use the controls with the Flare Actor widget.
controller: controls),
floatingActionButton: FloatingActionButton(
onPressed: _playSuccessAnimation,
tooltip: 'Play',
child: Icon(Icons.play_arrow),
),
);
}
}
I also have a video explaining how to integrate these animations and replay the same one.