Unverified Commit 652fb975 authored by Gary Qian's avatar Gary Qian Committed by GitHub

FadeInImage cacheWidth and cacheHeight support (#43286)

parent 50d8cc07
...@@ -540,6 +540,18 @@ class ResizeImage extends ImageProvider<_SizeAwareCacheKey> { ...@@ -540,6 +540,18 @@ class ResizeImage extends ImageProvider<_SizeAwareCacheKey> {
/// The height the image should decode to and cache. /// The height the image should decode to and cache.
final int height; final int height;
/// Composes the `provider` in a [ResizeImage] only when `cacheWidth` and
/// `cacheHeight` are not both null.
///
/// When `cacheWidth` and `cacheHeight` are both null, this will return the
/// `provider` directly.
static ImageProvider<dynamic> resizeIfNeeded(int cacheWidth, int cacheHeight, ImageProvider<dynamic> provider) {
if (cacheWidth != null || cacheHeight != null) {
return ResizeImage(provider, width: cacheWidth, height: cacheHeight);
}
return provider;
}
@override @override
ImageStreamCompleter load(_SizeAwareCacheKey key, DecoderCallback decode) { ImageStreamCompleter load(_SizeAwareCacheKey key, DecoderCallback decode) {
final DecoderCallback decodeResize = (Uint8List bytes, {int cacheWidth, int cacheHeight}) { final DecoderCallback decodeResize = (Uint8List bytes, {int cacheWidth, int cacheHeight}) {
......
...@@ -66,6 +66,9 @@ class FadeInImage extends StatelessWidget { ...@@ -66,6 +66,9 @@ class FadeInImage extends StatelessWidget {
/// 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.
/// ///
/// The [placeholder] and [image] may be composed in a [ResizeImage] to provide
/// a custom decode/cache size.
///
/// The [placeholder], [image], [fadeOutDuration], [fadeOutCurve], /// The [placeholder], [image], [fadeOutDuration], [fadeOutCurve],
/// [fadeInDuration], [fadeInCurve], [alignment], [repeat], and /// [fadeInDuration], [fadeInCurve], [alignment], [repeat], and
/// [matchTextDirection] arguments must not be null. /// [matchTextDirection] arguments must not be null.
...@@ -108,6 +111,13 @@ class FadeInImage extends StatelessWidget { ...@@ -108,6 +111,13 @@ class FadeInImage extends StatelessWidget {
/// The `placeholderScale` and `imageScale` arguments are passed to their /// The `placeholderScale` and `imageScale` arguments are passed to their
/// respective [ImageProvider]s (see also [ImageInfo.scale]). /// respective [ImageProvider]s (see also [ImageInfo.scale]).
/// ///
/// If [placeholderCacheWidth], [placeholderCacheHeight], [imageCacheWidth],
/// or [imageCacheHeight] are provided, it indicates to the
/// engine that the respective image should be decoded at the specified size.
/// The image will be rendered to the constraints of the layout or [width]
/// and [height] regardless of these parameters. These parameters are primarily
/// intended to reduce the memory usage of [ImageCache].
///
/// The [placeholder], [image], [placeholderScale], [imageScale], /// The [placeholder], [image], [placeholderScale], [imageScale],
/// [fadeOutDuration], [fadeOutCurve], [fadeInDuration], [fadeInCurve], /// [fadeOutDuration], [fadeOutCurve], [fadeInDuration], [fadeInCurve],
/// [alignment], [repeat], and [matchTextDirection] arguments must not be /// [alignment], [repeat], and [matchTextDirection] arguments must not be
...@@ -137,6 +147,10 @@ class FadeInImage extends StatelessWidget { ...@@ -137,6 +147,10 @@ class FadeInImage extends StatelessWidget {
this.alignment = Alignment.center, this.alignment = Alignment.center,
this.repeat = ImageRepeat.noRepeat, this.repeat = ImageRepeat.noRepeat,
this.matchTextDirection = false, this.matchTextDirection = false,
int placeholderCacheWidth,
int placeholderCacheHeight,
int imageCacheWidth,
int imageCacheHeight,
}) : assert(placeholder != null), }) : assert(placeholder != null),
assert(image != null), assert(image != null),
assert(placeholderScale != null), assert(placeholderScale != null),
...@@ -148,8 +162,8 @@ class FadeInImage extends StatelessWidget { ...@@ -148,8 +162,8 @@ class FadeInImage extends StatelessWidget {
assert(alignment != null), assert(alignment != null),
assert(repeat != null), assert(repeat != null),
assert(matchTextDirection != null), assert(matchTextDirection != null),
placeholder = MemoryImage(placeholder, scale: placeholderScale), placeholder = ResizeImage.resizeIfNeeded(placeholderCacheWidth, placeholderCacheHeight, MemoryImage(placeholder, scale: placeholderScale)),
image = NetworkImage(image, scale: imageScale), image = ResizeImage.resizeIfNeeded(imageCacheWidth, imageCacheHeight, NetworkImage(image, scale: imageScale)),
super(key: key); super(key: key);
/// Creates a widget that uses a placeholder image stored in an asset bundle /// Creates a widget that uses a placeholder image stored in an asset bundle
...@@ -166,6 +180,13 @@ class FadeInImage extends StatelessWidget { ...@@ -166,6 +180,13 @@ class FadeInImage extends StatelessWidget {
/// resolution will be attempted for the [placeholder] image. Otherwise, the /// resolution will be attempted for the [placeholder] image. Otherwise, the
/// exact asset specified will be used. /// exact asset specified will be used.
/// ///
/// If [placeholderCacheWidth], [placeholderCacheHeight], [imageCacheWidth],
/// or [imageCacheHeight] are provided, it indicates to the
/// engine that the respective image should be decoded at the specified size.
/// The image will be rendered to the constraints of the layout or [width]
/// and [height] regardless of these parameters. These parameters are primarily
/// intended to reduce the memory usage of [ImageCache].
///
/// The [placeholder], [image], [imageScale], [fadeOutDuration], /// The [placeholder], [image], [imageScale], [fadeOutDuration],
/// [fadeOutCurve], [fadeInDuration], [fadeInCurve], [alignment], [repeat], /// [fadeOutCurve], [fadeInDuration], [fadeInCurve], [alignment], [repeat],
/// and [matchTextDirection] arguments must not be null. /// and [matchTextDirection] arguments must not be null.
...@@ -195,11 +216,15 @@ class FadeInImage extends StatelessWidget { ...@@ -195,11 +216,15 @@ class FadeInImage extends StatelessWidget {
this.alignment = Alignment.center, this.alignment = Alignment.center,
this.repeat = ImageRepeat.noRepeat, this.repeat = ImageRepeat.noRepeat,
this.matchTextDirection = false, this.matchTextDirection = false,
int placeholderCacheWidth,
int placeholderCacheHeight,
int imageCacheWidth,
int imageCacheHeight,
}) : assert(placeholder != null), }) : assert(placeholder != null),
assert(image != null), assert(image != null),
placeholder = placeholderScale != null placeholder = placeholderScale != null
? ExactAssetImage(placeholder, bundle: bundle, scale: placeholderScale) ? ResizeImage.resizeIfNeeded(placeholderCacheWidth, placeholderCacheHeight, ExactAssetImage(placeholder, bundle: bundle, scale: placeholderScale))
: AssetImage(placeholder, bundle: bundle), : ResizeImage.resizeIfNeeded(placeholderCacheWidth, placeholderCacheHeight, AssetImage(placeholder, bundle: bundle)),
assert(imageScale != null), assert(imageScale != null),
assert(fadeOutDuration != null), assert(fadeOutDuration != null),
assert(fadeOutCurve != null), assert(fadeOutCurve != null),
...@@ -208,7 +233,7 @@ class FadeInImage extends StatelessWidget { ...@@ -208,7 +233,7 @@ class FadeInImage extends StatelessWidget {
assert(alignment != null), assert(alignment != null),
assert(repeat != null), assert(repeat != null),
assert(matchTextDirection != null), assert(matchTextDirection != null),
image = NetworkImage(image, scale: imageScale), image = ResizeImage.resizeIfNeeded(imageCacheWidth, imageCacheHeight, NetworkImage(image, scale: imageScale)),
super(key: key); super(key: key);
/// Image displayed while the target [image] is loading. /// Image displayed while the target [image] is loading.
......
...@@ -55,13 +55,6 @@ ImageConfiguration createLocalImageConfiguration(BuildContext context, { Size si ...@@ -55,13 +55,6 @@ ImageConfiguration createLocalImageConfiguration(BuildContext context, { Size si
); );
} }
ImageProvider<dynamic> _resizeIfNeeded(int cacheWidth, int cacheHeight, ImageProvider<dynamic> provider) {
if (cacheWidth != null || cacheHeight != null) {
return ResizeImage(provider, width: cacheWidth, height: cacheHeight);
}
return provider;
}
/// Prefetches an image into the image cache. /// Prefetches an image into the image cache.
/// ///
/// Returns a [Future] that will complete when the first image yielded by the /// Returns a [Future] that will complete when the first image yielded by the
...@@ -358,7 +351,7 @@ class Image extends StatefulWidget { ...@@ -358,7 +351,7 @@ class Image extends StatefulWidget {
Map<String, String> headers, Map<String, String> headers,
int cacheWidth, int cacheWidth,
int cacheHeight, int cacheHeight,
}) : image = _resizeIfNeeded(cacheWidth, cacheHeight, NetworkImage(src, scale: scale, headers: headers)), }) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, NetworkImage(src, scale: scale, headers: headers)),
assert(alignment != null), assert(alignment != null),
assert(repeat != null), assert(repeat != null),
assert(matchTextDirection != null), assert(matchTextDirection != null),
...@@ -410,7 +403,7 @@ class Image extends StatefulWidget { ...@@ -410,7 +403,7 @@ class Image extends StatefulWidget {
this.filterQuality = FilterQuality.low, this.filterQuality = FilterQuality.low,
int cacheWidth, int cacheWidth,
int cacheHeight, int cacheHeight,
}) : image = _resizeIfNeeded(cacheWidth, cacheHeight, FileImage(file, scale: scale)), }) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, FileImage(file, scale: scale)),
loadingBuilder = null, loadingBuilder = null,
assert(alignment != null), assert(alignment != null),
assert(repeat != null), assert(repeat != null),
...@@ -573,7 +566,7 @@ class Image extends StatefulWidget { ...@@ -573,7 +566,7 @@ class Image extends StatefulWidget {
this.filterQuality = FilterQuality.low, this.filterQuality = FilterQuality.low,
int cacheWidth, int cacheWidth,
int cacheHeight, int cacheHeight,
}) : image = _resizeIfNeeded(cacheWidth, cacheHeight, scale != null }) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, scale != null
? ExactAssetImage(name, bundle: bundle, scale: scale, package: package) ? ExactAssetImage(name, bundle: bundle, scale: scale, package: package)
: AssetImage(name, bundle: bundle, package: package) : AssetImage(name, bundle: bundle, package: package)
), ),
...@@ -630,7 +623,7 @@ class Image extends StatefulWidget { ...@@ -630,7 +623,7 @@ class Image extends StatefulWidget {
this.filterQuality = FilterQuality.low, this.filterQuality = FilterQuality.low,
int cacheWidth, int cacheWidth,
int cacheHeight, int cacheHeight,
}) : image = _resizeIfNeeded(cacheWidth, cacheHeight, MemoryImage(bytes, scale: scale)), }) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, MemoryImage(bytes, scale: scale)),
loadingBuilder = null, loadingBuilder = null,
assert(alignment != null), assert(alignment != null),
assert(repeat != null), assert(repeat != null),
......
...@@ -3,10 +3,13 @@ ...@@ -3,10 +3,13 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:typed_data';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter/painting.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../painting/image_data.dart';
import '../painting/image_test_utils.dart'; import '../painting/image_test_utils.dart';
const Duration animationDuration = Duration(milliseconds: 50); const Duration animationDuration = Duration(milliseconds: 50);
...@@ -51,6 +54,26 @@ class FadeInImageElements { ...@@ -51,6 +54,26 @@ class FadeInImageElements {
double get opacity => fadeTransition == null ? 1 : fadeTransition.opacity.value; double get opacity => fadeTransition == null ? 1 : fadeTransition.opacity.value;
} }
class LoadTestImageProvider extends ImageProvider<dynamic> {
LoadTestImageProvider(this.provider);
final ImageProvider provider;
ImageStreamCompleter testLoad(dynamic key, DecoderCallback decode) {
return provider.load(key, decode);
}
@override
Future<dynamic> obtainKey(ImageConfiguration configuration) {
return null;
}
@override
ImageStreamCompleter load(dynamic key, DecoderCallback decode) {
return null;
}
}
FadeInImageParts findFadeInImage(WidgetTester tester) { FadeInImageParts findFadeInImage(WidgetTester tester) {
final List<FadeInImageElements> elements = <FadeInImageElements>[]; final List<FadeInImageElements> elements = <FadeInImageElements>[];
final Iterable<Element> rawImageElements = tester.elementList(find.byType(RawImage)); final Iterable<Element> rawImageElements = tester.elementList(find.byType(RawImage));
...@@ -262,6 +285,58 @@ Future<void> main() async { ...@@ -262,6 +285,58 @@ Future<void> main() async {
expect(findFadeInImage(tester).target.opacity, moreOrLessEquals(1)); expect(findFadeInImage(tester).target.opacity, moreOrLessEquals(1));
}); });
group(ImageProvider, () {
testWidgets('memory placeholder cacheWidth and cacheHeight is passed through', (WidgetTester tester) async {
final Uint8List testBytes = Uint8List.fromList(kTransparentImage);
final FadeInImage image = FadeInImage.memoryNetwork(
placeholder: testBytes,
image: 'test.com',
placeholderCacheWidth: 20,
placeholderCacheHeight: 30,
imageCacheWidth: 40,
imageCacheHeight: 50,
);
bool called = false;
final DecoderCallback decode = (Uint8List bytes, {int cacheWidth, int cacheHeight}) {
expect(cacheWidth, 20);
expect(cacheHeight, 30);
called = true;
return PaintingBinding.instance.instantiateImageCodec(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight);
};
final ImageProvider resizeImage = image.placeholder;
expect(image.placeholder, isA<ResizeImage>());
expect(called, false);
final LoadTestImageProvider testProvider = LoadTestImageProvider(image.placeholder);
testProvider.testLoad(await resizeImage.obtainKey(ImageConfiguration.empty), decode);
expect(called, true);
});
testWidgets('do not resize when null cache dimensions', (WidgetTester tester) async {
final Uint8List testBytes = Uint8List.fromList(kTransparentImage);
final FadeInImage image = FadeInImage.memoryNetwork(
placeholder: testBytes,
image: 'test.com',
);
bool called = false;
final DecoderCallback decode = (Uint8List bytes, {int cacheWidth, int cacheHeight}) {
expect(cacheWidth, null);
expect(cacheHeight, null);
called = true;
return PaintingBinding.instance.instantiateImageCodec(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight);
};
// image.placeholder should be an instance of MemoryImage instead of ResizeImage
final ImageProvider memoryImage = image.placeholder;
expect(image.placeholder, isA<MemoryImage>());
expect(called, false);
final LoadTestImageProvider testProvider = LoadTestImageProvider(image.placeholder);
testProvider.testLoad(await memoryImage.obtainKey(ImageConfiguration.empty), decode);
expect(called, true);
});
});
group('semantics', () { group('semantics', () {
testWidgets('only one Semantics node appears within FadeInImage', (WidgetTester tester) async { testWidgets('only one Semantics node appears within FadeInImage', (WidgetTester tester) async {
final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage); final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage);
......
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