Unverified Commit a84bb4eb authored by Dwayne Slater's avatar Dwayne Slater Committed by GitHub

Add ability to specify Image widget opacity as an animation (#83379)

parent 59f6cc7a
...@@ -360,6 +360,8 @@ void debugFlushLastFrameImageSizeInfo() { ...@@ -360,6 +360,8 @@ void debugFlushLastFrameImageSizeInfo() {
/// ///
/// * `scale`: The number of image pixels for each logical pixel. /// * `scale`: The number of image pixels for each logical pixel.
/// ///
/// * `opacity`: The opacity to paint the image onto the canvas with.
///
/// * `colorFilter`: If non-null, the color filter to apply when painting the /// * `colorFilter`: If non-null, the color filter to apply when painting the
/// image. /// image.
/// ///
...@@ -420,6 +422,7 @@ void paintImage({ ...@@ -420,6 +422,7 @@ void paintImage({
required ui.Image image, required ui.Image image,
String? debugImageLabel, String? debugImageLabel,
double scale = 1.0, double scale = 1.0,
double opacity = 1.0,
ColorFilter? colorFilter, ColorFilter? colorFilter,
BoxFit? fit, BoxFit? fit,
Alignment alignment = Alignment.center, Alignment alignment = Alignment.center,
...@@ -473,6 +476,7 @@ void paintImage({ ...@@ -473,6 +476,7 @@ void paintImage({
final Paint paint = Paint()..isAntiAlias = isAntiAlias; final Paint paint = Paint()..isAntiAlias = isAntiAlias;
if (colorFilter != null) if (colorFilter != null)
paint.colorFilter = colorFilter; paint.colorFilter = colorFilter;
paint.color = Color.fromRGBO(0, 0, 0, opacity);
paint.filterQuality = filterQuality; paint.filterQuality = filterQuality;
paint.invertColors = invertColors; paint.invertColors = invertColors;
final double halfWidthDelta = (outputSize.width - destinationSize.width) / 2.0; final double halfWidthDelta = (outputSize.width - destinationSize.width) / 2.0;
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
import 'dart:ui' as ui show Image; import 'dart:ui' as ui show Image;
import 'package:flutter/animation.dart';
import 'box.dart'; import 'box.dart';
import 'object.dart'; import 'object.dart';
...@@ -31,6 +33,7 @@ class RenderImage extends RenderBox { ...@@ -31,6 +33,7 @@ class RenderImage extends RenderBox {
double? height, double? height,
double scale = 1.0, double scale = 1.0,
Color? color, Color? color,
Animation<double>? opacity,
BlendMode? colorBlendMode, BlendMode? colorBlendMode,
BoxFit? fit, BoxFit? fit,
AlignmentGeometry alignment = Alignment.center, AlignmentGeometry alignment = Alignment.center,
...@@ -52,6 +55,7 @@ class RenderImage extends RenderBox { ...@@ -52,6 +55,7 @@ class RenderImage extends RenderBox {
_height = height, _height = height,
_scale = scale, _scale = scale,
_color = color, _color = color,
_opacity = opacity,
_colorBlendMode = colorBlendMode, _colorBlendMode = colorBlendMode,
_fit = fit, _fit = fit,
_alignment = alignment, _alignment = alignment,
...@@ -163,6 +167,21 @@ class RenderImage extends RenderBox { ...@@ -163,6 +167,21 @@ class RenderImage extends RenderBox {
markNeedsPaint(); markNeedsPaint();
} }
/// If non-null, the value from the [Animation] is multiplied with the opacity
/// of each image pixel before painting onto the canvas.
Animation<double>? get opacity => _opacity;
Animation<double>? _opacity;
set opacity(Animation<double>? value) {
if (value == _opacity)
return;
if (attached)
_opacity?.removeListener(markNeedsPaint);
_opacity = value;
if (attached)
value?.addListener(markNeedsPaint);
}
/// Used to set the filterQuality of the image /// Used to set the filterQuality of the image
/// Use the [FilterQuality.low] quality setting to scale the image, which corresponds to /// Use the [FilterQuality.low] quality setting to scale the image, which corresponds to
/// bilinear interpolation, rather than the default [FilterQuality.none] which corresponds /// bilinear interpolation, rather than the default [FilterQuality.none] which corresponds
...@@ -381,6 +400,18 @@ class RenderImage extends RenderBox { ...@@ -381,6 +400,18 @@ class RenderImage extends RenderBox {
size = _sizeForConstraints(constraints); size = _sizeForConstraints(constraints);
} }
@override
void attach(covariant PipelineOwner owner) {
super.attach(owner);
_opacity?.addListener(markNeedsPaint);
}
@override
void detach() {
_opacity?.removeListener(markNeedsPaint);
super.detach();
}
@override @override
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
if (_image == null) if (_image == null)
...@@ -394,6 +425,7 @@ class RenderImage extends RenderBox { ...@@ -394,6 +425,7 @@ class RenderImage extends RenderBox {
image: _image!, image: _image!,
debugImageLabel: debugImageLabel, debugImageLabel: debugImageLabel,
scale: _scale, scale: _scale,
opacity: _opacity?.value ?? 1.0,
colorFilter: _colorFilter, colorFilter: _colorFilter,
fit: _fit, fit: _fit,
alignment: _resolvedAlignment!, alignment: _resolvedAlignment!,
...@@ -414,6 +446,7 @@ class RenderImage extends RenderBox { ...@@ -414,6 +446,7 @@ class RenderImage extends RenderBox {
properties.add(DoubleProperty('height', height, defaultValue: null)); properties.add(DoubleProperty('height', height, defaultValue: null));
properties.add(DoubleProperty('scale', scale, defaultValue: 1.0)); properties.add(DoubleProperty('scale', scale, defaultValue: 1.0));
properties.add(ColorProperty('color', color, defaultValue: null)); properties.add(ColorProperty('color', color, defaultValue: null));
properties.add(DiagnosticsProperty<Animation<double>?>('opacity', opacity, defaultValue: null));
properties.add(EnumProperty<BlendMode>('colorBlendMode', colorBlendMode, defaultValue: null)); properties.add(EnumProperty<BlendMode>('colorBlendMode', colorBlendMode, defaultValue: null));
properties.add(EnumProperty<BoxFit>('fit', fit, defaultValue: null)); properties.add(EnumProperty<BoxFit>('fit', fit, defaultValue: null));
properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null)); properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:ui' as ui show Image, ImageFilter, TextHeightBehavior; import 'dart:ui' as ui show Image, ImageFilter, TextHeightBehavior;
import 'package:flutter/animation.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
...@@ -5915,6 +5916,7 @@ class RawImage extends LeafRenderObjectWidget { ...@@ -5915,6 +5916,7 @@ class RawImage extends LeafRenderObjectWidget {
this.height, this.height,
this.scale = 1.0, this.scale = 1.0,
this.color, this.color,
this.opacity,
this.colorBlendMode, this.colorBlendMode,
this.fit, this.fit,
this.alignment = Alignment.center, this.alignment = Alignment.center,
...@@ -5961,6 +5963,13 @@ class RawImage extends LeafRenderObjectWidget { ...@@ -5961,6 +5963,13 @@ class RawImage extends LeafRenderObjectWidget {
/// If non-null, this color is blended with each image pixel using [colorBlendMode]. /// If non-null, this color is blended with each image pixel using [colorBlendMode].
final Color? color; final Color? color;
/// If non-null, the value from the [Animation] is multiplied with the opacity
/// of each image pixel before painting onto the canvas.
///
/// This is more efficient than using [FadeTransition] to change the opacity
/// of an image.
final Animation<double>? opacity;
/// Used to set the filterQuality of the image /// Used to set the filterQuality of the image
/// Use the "low" quality setting to scale the image, which corresponds to /// Use the "low" quality setting to scale the image, which corresponds to
/// bilinear interpolation, rather than the default "none" which corresponds /// bilinear interpolation, rather than the default "none" which corresponds
...@@ -6070,6 +6079,7 @@ class RawImage extends LeafRenderObjectWidget { ...@@ -6070,6 +6079,7 @@ class RawImage extends LeafRenderObjectWidget {
height: height, height: height,
scale: scale, scale: scale,
color: color, color: color,
opacity: opacity,
colorBlendMode: colorBlendMode, colorBlendMode: colorBlendMode,
fit: fit, fit: fit,
alignment: alignment, alignment: alignment,
...@@ -6122,6 +6132,7 @@ class RawImage extends LeafRenderObjectWidget { ...@@ -6122,6 +6132,7 @@ class RawImage extends LeafRenderObjectWidget {
properties.add(DoubleProperty('height', height, defaultValue: null)); properties.add(DoubleProperty('height', height, defaultValue: null));
properties.add(DoubleProperty('scale', scale, defaultValue: 1.0)); properties.add(DoubleProperty('scale', scale, defaultValue: 1.0));
properties.add(ColorProperty('color', color, defaultValue: null)); properties.add(ColorProperty('color', color, defaultValue: null));
properties.add(DiagnosticsProperty<Animation<double>?>('opacity', opacity, defaultValue: null));
properties.add(EnumProperty<BlendMode>('colorBlendMode', colorBlendMode, defaultValue: null)); properties.add(EnumProperty<BlendMode>('colorBlendMode', colorBlendMode, defaultValue: null));
properties.add(EnumProperty<BoxFit>('fit', fit, defaultValue: null)); properties.add(EnumProperty<BoxFit>('fit', fit, defaultValue: null));
properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null)); properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
......
...@@ -11,7 +11,6 @@ import 'basic.dart'; ...@@ -11,7 +11,6 @@ import 'basic.dart';
import 'framework.dart'; import 'framework.dart';
import 'image.dart'; import 'image.dart';
import 'implicit_animations.dart'; import 'implicit_animations.dart';
import 'transitions.dart';
// Examples can assume: // Examples can assume:
// late Uint8List bytes; // late Uint8List bytes;
...@@ -62,7 +61,7 @@ import 'transitions.dart'; ...@@ -62,7 +61,7 @@ import 'transitions.dart';
/// ) /// )
/// ``` /// ```
/// {@end-tool} /// {@end-tool}
class FadeInImage extends StatelessWidget { class FadeInImage extends StatefulWidget {
/// Creates a widget that displays a [placeholder] while an [image] is loading, /// Creates a widget that displays a [placeholder] while an [image] is loading,
/// then fades-out the placeholder and fades-in the image. /// then fades-out the placeholder and fades-in the image.
/// ///
...@@ -356,22 +355,42 @@ class FadeInImage extends StatelessWidget { ...@@ -356,22 +355,42 @@ class FadeInImage extends StatelessWidget {
/// once the image has loaded. /// once the image has loaded.
final String? imageSemanticLabel; final String? imageSemanticLabel;
@override
State<FadeInImage> createState() => _FadeInImageState();
}
class _FadeInImageState extends State<FadeInImage> {
static const Animation<double> _kOpaqueAnimation = AlwaysStoppedAnimation<double>(1.0);
// These ProxyAnimations are changed to the fade in animation by
// [_AnimatedFadeOutFadeInState]. Otherwise these animations are reset to
// their defaults by [_resetAnimations].
final ProxyAnimation _imageAnimation = ProxyAnimation(_kOpaqueAnimation);
final ProxyAnimation _placeholderAnimation = ProxyAnimation(_kOpaqueAnimation);
void _resetAnimations() {
_imageAnimation.parent = _kOpaqueAnimation;
_placeholderAnimation.parent = _kOpaqueAnimation;
}
Image _image({ Image _image({
required ImageProvider image, required ImageProvider image,
ImageErrorWidgetBuilder? errorBuilder, ImageErrorWidgetBuilder? errorBuilder,
ImageFrameBuilder? frameBuilder, ImageFrameBuilder? frameBuilder,
required Animation<double> opacity,
}) { }) {
assert(image != null); assert(image != null);
return Image( return Image(
image: image, image: image,
errorBuilder: errorBuilder, errorBuilder: errorBuilder,
frameBuilder: frameBuilder, frameBuilder: frameBuilder,
width: width, opacity: opacity,
height: height, width: widget.width,
fit: fit, height: widget.height,
alignment: alignment, fit: widget.fit,
repeat: repeat, alignment: widget.alignment,
matchTextDirection: matchTextDirection, repeat: widget.repeat,
matchTextDirection: widget.matchTextDirection,
gaplessPlayback: true, gaplessPlayback: true,
excludeFromSemantics: true, excludeFromSemantics: true,
); );
...@@ -380,28 +399,37 @@ class FadeInImage extends StatelessWidget { ...@@ -380,28 +399,37 @@ class FadeInImage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget result = _image( Widget result = _image(
image: image, image: widget.image,
errorBuilder: imageErrorBuilder, errorBuilder: widget.imageErrorBuilder,
opacity: _imageAnimation,
frameBuilder: (BuildContext context, Widget child, int? frame, bool wasSynchronouslyLoaded) { frameBuilder: (BuildContext context, Widget child, int? frame, bool wasSynchronouslyLoaded) {
if (wasSynchronouslyLoaded) if (wasSynchronouslyLoaded) {
_resetAnimations();
return child; return child;
}
return _AnimatedFadeOutFadeIn( return _AnimatedFadeOutFadeIn(
target: child, target: child,
placeholder: _image(image: placeholder, errorBuilder: placeholderErrorBuilder), targetProxyAnimation: _imageAnimation,
placeholder: _image(
image: widget.placeholder,
errorBuilder: widget.placeholderErrorBuilder,
opacity: _placeholderAnimation,
),
placeholderProxyAnimation: _placeholderAnimation,
isTargetLoaded: frame != null, isTargetLoaded: frame != null,
fadeInDuration: fadeInDuration, fadeInDuration: widget.fadeInDuration,
fadeOutDuration: fadeOutDuration, fadeOutDuration: widget.fadeOutDuration,
fadeInCurve: fadeInCurve, fadeInCurve: widget.fadeInCurve,
fadeOutCurve: fadeOutCurve, fadeOutCurve: widget.fadeOutCurve,
); );
}, },
); );
if (!excludeFromSemantics) { if (!widget.excludeFromSemantics) {
result = Semantics( result = Semantics(
container: imageSemanticLabel != null, container: widget.imageSemanticLabel != null,
image: true, image: true,
label: imageSemanticLabel ?? '', label: widget.imageSemanticLabel ?? '',
child: result, child: result,
); );
} }
...@@ -414,7 +442,9 @@ class _AnimatedFadeOutFadeIn extends ImplicitlyAnimatedWidget { ...@@ -414,7 +442,9 @@ class _AnimatedFadeOutFadeIn extends ImplicitlyAnimatedWidget {
const _AnimatedFadeOutFadeIn({ const _AnimatedFadeOutFadeIn({
Key? key, Key? key,
required this.target, required this.target,
required this.targetProxyAnimation,
required this.placeholder, required this.placeholder,
required this.placeholderProxyAnimation,
required this.isTargetLoaded, required this.isTargetLoaded,
required this.fadeOutDuration, required this.fadeOutDuration,
required this.fadeOutCurve, required this.fadeOutCurve,
...@@ -430,7 +460,9 @@ class _AnimatedFadeOutFadeIn extends ImplicitlyAnimatedWidget { ...@@ -430,7 +460,9 @@ class _AnimatedFadeOutFadeIn extends ImplicitlyAnimatedWidget {
super(key: key, duration: fadeInDuration + fadeOutDuration); super(key: key, duration: fadeInDuration + fadeOutDuration);
final Widget target; final Widget target;
final ProxyAnimation targetProxyAnimation;
final Widget placeholder; final Widget placeholder;
final ProxyAnimation placeholderProxyAnimation;
final bool isTargetLoaded; final bool isTargetLoaded;
final Duration fadeInDuration; final Duration fadeInDuration;
final Duration fadeOutDuration; final Duration fadeOutDuration;
...@@ -494,6 +526,9 @@ class _AnimatedFadeOutFadeInState extends ImplicitlyAnimatedWidgetState<_Animate ...@@ -494,6 +526,9 @@ class _AnimatedFadeOutFadeInState extends ImplicitlyAnimatedWidgetState<_Animate
// for the full animation when the new target image becomes ready. // for the full animation when the new target image becomes ready.
controller.value = controller.upperBound; controller.value = controller.upperBound;
} }
widget.targetProxyAnimation.parent = _targetOpacityAnimation;
widget.placeholderProxyAnimation.parent = _placeholderOpacityAnimation;
} }
bool _isValid(Tween<double> tween) { bool _isValid(Tween<double> tween) {
...@@ -502,13 +537,8 @@ class _AnimatedFadeOutFadeInState extends ImplicitlyAnimatedWidgetState<_Animate ...@@ -502,13 +537,8 @@ class _AnimatedFadeOutFadeInState extends ImplicitlyAnimatedWidgetState<_Animate
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Widget target = FadeTransition(
opacity: _targetOpacityAnimation!,
child: widget.target,
);
if (_placeholderOpacityAnimation!.isCompleted) { if (_placeholderOpacityAnimation!.isCompleted) {
return target; return widget.target;
} }
return Stack( return Stack(
...@@ -518,11 +548,8 @@ class _AnimatedFadeOutFadeInState extends ImplicitlyAnimatedWidgetState<_Animate ...@@ -518,11 +548,8 @@ class _AnimatedFadeOutFadeInState extends ImplicitlyAnimatedWidgetState<_Animate
// but it allows the Stack to avoid a call to Directionality.of() // but it allows the Stack to avoid a call to Directionality.of()
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
children: <Widget>[ children: <Widget>[
target, widget.target,
FadeTransition( widget.placeholder,
opacity: _placeholderOpacityAnimation!,
child: widget.placeholder,
),
], ],
); );
} }
......
...@@ -330,6 +330,7 @@ class Image extends StatefulWidget { ...@@ -330,6 +330,7 @@ class Image extends StatefulWidget {
this.width, this.width,
this.height, this.height,
this.color, this.color,
this.opacity,
this.colorBlendMode, this.colorBlendMode,
this.fit, this.fit,
this.alignment = Alignment.center, this.alignment = Alignment.center,
...@@ -389,6 +390,7 @@ class Image extends StatefulWidget { ...@@ -389,6 +390,7 @@ class Image extends StatefulWidget {
this.width, this.width,
this.height, this.height,
this.color, this.color,
this.opacity,
this.colorBlendMode, this.colorBlendMode,
this.fit, this.fit,
this.alignment = Alignment.center, this.alignment = Alignment.center,
...@@ -451,6 +453,7 @@ class Image extends StatefulWidget { ...@@ -451,6 +453,7 @@ class Image extends StatefulWidget {
this.width, this.width,
this.height, this.height,
this.color, this.color,
this.opacity,
this.colorBlendMode, this.colorBlendMode,
this.fit, this.fit,
this.alignment = Alignment.center, this.alignment = Alignment.center,
...@@ -612,6 +615,7 @@ class Image extends StatefulWidget { ...@@ -612,6 +615,7 @@ class Image extends StatefulWidget {
this.width, this.width,
this.height, this.height,
this.color, this.color,
this.opacity,
this.colorBlendMode, this.colorBlendMode,
this.fit, this.fit,
this.alignment = Alignment.center, this.alignment = Alignment.center,
...@@ -681,6 +685,7 @@ class Image extends StatefulWidget { ...@@ -681,6 +685,7 @@ class Image extends StatefulWidget {
this.width, this.width,
this.height, this.height,
this.color, this.color,
this.opacity,
this.colorBlendMode, this.colorBlendMode,
this.fit, this.fit,
this.alignment = Alignment.center, this.alignment = Alignment.center,
...@@ -922,6 +927,20 @@ class Image extends StatefulWidget { ...@@ -922,6 +927,20 @@ class Image extends StatefulWidget {
/// If non-null, this color is blended with each image pixel using [colorBlendMode]. /// If non-null, this color is blended with each image pixel using [colorBlendMode].
final Color? color; final Color? color;
/// If non-null, the value from the [Animation] is multiplied with the opacity
/// of each image pixel before painting onto the canvas.
///
/// This is more efficient than using [FadeTransition] to change the opacity
/// of an image, since this avoids creating a new composited layer. Composited
/// layers may double memory usage as the image is painted onto an offscreen
/// render target.
///
/// See also:
///
/// * [AlwaysStoppedAnimation], which allows you to create an [Animation]
/// from a single opacity value.
final Animation<double>? opacity;
/// The rendering quality of the image. /// The rendering quality of the image.
/// ///
/// If the image is of a high quality and its pixels are perfectly aligned /// If the image is of a high quality and its pixels are perfectly aligned
...@@ -1071,6 +1090,7 @@ class Image extends StatefulWidget { ...@@ -1071,6 +1090,7 @@ class Image extends StatefulWidget {
properties.add(DoubleProperty('width', width, defaultValue: null)); properties.add(DoubleProperty('width', width, defaultValue: null));
properties.add(DoubleProperty('height', height, defaultValue: null)); properties.add(DoubleProperty('height', height, defaultValue: null));
properties.add(ColorProperty('color', color, defaultValue: null)); properties.add(ColorProperty('color', color, defaultValue: null));
properties.add(DiagnosticsProperty<Animation<double>?>('opacity', opacity, defaultValue: null));
properties.add(EnumProperty<BlendMode>('colorBlendMode', colorBlendMode, defaultValue: null)); properties.add(EnumProperty<BlendMode>('colorBlendMode', colorBlendMode, defaultValue: null));
properties.add(EnumProperty<BoxFit>('fit', fit, defaultValue: null)); properties.add(EnumProperty<BoxFit>('fit', fit, defaultValue: null));
properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null)); properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
...@@ -1326,6 +1346,7 @@ class _ImageState extends State<Image> with WidgetsBindingObserver { ...@@ -1326,6 +1346,7 @@ class _ImageState extends State<Image> with WidgetsBindingObserver {
height: widget.height, height: widget.height,
scale: _imageInfo?.scale ?? 1.0, scale: _imageInfo?.scale ?? 1.0,
color: widget.color, color: widget.color,
opacity: widget.opacity,
colorBlendMode: widget.colorBlendMode, colorBlendMode: widget.colorBlendMode,
fit: widget.fit, fit: widget.fit,
alignment: widget.alignment, alignment: widget.alignment,
......
...@@ -43,14 +43,12 @@ class FadeInImageParts { ...@@ -43,14 +43,12 @@ class FadeInImageParts {
} }
class FadeInImageElements { class FadeInImageElements {
const FadeInImageElements(this.rawImageElement, this.fadeTransitionElement); const FadeInImageElements(this.rawImageElement);
final Element rawImageElement; final Element rawImageElement;
final Element? fadeTransitionElement;
RawImage get rawImage => rawImageElement.widget as RawImage; RawImage get rawImage => rawImageElement.widget as RawImage;
FadeTransition? get fadeTransition => fadeTransitionElement?.widget as FadeTransition?; double get opacity => rawImage.opacity?.value ?? 1.0;
double get opacity => fadeTransition == null ? 1 : fadeTransition!.opacity.value;
} }
class LoadTestImageProvider extends ImageProvider<Object> { class LoadTestImageProvider extends ImageProvider<Object> {
...@@ -78,11 +76,8 @@ FadeInImageParts findFadeInImage(WidgetTester tester) { ...@@ -78,11 +76,8 @@ FadeInImageParts findFadeInImage(WidgetTester tester) {
final Iterable<Element> rawImageElements = tester.elementList(find.byType(RawImage)); final Iterable<Element> rawImageElements = tester.elementList(find.byType(RawImage));
ComponentElement? fadeInImageElement; ComponentElement? fadeInImageElement;
for (final Element rawImageElement in rawImageElements) { for (final Element rawImageElement in rawImageElements) {
Element? fadeTransitionElement;
rawImageElement.visitAncestorElements((Element ancestor) { rawImageElement.visitAncestorElements((Element ancestor) {
if (ancestor.widget is FadeTransition) { if (ancestor.widget is FadeInImage) {
fadeTransitionElement = ancestor;
} else if (ancestor.widget is FadeInImage) {
if (fadeInImageElement == null) { if (fadeInImageElement == null) {
fadeInImageElement = ancestor as ComponentElement; fadeInImageElement = ancestor as ComponentElement;
} else { } else {
...@@ -93,7 +88,7 @@ FadeInImageParts findFadeInImage(WidgetTester tester) { ...@@ -93,7 +88,7 @@ FadeInImageParts findFadeInImage(WidgetTester tester) {
return true; return true;
}); });
expect(fadeInImageElement, isNotNull); expect(fadeInImageElement, isNotNull);
elements.add(FadeInImageElements(rawImageElement, fadeTransitionElement)); elements.add(FadeInImageElements(rawImageElement));
} }
if (elements.length == 2) { if (elements.length == 2) {
return FadeInImageParts(fadeInImageElement!, elements.last, elements.first); return FadeInImageParts(fadeInImageElement!, elements.last, elements.first);
......
...@@ -753,6 +753,19 @@ void main() { ...@@ -753,6 +753,19 @@ void main() {
expect(renderer.colorBlendMode, BlendMode.clear); expect(renderer.colorBlendMode, BlendMode.clear);
}); });
testWidgets('Image opacity parameter', (WidgetTester tester) async {
const Animation<double> opacity = AlwaysStoppedAnimation<double>(0.5);
await tester.pumpWidget(
Image(
excludeFromSemantics: true,
image: _TestImageProvider(),
opacity: opacity,
),
);
final RenderImage renderer = tester.renderObject<RenderImage>(find.byType(Image));
expect(renderer.opacity, opacity);
});
testWidgets('Precache', (WidgetTester tester) async { testWidgets('Precache', (WidgetTester tester) async {
final _TestImageProvider provider = _TestImageProvider(); final _TestImageProvider provider = _TestImageProvider();
late Future<void> precache; late Future<void> precache;
...@@ -1721,6 +1734,57 @@ void main() { ...@@ -1721,6 +1734,57 @@ void main() {
skip: kIsWeb, // https://github.com/flutter/flutter/issues/54292. skip: kIsWeb, // https://github.com/flutter/flutter/issues/54292.
); );
testWidgets(
'Image opacity',
(WidgetTester tester) async {
final Key key = UniqueKey();
await tester.pumpWidget(RepaintBoundary(
key: key,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceAround,
textDirection: TextDirection.ltr,
children: <Widget>[
Image.memory(
Uint8List.fromList(kBlueRectPng),
opacity: const AlwaysStoppedAnimation<double>(0.25),
),
Image.memory(
Uint8List.fromList(kBlueRectPng),
opacity: const AlwaysStoppedAnimation<double>(0.5),
),
Image.memory(
Uint8List.fromList(kBlueRectPng),
opacity: const AlwaysStoppedAnimation<double>(0.75),
),
Image.memory(
Uint8List.fromList(kBlueRectPng),
opacity: const AlwaysStoppedAnimation<double>(1.0),
),
],
),
));
// precacheImage is needed, or the image in the golden file will be empty.
if (!kIsWeb) {
final Finder allImages = find.byType(Image);
for (final Element e in allImages.evaluate()) {
await tester.runAsync(() async {
final Image image = e.widget as Image;
await precacheImage(image.image, e);
});
}
await tester.pumpAndSettle();
}
await expectLater(
find.byKey(key),
matchesGoldenFile('transparent_image.png'),
);
},
skip: kIsWeb, // https://github.com/flutter/flutter/issues/54292.
);
testWidgets('Reports image size when painted', (WidgetTester tester) async { testWidgets('Reports image size when painted', (WidgetTester tester) async {
late ImageSizeInfo imageSizeInfo; late ImageSizeInfo imageSizeInfo;
int count = 0; int count = 0;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment