forked from firka/flutter
FadeInImage cacheWidth and cacheHeight support (#43286)
This commit is contained in:
@@ -540,6 +540,18 @@ class ResizeImage extends ImageProvider<_SizeAwareCacheKey> {
|
||||
/// The height the image should decode to and cache.
|
||||
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
|
||||
ImageStreamCompleter load(_SizeAwareCacheKey key, DecoderCallback decode) {
|
||||
final DecoderCallback decodeResize = (Uint8List bytes, {int cacheWidth, int cacheHeight}) {
|
||||
|
||||
@@ -66,6 +66,9 @@ class FadeInImage extends StatelessWidget {
|
||||
/// Creates a widget that displays a [placeholder] while an [image] is loading,
|
||||
/// 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],
|
||||
/// [fadeInDuration], [fadeInCurve], [alignment], [repeat], and
|
||||
/// [matchTextDirection] arguments must not be null.
|
||||
@@ -108,6 +111,13 @@ class FadeInImage extends StatelessWidget {
|
||||
/// The `placeholderScale` and `imageScale` arguments are passed to their
|
||||
/// 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],
|
||||
/// [fadeOutDuration], [fadeOutCurve], [fadeInDuration], [fadeInCurve],
|
||||
/// [alignment], [repeat], and [matchTextDirection] arguments must not be
|
||||
@@ -137,6 +147,10 @@ class FadeInImage extends StatelessWidget {
|
||||
this.alignment = Alignment.center,
|
||||
this.repeat = ImageRepeat.noRepeat,
|
||||
this.matchTextDirection = false,
|
||||
int placeholderCacheWidth,
|
||||
int placeholderCacheHeight,
|
||||
int imageCacheWidth,
|
||||
int imageCacheHeight,
|
||||
}) : assert(placeholder != null),
|
||||
assert(image != null),
|
||||
assert(placeholderScale != null),
|
||||
@@ -148,8 +162,8 @@ class FadeInImage extends StatelessWidget {
|
||||
assert(alignment != null),
|
||||
assert(repeat != null),
|
||||
assert(matchTextDirection != null),
|
||||
placeholder = MemoryImage(placeholder, scale: placeholderScale),
|
||||
image = NetworkImage(image, scale: imageScale),
|
||||
placeholder = ResizeImage.resizeIfNeeded(placeholderCacheWidth, placeholderCacheHeight, MemoryImage(placeholder, scale: placeholderScale)),
|
||||
image = ResizeImage.resizeIfNeeded(imageCacheWidth, imageCacheHeight, NetworkImage(image, scale: imageScale)),
|
||||
super(key: key);
|
||||
|
||||
/// Creates a widget that uses a placeholder image stored in an asset bundle
|
||||
@@ -166,6 +180,13 @@ class FadeInImage extends StatelessWidget {
|
||||
/// resolution will be attempted for the [placeholder] image. Otherwise, the
|
||||
/// 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],
|
||||
/// [fadeOutCurve], [fadeInDuration], [fadeInCurve], [alignment], [repeat],
|
||||
/// and [matchTextDirection] arguments must not be null.
|
||||
@@ -195,11 +216,15 @@ class FadeInImage extends StatelessWidget {
|
||||
this.alignment = Alignment.center,
|
||||
this.repeat = ImageRepeat.noRepeat,
|
||||
this.matchTextDirection = false,
|
||||
int placeholderCacheWidth,
|
||||
int placeholderCacheHeight,
|
||||
int imageCacheWidth,
|
||||
int imageCacheHeight,
|
||||
}) : assert(placeholder != null),
|
||||
assert(image != null),
|
||||
placeholder = placeholderScale != null
|
||||
? ExactAssetImage(placeholder, bundle: bundle, scale: placeholderScale)
|
||||
: AssetImage(placeholder, bundle: bundle),
|
||||
? ResizeImage.resizeIfNeeded(placeholderCacheWidth, placeholderCacheHeight, ExactAssetImage(placeholder, bundle: bundle, scale: placeholderScale))
|
||||
: ResizeImage.resizeIfNeeded(placeholderCacheWidth, placeholderCacheHeight, AssetImage(placeholder, bundle: bundle)),
|
||||
assert(imageScale != null),
|
||||
assert(fadeOutDuration != null),
|
||||
assert(fadeOutCurve != null),
|
||||
@@ -208,7 +233,7 @@ class FadeInImage extends StatelessWidget {
|
||||
assert(alignment != null),
|
||||
assert(repeat != null),
|
||||
assert(matchTextDirection != null),
|
||||
image = NetworkImage(image, scale: imageScale),
|
||||
image = ResizeImage.resizeIfNeeded(imageCacheWidth, imageCacheHeight, NetworkImage(image, scale: imageScale)),
|
||||
super(key: key);
|
||||
|
||||
/// Image displayed while the target [image] is loading.
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
/// Returns a [Future] that will complete when the first image yielded by the
|
||||
@@ -358,7 +351,7 @@ class Image extends StatefulWidget {
|
||||
Map<String, String> headers,
|
||||
int cacheWidth,
|
||||
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(repeat != null),
|
||||
assert(matchTextDirection != null),
|
||||
@@ -410,7 +403,7 @@ class Image extends StatefulWidget {
|
||||
this.filterQuality = FilterQuality.low,
|
||||
int cacheWidth,
|
||||
int cacheHeight,
|
||||
}) : image = _resizeIfNeeded(cacheWidth, cacheHeight, FileImage(file, scale: scale)),
|
||||
}) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, FileImage(file, scale: scale)),
|
||||
loadingBuilder = null,
|
||||
assert(alignment != null),
|
||||
assert(repeat != null),
|
||||
@@ -573,7 +566,7 @@ class Image extends StatefulWidget {
|
||||
this.filterQuality = FilterQuality.low,
|
||||
int cacheWidth,
|
||||
int cacheHeight,
|
||||
}) : image = _resizeIfNeeded(cacheWidth, cacheHeight, scale != null
|
||||
}) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, scale != null
|
||||
? ExactAssetImage(name, bundle: bundle, scale: scale, package: package)
|
||||
: AssetImage(name, bundle: bundle, package: package)
|
||||
),
|
||||
@@ -630,7 +623,7 @@ class Image extends StatefulWidget {
|
||||
this.filterQuality = FilterQuality.low,
|
||||
int cacheWidth,
|
||||
int cacheHeight,
|
||||
}) : image = _resizeIfNeeded(cacheWidth, cacheHeight, MemoryImage(bytes, scale: scale)),
|
||||
}) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, MemoryImage(bytes, scale: scale)),
|
||||
loadingBuilder = null,
|
||||
assert(alignment != null),
|
||||
assert(repeat != null),
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/painting.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import '../painting/image_data.dart';
|
||||
import '../painting/image_test_utils.dart';
|
||||
|
||||
const Duration animationDuration = Duration(milliseconds: 50);
|
||||
@@ -51,6 +54,26 @@ class FadeInImageElements {
|
||||
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) {
|
||||
final List<FadeInImageElements> elements = <FadeInImageElements>[];
|
||||
final Iterable<Element> rawImageElements = tester.elementList(find.byType(RawImage));
|
||||
@@ -262,6 +285,58 @@ Future<void> main() async {
|
||||
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', () {
|
||||
testWidgets('only one Semantics node appears within FadeInImage', (WidgetTester tester) async {
|
||||
final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage);
|
||||
|
||||
Reference in New Issue
Block a user