mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-01 19:12:31 +08:00
fix: Widgets flickering (#3343)
Fix the flickering in the widgets: `SpriteAnimationWidget`, `SpriteWidget` and `NineTileBoxWidget`.
This commit is contained in:
@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -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),
|
||||
),
|
||||
|
||||
@ -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';
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
@ -91,6 +91,34 @@ 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>(
|
||||
@ -98,16 +126,16 @@ class NineTileBoxWidget extends StatelessWidget {
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -68,6 +68,34 @@ 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>(
|
||||
@ -75,13 +103,13 @@ class SpriteWidget extends StatelessWidget {
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@ Future<void> main() async {
|
||||
const executionCount = 10;
|
||||
final frames = List.generate(5, (_) => Sprite(image));
|
||||
final animation1 = SpriteAnimation.spriteList(frames, stepTime: 0.1);
|
||||
final animation2 = SpriteAnimation.spriteList(frames, stepTime: 0.1);
|
||||
final animation2 = SpriteAnimation.spriteList(frames, stepTime: 0.2);
|
||||
final animationTicker1 = SpriteAnimationTicker(animation1);
|
||||
final animationTicker2 = SpriteAnimationTicker(animation2);
|
||||
|
||||
@ -98,10 +98,13 @@ Future<void> main() async {
|
||||
animationTicker: animationTicker1,
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(animationTicker1.onComplete, isNotNull);
|
||||
expect(animationTicker2.onComplete, isNull);
|
||||
|
||||
await tester.pump();
|
||||
|
||||
expect(animation1Started, true);
|
||||
|
||||
// This will call didUpdateWidget lifecycle
|
||||
@ -111,6 +114,9 @@ Future<void> main() async {
|
||||
animationTicker: animationTicker2,
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
|
||||
expect(animationTicker1.onComplete, isNull);
|
||||
expect(animationTicker2.onComplete, isNotNull);
|
||||
|
||||
@ -191,5 +197,334 @@ Future<void> main() async {
|
||||
expect(onCompleteCalled, isTrue);
|
||||
},
|
||||
);
|
||||
|
||||
group('when the image 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);
|
||||
|
||||
final spriteAnimationData = SpriteAnimationData.sequenced(
|
||||
amount: 1,
|
||||
stepTime: 0.1,
|
||||
textureSize: Vector2(16, 16),
|
||||
loop: false,
|
||||
);
|
||||
|
||||
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'),
|
||||
),
|
||||
SpriteAnimationWidget.asset(
|
||||
path: flag ? imagePath2 : imagePath,
|
||||
data: spriteAnimationData,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
|
||||
var internalWidget = tester.widget<InternalSpriteAnimationWidget>(
|
||||
find.byType(InternalSpriteAnimationWidget),
|
||||
);
|
||||
|
||||
expect(internalWidget.animation.frames.first.sprite.image, image);
|
||||
|
||||
await tester.tap(find.byType(ElevatedButton));
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
|
||||
internalWidget = tester.widget<InternalSpriteAnimationWidget>(
|
||||
find.byType(InternalSpriteAnimationWidget),
|
||||
);
|
||||
|
||||
expect(internalWidget.animation.frames.first.sprite.image, image2);
|
||||
});
|
||||
});
|
||||
|
||||
group('when the sprite data changes', () {
|
||||
group('when the frame length changes', () {
|
||||
testWidgets('updates the widget', (tester) async {
|
||||
const imagePath = 'test_path_2';
|
||||
|
||||
final image = await generateImage(100, 100);
|
||||
|
||||
Flame.images.add(imagePath, image);
|
||||
|
||||
final spriteAnimationData = SpriteAnimationData.sequenced(
|
||||
amount: 1,
|
||||
stepTime: 0.1,
|
||||
textureSize: Vector2(16, 16),
|
||||
loop: false,
|
||||
);
|
||||
|
||||
final spriteAnimationData2 = SpriteAnimationData.sequenced(
|
||||
amount: 2,
|
||||
stepTime: 0.1,
|
||||
textureSize: Vector2(16, 16),
|
||||
loop: false,
|
||||
);
|
||||
|
||||
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'),
|
||||
),
|
||||
SpriteAnimationWidget.asset(
|
||||
path: imagePath,
|
||||
data: flag
|
||||
? spriteAnimationData2
|
||||
: spriteAnimationData,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
|
||||
var internalWidget = tester.widget<InternalSpriteAnimationWidget>(
|
||||
find.byType(InternalSpriteAnimationWidget),
|
||||
);
|
||||
|
||||
expect(internalWidget.animation.frames, hasLength(1));
|
||||
|
||||
await tester.tap(find.byType(ElevatedButton));
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
|
||||
internalWidget = tester.widget<InternalSpriteAnimationWidget>(
|
||||
find.byType(InternalSpriteAnimationWidget),
|
||||
);
|
||||
|
||||
expect(internalWidget.animation.frames, hasLength(2));
|
||||
});
|
||||
});
|
||||
|
||||
group('when a single frame changes', () {
|
||||
testWidgets('updates the widget', (tester) async {
|
||||
const imagePath = 'test_path_2';
|
||||
|
||||
final image = await generateImage(100, 100);
|
||||
|
||||
Flame.images.add(imagePath, image);
|
||||
|
||||
final spriteAnimationData = SpriteAnimationData.sequenced(
|
||||
amount: 1,
|
||||
stepTime: 0.1,
|
||||
textureSize: Vector2(16, 16),
|
||||
loop: false,
|
||||
);
|
||||
|
||||
final spriteAnimationData2 = SpriteAnimationData.sequenced(
|
||||
amount: 1,
|
||||
stepTime: 0.1,
|
||||
textureSize: Vector2(12, 12),
|
||||
loop: false,
|
||||
);
|
||||
|
||||
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'),
|
||||
),
|
||||
SpriteAnimationWidget.asset(
|
||||
path: imagePath,
|
||||
data: flag
|
||||
? spriteAnimationData2
|
||||
: spriteAnimationData,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
|
||||
var internalWidget = tester.widget<InternalSpriteAnimationWidget>(
|
||||
find.byType(InternalSpriteAnimationWidget),
|
||||
);
|
||||
|
||||
expect(
|
||||
internalWidget.animation.frames.first.sprite.srcSize,
|
||||
Vector2.all(16),
|
||||
);
|
||||
|
||||
await tester.tap(find.byType(ElevatedButton));
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
|
||||
internalWidget = tester.widget<InternalSpriteAnimationWidget>(
|
||||
find.byType(InternalSpriteAnimationWidget),
|
||||
);
|
||||
|
||||
expect(
|
||||
internalWidget.animation.frames.first.sprite.srcSize,
|
||||
Vector2.all(12),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('when looping changes', () {
|
||||
testWidgets('updates the widget', (tester) async {
|
||||
const imagePath = 'test_path_2';
|
||||
|
||||
final image = await generateImage(100, 100);
|
||||
|
||||
Flame.images.add(imagePath, image);
|
||||
|
||||
final spriteAnimationData = SpriteAnimationData.sequenced(
|
||||
amount: 1,
|
||||
stepTime: 0.1,
|
||||
textureSize: Vector2(16, 16),
|
||||
loop: false,
|
||||
);
|
||||
|
||||
final spriteAnimationData2 = SpriteAnimationData.sequenced(
|
||||
amount: 1,
|
||||
stepTime: 0.1,
|
||||
textureSize: Vector2(16, 16),
|
||||
);
|
||||
|
||||
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'),
|
||||
),
|
||||
SpriteAnimationWidget.asset(
|
||||
path: imagePath,
|
||||
data: flag
|
||||
? spriteAnimationData2
|
||||
: spriteAnimationData,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
|
||||
var internalWidget = tester.widget<InternalSpriteAnimationWidget>(
|
||||
find.byType(InternalSpriteAnimationWidget),
|
||||
);
|
||||
|
||||
expect(
|
||||
internalWidget.animation.loop,
|
||||
isFalse,
|
||||
);
|
||||
|
||||
await tester.tap(find.byType(ElevatedButton));
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
|
||||
internalWidget = tester.widget<InternalSpriteAnimationWidget>(
|
||||
find.byType(InternalSpriteAnimationWidget),
|
||||
);
|
||||
|
||||
expect(
|
||||
internalWidget.animation.loop,
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/flame.dart';
|
||||
import 'package:flame/widgets.dart';
|
||||
import 'package:flame_test/flame_test.dart';
|
||||
@ -51,5 +52,60 @@ Future<void> main() async {
|
||||
expect(spriteWidgetFinder, findsOneWidget);
|
||||
},
|
||||
);
|
||||
|
||||
group('when the sprite changes', () {
|
||||
testWidgets('updates the sprite widget', (tester) async {
|
||||
const imagePath = 'test_path_2';
|
||||
Flame.images.add(imagePath, await generateImage(100, 100));
|
||||
|
||||
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'),
|
||||
),
|
||||
SpriteWidget.asset(
|
||||
path: imagePath,
|
||||
srcPosition: flag ? Vector2(10, 10) : Vector2(0, 0),
|
||||
loadingBuilder: (_) => const LoadingWidget(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
var internalSpriteWidgetFinder = tester
|
||||
.widget<InternalSpriteWidget>(find.byType(InternalSpriteWidget));
|
||||
|
||||
expect(internalSpriteWidgetFinder.sprite.srcPosition, Vector2(0, 0));
|
||||
|
||||
await tester.tap(find.byType(ElevatedButton));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
internalSpriteWidgetFinder = tester
|
||||
.widget<InternalSpriteWidget>(find.byType(InternalSpriteWidget));
|
||||
|
||||
expect(internalSpriteWidgetFinder.sprite.srcPosition, Vector2(10, 10));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,14 +1,22 @@
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/extensions.dart';
|
||||
Future<Image> generateImage([int width = 1, int height = 1]) {
|
||||
final recorder = PictureRecorder();
|
||||
final canvas = Canvas(recorder);
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(
|
||||
0,
|
||||
0,
|
||||
height.toDouble(),
|
||||
width.toDouble(),
|
||||
),
|
||||
Paint()..color = const Color(0xFFFFFFFF),
|
||||
);
|
||||
|
||||
Future<Image> generateImage() {
|
||||
final data = Uint8List(4);
|
||||
for (var i = 0; i < data.length; i += 4) {
|
||||
data[i] = 255;
|
||||
data[i + 1] = 255;
|
||||
data[i + 2] = 255;
|
||||
data[i + 3] = 255;
|
||||
}
|
||||
return ImageExtension.fromPixels(data, 1, 1);
|
||||
final picture = recorder.endRecording();
|
||||
final image = picture.toImage(
|
||||
width,
|
||||
height,
|
||||
);
|
||||
return image;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user