diff --git a/packages/flutter/lib/src/widgets/fade_in_image.dart b/packages/flutter/lib/src/widgets/fade_in_image.dart index b16f5d3305..1837eae0c4 100644 --- a/packages/flutter/lib/src/widgets/fade_in_image.dart +++ b/packages/flutter/lib/src/widgets/fade_in_image.dart @@ -84,6 +84,10 @@ class FadeInImage extends StatefulWidget { this.fadeOutCurve = Curves.easeOut, this.fadeInDuration = const Duration(milliseconds: 700), this.fadeInCurve = Curves.easeIn, + this.color, + this.colorBlendMode, + this.placeholderColor, + this.placeholderColorBlendMode, this.width, this.height, this.fit, @@ -140,6 +144,10 @@ class FadeInImage extends StatefulWidget { this.width, this.height, this.fit, + this.color, + this.colorBlendMode, + this.placeholderColor, + this.placeholderColorBlendMode, this.placeholderFit, this.filterQuality = FilterQuality.low, this.placeholderFilterQuality, @@ -198,6 +206,10 @@ class FadeInImage extends StatefulWidget { this.width, this.height, this.fit, + this.color, + this.colorBlendMode, + this.placeholderColor, + this.placeholderColorBlendMode, this.placeholderFit, this.filterQuality = FilterQuality.low, this.placeholderFilterQuality, @@ -254,6 +266,46 @@ class FadeInImage extends StatefulWidget { /// also affected by the scale factor. final double? width; + /// If non-null, this color is blended with each image pixel using [colorBlendMode]. + /// + /// Color applies to the [image]. + /// + /// See Also: + /// + /// * [placeholderColor], the color which applies to the [placeholder]. + final Color? color; + + /// Used to combine [color] with this [image]. + /// + /// The default is [BlendMode.srcIn]. In terms of the blend mode, [color] is + /// the source and this image is the destination. + /// + /// See also: + /// + /// * [BlendMode], which includes an illustration of the effect of each blend mode. + /// * [placeholderColorBlendMode], the color blend mode which applies to the [placeholder]. + final BlendMode? colorBlendMode; + + /// If non-null, this color is blended with each placeholder image pixel using [placeholderColorBlendMode]. + /// + /// Color applies to the [placeholder]. + /// + /// See Also: + /// + /// * [color], the color which applies to the [image]. + final Color? placeholderColor; + + /// Used to combine [placeholderColor] with the [placeholder] image. + /// + /// The default is [BlendMode.srcIn]. In terms of the blend mode, [placeholderColor] is + /// the source and this placeholder is the destination. + /// + /// See also: + /// + /// * [BlendMode], which includes an illustration of the effect of each blend mode. + /// * [colorBlendMode], the color blend mode which applies to the [image]. + final BlendMode? placeholderColorBlendMode; + /// If non-null, require the image to have this height. /// /// If null, the image will pick a size that best preserves its intrinsic @@ -361,6 +413,8 @@ class _FadeInImageState extends State { ImageErrorWidgetBuilder? errorBuilder, ImageFrameBuilder? frameBuilder, BoxFit? fit, + Color? color, + BlendMode? colorBlendMode, required FilterQuality filterQuality, required Animation opacity, }) { @@ -372,6 +426,8 @@ class _FadeInImageState extends State { width: widget.width, height: widget.height, fit: fit, + color: color, + colorBlendMode: colorBlendMode, filterQuality: filterQuality, alignment: widget.alignment, repeat: widget.repeat, @@ -388,6 +444,8 @@ class _FadeInImageState extends State { errorBuilder: widget.imageErrorBuilder, opacity: _imageAnimation, fit: widget.fit, + color: widget.color, + colorBlendMode: widget.colorBlendMode, filterQuality: widget.filterQuality, frameBuilder: (BuildContext context, Widget child, int? frame, bool wasSynchronouslyLoaded) { if (wasSynchronouslyLoaded || frame != null) { @@ -400,6 +458,8 @@ class _FadeInImageState extends State { image: widget.placeholder, errorBuilder: widget.placeholderErrorBuilder, opacity: _placeholderAnimation, + color: widget.placeholderColor, + colorBlendMode: widget.placeholderColorBlendMode, fit: widget.placeholderFit ?? widget.fit, filterQuality: widget.placeholderFilterQuality ?? widget.filterQuality, ), diff --git a/packages/flutter/test/widgets/fade_in_image_test.dart b/packages/flutter/test/widgets/fade_in_image_test.dart index e12f2ecb76..1d053fb21a 100644 --- a/packages/flutter/test/widgets/fade_in_image_test.dart +++ b/packages/flutter/test/widgets/fade_in_image_test.dart @@ -42,6 +42,8 @@ class FadeInImageElements { double get opacity => rawImage.opacity?.value ?? 1.0; BoxFit? get fit => rawImage.fit; FilterQuality? get filterQuality => rawImage.filterQuality; + Color? get color => rawImage.color; + BlendMode? get colorBlendMode => rawImage.colorBlendMode; } class LoadTestImageProvider extends ImageProvider { @@ -378,6 +380,29 @@ void main() { expect(findFadeInImage(tester).target.opacity, moreOrLessEquals(1)); }); + testWidgetsWithLeakTracking('Image color and colorBlend parameters', (WidgetTester tester) async { + final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage); + final TestImageProvider imageProvider = TestImageProvider(targetImage); + + await tester.pumpWidget(FadeInImage( + placeholder: placeholderProvider, + image: imageProvider, + color: const Color(0xFF00FF00), + colorBlendMode: BlendMode.clear, + placeholderColor: const Color(0xFF0000FF), + placeholderColorBlendMode: BlendMode.modulate, + fadeOutDuration: animationDuration, + fadeInDuration: animationDuration, + excludeFromSemantics: true, + )); + + expect(findFadeInImage(tester).placeholder?.color, const Color(0xFF0000FF)); + expect(findFadeInImage(tester).placeholder?.colorBlendMode, BlendMode.modulate); + await tester.pump(animationDuration); + expect(findFadeInImage(tester).target.color, const Color(0xFF00FF00)); + expect(findFadeInImage(tester).target.colorBlendMode, BlendMode.clear); + }); + group('ImageProvider', () { test('memory placeholder cacheWidth and cacheHeight is passed through', () async {