From 8aa67610f7f4f1ad85810cc57c8a3a82aa5ee0ad Mon Sep 17 00:00:00 2001 From: Dan Field Date: Tue, 18 Feb 2020 11:29:55 -0800 Subject: [PATCH] Avoid including a potentially animated invisible image (#50842) * Avoid including a potentially animated invisible image --- .../lib/src/widgets/fade_in_image.dart | 22 +++++++++++++---- .../test/widgets/fade_in_image_test.dart | 24 ++++++++++++++++++- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/packages/flutter/lib/src/widgets/fade_in_image.dart b/packages/flutter/lib/src/widgets/fade_in_image.dart index 5cbc0b23c7..feae786d04 100644 --- a/packages/flutter/lib/src/widgets/fade_in_image.dart +++ b/packages/flutter/lib/src/widgets/fade_in_image.dart @@ -448,7 +448,13 @@ class _AnimatedFadeOutFadeInState extends ImplicitlyAnimatedWidgetState<_Animate tween: ConstantTween(0), weight: widget.fadeInDuration.inMilliseconds.toDouble(), ), - ])); + ]))..addStatusListener((AnimationStatus status) { + if (_placeholderOpacityAnimation.isCompleted) { + // Need to rebuild to remove placeholder now that it is invisibile. + setState(() {}); + } + }); + _targetOpacityAnimation = animation.drive(TweenSequence(>[ TweenSequenceItem( tween: ConstantTween(0), @@ -472,6 +478,15 @@ class _AnimatedFadeOutFadeInState extends ImplicitlyAnimatedWidgetState<_Animate @override Widget build(BuildContext context) { + final Widget target = FadeTransition( + opacity: _targetOpacityAnimation, + child: widget.target, + ); + + if (_placeholderOpacityAnimation.isCompleted) { + return target; + } + return Stack( fit: StackFit.passthrough, alignment: AlignmentDirectional.center, @@ -479,10 +494,7 @@ class _AnimatedFadeOutFadeInState extends ImplicitlyAnimatedWidgetState<_Animate // but it allows the Stack to avoid a call to Directionality.of() textDirection: TextDirection.ltr, children: [ - FadeTransition( - opacity: _targetOpacityAnimation, - child: widget.target, - ), + target, FadeTransition( opacity: _placeholderOpacityAnimation, child: widget.placeholder, diff --git a/packages/flutter/test/widgets/fade_in_image_test.dart b/packages/flutter/test/widgets/fade_in_image_test.dart index 97aaec23f3..5058326dc8 100644 --- a/packages/flutter/test/widgets/fade_in_image_test.dart +++ b/packages/flutter/test/widgets/fade_in_image_test.dart @@ -209,6 +209,28 @@ Future main() async { expect(findFadeInImage(tester).state, same(state)); }); + testWidgets('does not keep the placeholder in the tree if it is invisible', (WidgetTester tester) async { + final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage); + final TestImageProvider imageProvider = TestImageProvider(targetImage); + + await tester.pumpWidget(FadeInImage( + placeholder: placeholderProvider, + image: imageProvider, + fadeOutDuration: animationDuration, + fadeInDuration: animationDuration, + excludeFromSemantics: true, + )); + + placeholderProvider.complete(); + await tester.pumpAndSettle(); + + expect(find.byType(Image), findsNWidgets(2)); + + imageProvider.complete(); + await tester.pumpAndSettle(); + expect(find.byType(Image), findsOneWidget); + }); + testWidgets('re-fades in the image when the target image is updated', (WidgetTester tester) async { final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage); final TestImageProvider imageProvider = TestImageProvider(targetImage); @@ -285,7 +307,7 @@ Future main() async { expect(findFadeInImage(tester).target.opacity, moreOrLessEquals(1)); }); - group(ImageProvider, () { + group('ImageProvider', () { testWidgets('memory placeholder cacheWidth and cacheHeight is passed through', (WidgetTester tester) async { final Uint8List testBytes = Uint8List.fromList(kTransparentImage);