I am currently using a SpriteAnimationWidget from Flame, and I want to have different animations by clicking buttons.
To test out if this works, I am changing the animation parameter of the SpriteAnimationWidget with a button click. After 1 second, the widget has to return to its original animation.
Here is my code:
Future<void> playReaction() async {
setState(() {
isPlaying = true;
state = 'reaction';
});
await Future.delayed(Duration(milliseconds: 1000));
setState(() {
isPlaying = false;
state = 'idle';
});
}
And inside the build, I am returning:
if(state == 'reaction') {
spriteAnimation = reactionSprite!.createAnimation(row: 0, stepTime: 0.1, from: 0, to: 10);
print('reaction');
}
else if(state == 'idle'){
spriteAnimation = sprite!.createAnimation(row: 1, stepTime: 0.2, from: 0, to: 4);
print('idle');
}
return Container(
height: widget.size,
child: Stack(
fit: StackFit.expand,
children: [
Positioned.fill(
child: SpriteAnimationWidget(
animation: spriteAnimation!,
anchor: Anchor.center,
)
),
]
),
);
The variables sprite and reactionSprite are sprite sheets that I preload using SpriteSheet.fromColumnsAndRows().
The problem is that, although my app successfully runs playReaction() and reaches print('reaction'), the SpriteAnimationWidget is still playing the idle animation instead of the reaction animation. I know that it's being rebuilt somehow because I can see that there is a slight unnatural discontinuity in the animation when I hit the button (plus the print messages).
Why is setState() not updating the SpriteAnimationWidget with a new animation parameter?
I am relatively new to Flutter and Flame, so I would love some help or advice.
ps. I tried using the same sprite sheet for the change with a different row (instead of using a completely different sprite sheet as in the code above), but the result was the same.
This was actually a bug, we are fixing it here.
Meanwhile, as a workaround, you can change the key of the widget every time that you change the animation and it should update the widget.
Related
From initState() I call setDefaults(), which builds a Person object, having done this execution should return to initState which calls getPrefs(), an async function.
#override
void initState() {
super.initState();
setDefaults();
getPrefs();
}
getPrefs calls SharedPreferences.instance() and retrieves some data from prefs - hooyah!
This is compared with some fields of the Person object, finally setState() is called to update the screen with a Tile colour, and to render a Banner widget if (usersTile == true).
This never happens, setState never updates the screen. I don't know why.
Future<void> getPrefs() async {
prefs = await SharedPreferences.getInstance();
List<String> neighbourProfilesDownloaded = prefs!.getStringList("downloadedPersonProfiles") ?? [];
neighbourProfilesDownloaded.add(widget._person!.firebaseId!);
prefs!.setStringList("downloadedPersonProfiles", neighbourProfilesDownloaded);
usersTile = widget._person!.firebaseId == prefs!.getString("firebaseId") ?? "";
setState(() {
tileColor = usersTile ? Colors.grey.shade900 : Color(0xFF462c22);
usersTile;
});
}
This question has been asked before. I've tried looking at other solutions on StackOverflow, so far as I can see I'm already following their proposed solutions, to await the asynchronous call then setState after it. And none of the proposed answer have been marked correct anyway.
I've tried assigning the value of usersTile inside setState, no difference.
The Tile colour does sometimes work, however not on screen load. If I scroll down the Tile list, swiping the usersTile out of the viewport, then swiping back up to return to it, I find that DOES update the Tile's colour and Banner. Indicating the Tile property assignments are working but setState is not.
This is not satisfactory, it screams buggy to the user.
I know this can work, because I have another class, another ListView screen in my app which seems to reliably update the screen on load. I've studied the control flow on that screen and can't see how it's different. Quite the boggle.
This is the Card Widget, with properties I want to adjust, from build()
return Card(
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0))),
color: tileColor,
key: ValueKey(widget._person!.firebaseId! + "LoaningTileCard"),
child: usersTile ? Banner(
message: "YOURS",
location: BannerLocation.topEnd,
color: Colors.red,
child: CardContentWidget)
: CardContentWidget);
I think CardContentWidget uses Card in build method, Card has default background color as Colors.white by using CardTheme.of(context).
You may have to reset with transparent or user Container instead.
I'm using this character animation in my Flutter app (with the animated background):
https://rive.app/community/1201-5354-lumberjack-walk-cycle/
(if you hit download on that link you will only get the character animation without the background.
to get the character plus background you have to open it in editor and hit download in there)
Now in Flutter I can run the animation forever like this:
RiveAnimation.asset(
'assets/animations/lumberjack.riv',
),
The Character and the background is animated. Now I want to customize the animation speed. For that I looked at this example: https://github.com/rive-app/rive-flutter/blob/master/example/lib/custom_controller.dart
So I put in this class:
class SpeedController extends SimpleAnimation {
final double speedMultiplier;
SpeedController(
String animationName, {
double mix = 1,
this.speedMultiplier = 1,
}) : super(animationName, mix: mix);
#override
void apply(RuntimeArtboard artboard, double elapsedSeconds) {
if (instance == null || !instance!.keepGoing) {
isActive = false;
}
instance!
..animation.apply(instance!.time, coreContext: artboard, mix: mix)
..advance(elapsedSeconds * speedMultiplier);
}
}
and added the controller to my animation:
RiveAnimation.asset(
animations: const ['idle'],
controllers: [SpeedController('curves', speedMultiplier: 5)],
'assets/animations/lumberjack.riv',
),
Not only is the animation speed not changing (character is animated in default speed), but also is the background not animated at all anymore.
Instead of direct download, open in rive
Now make sure to download on V7
The walk animation is on a different artBoard,
You can find it by
late SpeedController walkController = SpeedController(
'walk',
speedMultiplier: .1,
);
SizedBox(
height: 200,
child: RiveAnimation.asset(
animations: ["walk"],
artboard: "Lumberjack",
controllers: [walkController],
fileLoc,
),
),
Also you need to change the animation name according to rive animation LumberingLumberjack
late SpeedController _controller =
SpeedController('LumberingLumberjack', speedMultiplier: .3);
RiveAnimation.asset(
animations: ["LumberingLumberjack"],
controllers: [_controller],
fileLoc,
),
I have a working code before null safety flutter upgrade. But after the migration, the same code doesn't work.
I had a simple horizontal swipe card, but now something force the swipe to stay on the first position or rebuild. When I remove didChangeDependencies (function I use to load when data change) the swipe is OK. I think when data is load by didChangeDependenciesit refresh new Swiper.children( and force to return always to first index position.
But I can't do without didChangeDependencies, how can I do ?
Here is the package https://pub.dev/packages/flutter_swiper_null_safety/example
Here is my code:
#override
void didChangeDependencies() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final MyInheritedWidgetState state = MyInheritedWidget.of(context);
}
new Swiper.children(
viewportFraction: 0.8,
scale: 0.6,
autoplay: false,
loop: false,
control: new SwiperControl(
size: 25.0,
color: Color(0xffff9a7b),
disableColor: Colors.transparent ,
padding: const EdgeInsets.all (5.0),
),
children: <Widget>[
Card1()
Card2()
Card3()
]
Usually you would need to have a controller or index saved in the state that could hold the state of the downstream widget so on rebuilds the state stayed the same.
After looking at this package it doesn't appear you can pass in an Index or Controller to the widget so it will be rebuilt any time something above it on the stack is rebuilt.
Is it possible to reorganize your page so that the swiper is not under it in the stack?
I have created a Miniplayer in Flutter which looks like this:
Miniplayer(
controller: miniplayerController,
minHeight: 60,
onDismissed: (){
context.read(selectedVideoProvider).state = null;
},
maxHeight: size.height - topPadding - 60,
builder: (height, percentage){
if (selectedVideo == null) {
return const SizedBox.shrink();
}
if (height <= size.width / 16 * 9){
return Row(
children: [
Text('This is a miniplayer'),
IconButton(
onPressed: (){
context.read(selectedVideoProvider).state = null;
},
icon: Icon(Icons.close)
)
],
);
}
return AnotherScreen();
}
);
Also as you can see I have a selectedVideoProvider StateProvider (I use flutter_riverpod for a state management). Also I wrapped my Miniplayer with a Visibility widget:
Consumer(
builder: (context, watch, _){
final selectedVideo = watch(selectedVideoProvider).state;
return Visibility(maintainState: true, visible: selectedVideo != null, child: MyMiniplayerWidget());
}
),
Here is the screen of the app:
Miniplayer appears when I click on these pictures in my ListView. When I click for the first time everything works great (miniplayer appears properly). However when i dismiss my miniplayer I can not open a new miniplayer by clicking on another picture. Only when i go to another tab (tabs are on my bottom navigation bar) and then go back to this screen the miniplayer appears with the MIN size:
But I want it to pop up when I click on the pictures. And again, when I click on the picture for the first time everything works great.
What is wrong with my code? Why miniplayer appears only when I toggle between tabs?
PS. if I make maintainState = false in my Visibility widget miniplayer appears immediately after clicking on the picture, however it appears with the MIN size, but I want it to appear in MAX size. Could you please also explain me why it doesn't work, if I make
context.read(miniPlayerControllerProvider).state.animateToHeight(state: PanelState.MAX);
this when I click on the picture (this is a StateProvider for my miniplayerController) (this line of code makes my miniplayer appear with MAX size when I click on the picture when maintainState = true in Visibility widget)?
I had the same problem with miniplayer and solved it with the following code:
Miniplayer(
valueNotifier: ValueNotifier(MediaQuery.of(context).size.height),
I have a stack widget I am trying to create in which:
1. The user touches the widget button triggering a pointerDown event.
2. Pointer down causes a slider type widget to scale from 0 to 100% from behind the button
3. With finger still down, the user drags to select a value on the scale
4. The value is selected by releasing the finger from the screen i.e. pointerUp.
The widget works fine when I use onTap instead of pointerDown in step 1. But when I try to use a pointer down event, the _open method (that manages the scaleUp of the slider) isn't triggered.
I have followed this example almost exactly: https://fireship.io/lessons/flutter-radial-menu-staggered-animations/, but have tried to change the touch event on the floatingActionbuton like this:
Transform.scale(
scale: widget.scale.value,
child: Listener(
onPointerDown: (PointerDownEvent event) {
print('pointer is down');
setState(() {
_open;
});
},
child: FloatingActionButton(child: Icon(Icons.blur_circular), onPressed: () {})),
)
The print part detects and fires the event, but the _open method does not do anything and the menu part does not appear like in the tutorial link.
I am at a loss.
Since the button below the FloatingActionButton also has a listener, The Listener widget doesn't get's the PointerDown event. So to do that you have to change the behaviour to opaque so that both get the events.
Try Something like this:
Listener(
behavior: HitTestBehavior.opaque,
onPointerDown: (PointerDownEvent details){
},
child: ...,
)