Tell image listeners if they are being called synchronously by the ImageStream (#5161)
Image listeners installed in paint handlers need to know whether the listener is being called during the paint. Fixes https://github.com/flutter/flutter/issues/4937
This commit is contained in:
parent
2656006c41
commit
39be1c3747
@ -1315,12 +1315,13 @@ class _BoxDecorationPainter extends BoxPainter {
|
||||
);
|
||||
}
|
||||
|
||||
void _imageListener(ImageInfo value) {
|
||||
void _imageListener(ImageInfo value, bool synchronousCall) {
|
||||
if (_image == value)
|
||||
return;
|
||||
_image = value;
|
||||
assert(onChanged != null);
|
||||
onChanged();
|
||||
if (!synchronousCall)
|
||||
onChanged();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -46,8 +46,11 @@ class ImageInfo {
|
||||
|
||||
/// Signature for callbacks reporting that an image is available.
|
||||
///
|
||||
/// synchronousCall is true if the listener is being invoked during the call
|
||||
/// to addListener.
|
||||
///
|
||||
/// Used by [ImageStream].
|
||||
typedef void ImageListener(ImageInfo image);
|
||||
typedef void ImageListener(ImageInfo image, bool synchronousCall);
|
||||
|
||||
/// A handle to an image resource.
|
||||
///
|
||||
@ -154,11 +157,15 @@ class ImageStreamCompleter {
|
||||
/// Adds a listener callback that is called whenever a concrete [ImageInfo]
|
||||
/// object is available. If a concrete image is already available, this object
|
||||
/// will call the listener synchronously.
|
||||
///
|
||||
/// The listener will be passed a flag indicating whether a synchronous call
|
||||
/// occurred. If the listener is added within a render object paint function,
|
||||
/// then use this flag to avoid calling markNeedsPaint during a paint.
|
||||
void addListener(ImageListener listener) {
|
||||
_listeners.add(listener);
|
||||
if (_current != null) {
|
||||
try {
|
||||
listener(_current);
|
||||
listener(_current, true);
|
||||
} catch (exception, stack) {
|
||||
_handleImageError('by a synchronously-called image listener', exception, stack);
|
||||
}
|
||||
@ -179,7 +186,7 @@ class ImageStreamCompleter {
|
||||
List<ImageListener> localListeners = new List<ImageListener>.from(_listeners);
|
||||
for (ImageListener listener in localListeners) {
|
||||
try {
|
||||
listener(image);
|
||||
listener(image, false);
|
||||
} catch (exception, stack) {
|
||||
_handleImageError('by an image listener', exception, stack);
|
||||
}
|
||||
|
@ -226,7 +226,7 @@ class _ImageState extends State<Image> {
|
||||
}
|
||||
}
|
||||
|
||||
void _handleImageChanged(ImageInfo imageInfo) {
|
||||
void _handleImageChanged(ImageInfo imageInfo, bool synchronousCall) {
|
||||
setState(() {
|
||||
_imageInfo = imageInfo;
|
||||
});
|
||||
|
@ -2,9 +2,48 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/painting.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:quiver/testing/async.dart';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
import '../services/mocks_for_image_cache.dart';
|
||||
|
||||
class TestCanvas implements Canvas {
|
||||
@override
|
||||
void noSuchMethod(Invocation invocation) {}
|
||||
}
|
||||
|
||||
class SynchronousTestImageProvider extends ImageProvider<int> {
|
||||
@override
|
||||
Future<int> obtainKey(ImageConfiguration configuration) {
|
||||
return new SynchronousFuture<int>(1);
|
||||
}
|
||||
|
||||
@override
|
||||
ImageStreamCompleter load(int key) {
|
||||
return new OneFrameImageStreamCompleter(
|
||||
new SynchronousFuture<ImageInfo>(new TestImageInfo(key))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AsyncTestImageProvider extends ImageProvider<int> {
|
||||
@override
|
||||
Future<int> obtainKey(ImageConfiguration configuration) {
|
||||
return new Future<int>.value(2);
|
||||
}
|
||||
|
||||
@override
|
||||
ImageStreamCompleter load(int key) {
|
||||
return new OneFrameImageStreamCompleter(
|
||||
new Future<ImageInfo>.value(new TestImageInfo(key))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
test("Decoration.lerp()", () {
|
||||
@ -20,4 +59,44 @@ void main() {
|
||||
c = Decoration.lerp(a, b, 1.0);
|
||||
expect(c.backgroundColor, equals(b.backgroundColor));
|
||||
});
|
||||
|
||||
test("BoxDecorationImageListenerSync", () {
|
||||
ImageProvider imageProvider = new SynchronousTestImageProvider();
|
||||
BackgroundImage backgroundImage = new BackgroundImage(image: imageProvider);
|
||||
|
||||
BoxDecoration boxDecoration = new BoxDecoration(backgroundImage: backgroundImage);
|
||||
bool onChangedCalled = false;
|
||||
BoxPainter boxPainter = boxDecoration.createBoxPainter(() {
|
||||
onChangedCalled = true;
|
||||
});
|
||||
|
||||
TestCanvas canvas = new TestCanvas();
|
||||
ImageConfiguration imageConfiguration = new ImageConfiguration(size: Size.zero);
|
||||
boxPainter.paint(canvas, Offset.zero, imageConfiguration);
|
||||
|
||||
// The onChanged callback should not be invoked during the call to boxPainter.paint
|
||||
expect(onChangedCalled, equals(false));
|
||||
});
|
||||
|
||||
test("BoxDecorationImageListenerAsync", () {
|
||||
new FakeAsync().run((FakeAsync async) {
|
||||
ImageProvider imageProvider = new AsyncTestImageProvider();
|
||||
BackgroundImage backgroundImage = new BackgroundImage(image: imageProvider);
|
||||
|
||||
BoxDecoration boxDecoration = new BoxDecoration(backgroundImage: backgroundImage);
|
||||
bool onChangedCalled = false;
|
||||
BoxPainter boxPainter = boxDecoration.createBoxPainter(() {
|
||||
onChangedCalled = true;
|
||||
});
|
||||
|
||||
TestCanvas canvas = new TestCanvas();
|
||||
ImageConfiguration imageConfiguration = new ImageConfiguration(size: Size.zero);
|
||||
boxPainter.paint(canvas, Offset.zero, imageConfiguration);
|
||||
|
||||
// The onChanged callback should be invoked asynchronously.
|
||||
expect(onChangedCalled, equals(false));
|
||||
async.flushMicrotasks();
|
||||
expect(onChangedCalled, equals(true));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ class TestProvider extends ImageProvider<int> {
|
||||
|
||||
Future<ImageInfo> extractOneFrame(ImageStream stream) {
|
||||
Completer<ImageInfo> completer = new Completer<ImageInfo>();
|
||||
void listener(ImageInfo image) {
|
||||
void listener(ImageInfo image, bool synchronousCall) {
|
||||
completer.complete(image);
|
||||
stream.removeListener(listener);
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ class ImageMap {
|
||||
Future<ui.Image> _loadImage(String url) async {
|
||||
ImageStream stream = new AssetImage(url, bundle: _bundle).resolve(ImageConfiguration.empty);
|
||||
Completer<ui.Image> completer = new Completer<ui.Image>();
|
||||
void listener(ImageInfo frame) {
|
||||
void listener(ImageInfo frame, bool synchronousCall) {
|
||||
final ui.Image image = frame.image;
|
||||
_images[url] = image;
|
||||
completer.complete(image);
|
||||
|
Loading…
x
Reference in New Issue
Block a user