Avoid including a potentially animated invisible image (#50842) * Avoid including a potentially animated invisible image
diff --git a/packages/flutter/lib/src/widgets/fade_in_image.dart b/packages/flutter/lib/src/widgets/fade_in_image.dart index 5cbc0b2..feae786 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 @@ tween: ConstantTween<double>(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<double>(<TweenSequenceItem<double>>[ TweenSequenceItem<double>( tween: ConstantTween<double>(0), @@ -472,6 +478,15 @@ @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 @@ // but it allows the Stack to avoid a call to Directionality.of() textDirection: TextDirection.ltr, children: <Widget>[ - 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 97aaec2..5058326 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 @@ 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 @@ 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);