Skip to content

Commit

Permalink
fix: Widgets flickering (#3343)
Browse files Browse the repository at this point in the history
Fix the flickering in the widgets: `SpriteAnimationWidget`,
`SpriteWidget` and `NineTileBoxWidget`.
  • Loading branch information
erickzanardo authored Oct 16, 2024
1 parent 7311d03 commit ff170dc
Show file tree
Hide file tree
Showing 10 changed files with 664 additions and 36 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/widgets.dart';
import 'package:flutter/material.dart';

var _opacity = 1.0;

Widget nineTileBoxBuilderWithAnimation(DashbookContext ctx) {
return StatefulBuilder(
builder: (context, setState) {
return Column(
children: [
const SizedBox(height: 8),
ElevatedButton(
onPressed: () {
setState(() {
_opacity = _opacity == 1.0 ? 0.0 : 1.0;
});
},
child: const Text('Toggle'),
),
const SizedBox(height: 8),
AnimatedOpacity(
duration: const Duration(seconds: 2),
opacity: _opacity,
child: NineTileBoxWidget.asset(
width: 400,
height: 400,
path: 'nine-box.png',
tileSize: 22,
destTileSize: 50,
child: const Center(
child: Text(
'Cool label',
style: TextStyle(
color: Color(0xFF000000),
),
),
),
),
),
],
);
},
);
}
10 changes: 8 additions & 2 deletions examples/lib/stories/widgets/partial_sprite_widget_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,14 @@ Widget partialSpriteWidgetBuilder(DashbookContext ctx) {
decoration: BoxDecoration(border: Border.all(color: Colors.amber)),
child: SpriteWidget.asset(
path: 'bomb_ptero.png',
srcPosition: Vector2(48, 0),
srcSize: Vector2(48, 32),
srcPosition: Vector2(
ctx.numberProperty('srcPosition.x', 48),
ctx.numberProperty('srcPosition.y', 0),
),
srcSize: Vector2(
ctx.numberProperty('srcSize.x', 48),
ctx.numberProperty('srcSize.y', 32),
),
anchor: Anchor.valueOf(
ctx.listProperty('anchor', 'center', anchorOptions),
),
Expand Down
10 changes: 10 additions & 0 deletions examples/lib/stories/widgets/widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:dashbook/dashbook.dart';
import 'package:examples/commons/commons.dart';
import 'package:examples/stories/widgets/custom_painter_example.dart';
import 'package:examples/stories/widgets/nine_tile_box_example.dart';
import 'package:examples/stories/widgets/nine_tile_box_example_with_animation.dart';
import 'package:examples/stories/widgets/partial_sprite_widget_example.dart';
import 'package:examples/stories/widgets/sprite_animation_widget_example.dart';
import 'package:examples/stories/widgets/sprite_button_example.dart';
Expand All @@ -21,6 +22,15 @@ void addWidgetsStories(Dashbook dashbook) {
out the settings on the pen icon.
''',
)
..add(
'Nine Tile Box (With animation widgets)',
nineTileBoxBuilderWithAnimation,
codeLink: baseLink('widgets/nine_tile_box_example_with_animation.dart'),
info: '''
Similar to the Nine Tile Box example, but here a NineTileBoxWidget is composed
with Flutter's AnimatedOpacity.
''',
)
..add(
'Sprite Button',
spriteButtonBuilder,
Expand Down
64 changes: 57 additions & 7 deletions packages/flame/lib/src/widgets/animation_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import 'package:flutter/material.dart' hide Animation;
export '../sprite_animation.dart';

/// A [StatelessWidget] that renders a [SpriteAnimation]
class SpriteAnimationWidget extends StatelessWidget {
class SpriteAnimationWidget extends StatefulWidget {
/// The positioning [Anchor].
final Anchor anchor;

Expand Down Expand Up @@ -67,24 +67,74 @@ class SpriteAnimationWidget extends StatelessWidget {
}) : _animationFuture = SpriteAnimation.load(path, data, images: images),
_animationTicker = null;

@override
State<SpriteAnimationWidget> createState() => _SpriteAnimationWidgetState();
}

class _SpriteAnimationWidgetState extends State<SpriteAnimationWidget> {
late FutureOr<SpriteAnimation> _animationFuture = widget._animationFuture;
late SpriteAnimationTicker? _animationTicker = widget._animationTicker;

@override
void didUpdateWidget(covariant SpriteAnimationWidget oldWidget) {
super.didUpdateWidget(oldWidget);

_updateAnimation(
oldWidget._animationFuture,
widget._animationFuture,
oldWidget._animationTicker,
widget._animationTicker,
);
}

Future<void> _updateAnimation(
FutureOr<SpriteAnimation> oldFutureValue,
FutureOr<SpriteAnimation> newFutureValue,
SpriteAnimationTicker? oldTicker,
SpriteAnimationTicker? newTicker,
) async {
final oldValue = await oldFutureValue;
final newValue = await newFutureValue;

final areFramesDifferent = oldValue != newValue ||
oldValue.frames.length != newValue.frames.length ||
oldValue.frames.fold(
true,
(previous, frame) {
final newFrame = newValue.frames[oldValue.frames.indexOf(frame)];

return previous &&
(frame.sprite.image == newFrame.sprite.image ||
frame.sprite.src == newFrame.sprite.src);
},
);

if (areFramesDifferent || oldTicker != newTicker) {
setState(() {
_animationFuture = newFutureValue;
_animationTicker = newTicker;
});
}
}

@override
Widget build(BuildContext context) {
return BaseFutureBuilder<SpriteAnimation>(
future: _animationFuture,
builder: (_, spriteAnimation) {
final ticker = _animationTicker ?? spriteAnimation.createTicker();
ticker.completed.then((_) => onComplete?.call());
ticker.completed.then((_) => widget.onComplete?.call());

return InternalSpriteAnimationWidget(
animation: spriteAnimation,
animationTicker: ticker,
anchor: anchor,
playing: playing,
paint: paint,
anchor: widget.anchor,
playing: widget.playing,
paint: widget.paint,
);
},
errorBuilder: errorBuilder,
loadingBuilder: loadingBuilder,
errorBuilder: widget.errorBuilder,
loadingBuilder: widget.loadingBuilder,
);
}
}
Expand Down
46 changes: 37 additions & 9 deletions packages/flame/lib/src/widgets/nine_tile_box.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class _Painter extends CustomPainter {
}

/// A [StatelessWidget] that renders NineTileBox
class NineTileBoxWidget extends StatelessWidget {
class NineTileBoxWidget extends StatefulWidget {
final FutureOr<Image> _imageFuture;

/// The size of the tile on the image
Expand Down Expand Up @@ -91,23 +91,51 @@ class NineTileBoxWidget extends StatelessWidget {
super.key,
}) : _imageFuture = (images ?? Flame.images).load(path);

@override
State<NineTileBoxWidget> createState() => _NineTileBoxWidgetState();
}

class _NineTileBoxWidgetState extends State<NineTileBoxWidget> {
late FutureOr<Image> _imageFuture = widget._imageFuture;

@override
void didUpdateWidget(covariant NineTileBoxWidget oldWidget) {
super.didUpdateWidget(oldWidget);

_updateNineTileBox(widget._imageFuture, oldWidget._imageFuture);
}

Future<void> _updateNineTileBox(
FutureOr<Image> imageFuture,
FutureOr<Image> oldImageFuture,
) async {
final image = await imageFuture;
final oldImage = await oldImageFuture;

if (image != oldImage) {
setState(() {
_imageFuture = imageFuture;
});
}
}

@override
Widget build(BuildContext context) {
return BaseFutureBuilder<Image>(
future: _imageFuture,
builder: (_, image) {
return InternalNineTileBox(
image: image,
tileSize: tileSize,
destTileSize: destTileSize,
width: width,
height: height,
padding: padding,
child: child,
tileSize: widget.tileSize,
destTileSize: widget.destTileSize,
width: widget.width,
height: widget.height,
padding: widget.padding,
child: widget.child,
);
},
errorBuilder: errorBuilder,
loadingBuilder: loadingBuilder,
errorBuilder: widget.errorBuilder,
loadingBuilder: widget.loadingBuilder,
);
}
}
Expand Down
40 changes: 34 additions & 6 deletions packages/flame/lib/src/widgets/sprite_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export '../sprite.dart';

/// A [StatelessWidget] which renders a Sprite
/// To render an animation, use [SpriteAnimationWidget].
class SpriteWidget extends StatelessWidget {
class SpriteWidget extends StatefulWidget {
/// The positioning [Anchor]
final Anchor anchor;

Expand Down Expand Up @@ -68,20 +68,48 @@ class SpriteWidget extends StatelessWidget {
images: images,
);

@override
State<SpriteWidget> createState() => _SpriteWidgetState();
}

class _SpriteWidgetState extends State<SpriteWidget> {
late FutureOr<Sprite> _spriteFuture = widget._spriteFuture;

@override
void didUpdateWidget(covariant SpriteWidget oldWidget) {
super.didUpdateWidget(oldWidget);

_updateSprite(oldWidget._spriteFuture, widget._spriteFuture);
}

Future<void> _updateSprite(
FutureOr<Sprite> oldFutureValue,
FutureOr<Sprite> newFutureValue,
) async {
final oldValue = await oldFutureValue;
final newValue = await newFutureValue;

if (oldValue.image != newValue.image || oldValue.src != newValue.src) {
setState(() {
_spriteFuture = newFutureValue;
});
}
}

@override
Widget build(BuildContext context) {
return BaseFutureBuilder<Sprite>(
future: _spriteFuture,
builder: (_, sprite) {
return InternalSpriteWidget(
sprite: sprite,
anchor: anchor,
angle: angle,
paint: paint,
anchor: widget.anchor,
angle: widget.angle,
paint: widget.paint,
);
},
errorBuilder: errorBuilder,
loadingBuilder: loadingBuilder,
errorBuilder: widget.errorBuilder,
loadingBuilder: widget.loadingBuilder,
);
}
}
Expand Down
62 changes: 62 additions & 0 deletions packages/flame/test/widgets/nine_tile_box_widget_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,67 @@ Future<void> main() async {
expect(nineTileBoxWidgetFinder, findsOneWidget);
},
);

group('when the nine tile box changes', () {
testWidgets('updates the widget', (tester) async {
const imagePath = 'test_path_2';
const imagePath2 = 'test_path_3';

final image = await generateImage(100, 100);
final image2 = await generateImage(100, 102);

Flame.images.add(imagePath, image);
Flame.images.add(imagePath2, image2);

var flag = false;
await tester.pumpWidget(
StatefulBuilder(
builder: (context, setState) {
return MaterialApp(
home: Scaffold(
body: SizedBox(
height: 200,
width: 200,
child: Wrap(
children: [
ElevatedButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: const Text('Change sprite'),
),
NineTileBoxWidget.asset(
path: flag ? imagePath2 : imagePath,
tileSize: 10,
destTileSize: 10,
loadingBuilder: (_) => const LoadingWidget(),
),
],
),
),
),
);
},
),
);

await tester.pumpAndSettle();

var internalWidget = tester
.widget<InternalNineTileBox>(find.byType(InternalNineTileBox));

expect(internalWidget.image, image);

await tester.tap(find.byType(ElevatedButton));
await tester.pumpAndSettle();

internalWidget = tester
.widget<InternalNineTileBox>(find.byType(InternalNineTileBox));

expect(internalWidget.image, image2);
});
});
});
}
Loading

0 comments on commit ff170dc

Please sign in to comment.