Commit 89d06450 authored by Yegor's avatar Yegor Committed by GitHub

FadeInImage: shows a placeholder while loading then fades in (#11371)

* FadeInImage: shows a placeholder while loading then fades in

* fix dartdoc quotes

* license headers; imports

* use ImageProvider; docs; constructors

* _resolveImage when placeholder changes

* address comments

* docs re ImageProvider changes; unsubscribe from placeholder

* rebase

* address comments
parent c72381aa
......@@ -14,7 +14,7 @@ export 'package:flutter/painting.dart' show
/// An image in the render tree.
///
/// The render image attempts to find a size for itself that fits in the given
/// constraints and preserves the image's intrinisc aspect ratio.
/// constraints and preserves the image's intrinsic aspect ratio.
///
/// The image is painted using [paintImage], which describes the meanings of the
/// various fields on this class in more detail.
......
This diff is collapsed.
......@@ -162,10 +162,11 @@ class Image extends StatefulWidget {
/// If the `bundle` argument is omitted or null, then the
/// [DefaultAssetBundle] will be used.
///
/// By default, the exact asset specified will be used. In addition:
/// By default, the pixel-density-aware asset resolution will be attempted. In
/// addition:
///
/// * If the `scale` argument is omitted or null, then pixel-density-aware
/// asset resolution will be attempted.
/// * If the `scale` argument is provided and is not null, then the exact
/// asset specified will be used.
//
// TODO(ianh): Implement the following (see ../services/image_resolution.dart):
// ///
......
......@@ -29,6 +29,7 @@ export 'src/widgets/debug.dart';
export 'src/widgets/dismissible.dart';
export 'src/widgets/drag_target.dart';
export 'src/widgets/editable_text.dart';
export 'src/widgets/fade_in_image.dart';
export 'src/widgets/focus_manager.dart';
export 'src/widgets/focus_scope.dart';
export 'src/widgets/form.dart';
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'image_data.dart';
class TestImageProvider extends ImageProvider<TestImageProvider> {
TestImageProvider(this.testImage);
final ui.Image testImage;
final Completer<ImageInfo> _completer = new Completer<ImageInfo>.sync();
ImageConfiguration configuration;
@override
Future<TestImageProvider> obtainKey(ImageConfiguration configuration) {
return new SynchronousFuture<TestImageProvider>(this);
}
@override
ImageStream resolve(ImageConfiguration config) {
configuration = config;
return super.resolve(configuration);
}
@override
ImageStreamCompleter load(TestImageProvider key) =>
new OneFrameImageStreamCompleter(_completer.future);
ImageInfo complete() {
final ImageInfo imageInfo = new ImageInfo(image: testImage);
_completer.complete(imageInfo);
return imageInfo;
}
@override
String toString() => '${describeIdentity(this)}()';
}
Future<ui.Image> createTestImage() {
final Completer<ui.Image> uiImage = new Completer<ui.Image>();
ui.decodeImageFromList(new Uint8List.fromList(kTransparentImage), uiImage.complete);
return uiImage.future;
}
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import '../services/image_test_utils.dart';
Future<Null> main() async {
// These must run outside test zone to complete
final ui.Image targetImage = await createTestImage();
final ui.Image placeholderImage = await createTestImage();
group('FadeInImage', () {
testWidgets('animates uncached image and shows cached image immediately', (WidgetTester tester) async {
// State type is private, hence using dynamic.
dynamic state() => tester.state(find.byType(FadeInImage));
RawImage displayedImage() => tester.widget(find.byType(RawImage));
// The placeholder is expected to be already loaded
final TestImageProvider placeholderProvider = new TestImageProvider(placeholderImage);
// Test case: long loading image
final TestImageProvider imageProvider = new TestImageProvider(targetImage);
await tester.pumpWidget(new FadeInImage(
placeholder: placeholderProvider,
image: imageProvider,
fadeOutDuration: const Duration(milliseconds: 50),
fadeInDuration: const Duration(milliseconds: 50),
));
expect(displayedImage().image, null); // image providers haven't completed yet
placeholderProvider.complete();
await tester.pump();
expect(displayedImage().image, same(placeholderImage)); // placeholder completed
expect(state().phase, FadeInImagePhase.waiting);
imageProvider.complete(); // load the image
expect(state().phase, FadeInImagePhase.fadeOut); // fade out placeholder
for (int i = 0; i < 7; i += 1) {
expect(displayedImage().image, same(placeholderImage));
await tester.pump(const Duration(milliseconds: 10));
}
expect(displayedImage().image, same(targetImage));
expect(state().phase, FadeInImagePhase.fadeIn); // fade in image
for (int i = 0; i < 6; i += 1) {
expect(displayedImage().image, same(targetImage));
await tester.pump(const Duration(milliseconds: 10));
}
expect(state().phase, FadeInImagePhase.completed); // done
expect(displayedImage().image, same(targetImage));
// Test case: re-use state object (didUpdateWidget)
final dynamic stateBeforeDidUpdateWidget = state();
await tester.pumpWidget(new FadeInImage(
placeholder: placeholderProvider,
image: imageProvider,
));
final dynamic stateAfterDidUpdateWidget = state();
expect(stateAfterDidUpdateWidget, same(stateBeforeDidUpdateWidget));
expect(stateAfterDidUpdateWidget.phase, FadeInImagePhase.completed); // completes immediately
expect(displayedImage().image, same(targetImage));
// Test case: new state object but cached image
final dynamic stateBeforeRecreate = state();
await tester.pumpWidget(new Container()); // clear widget tree to prevent state reuse
await tester.pumpWidget(new FadeInImage(
placeholder: placeholderProvider,
image: imageProvider,
));
expect(displayedImage().image, same(targetImage));
final dynamic stateAfterRecreate = state();
expect(stateAfterRecreate, isNot(same(stateBeforeRecreate)));
expect(stateAfterRecreate.phase, FadeInImagePhase.completed); // completes immediately
expect(displayedImage().image, same(targetImage));
});
});
}
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