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,11 +1315,12 @@ class _BoxDecorationPainter extends BoxPainter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _imageListener(ImageInfo value) {
|
void _imageListener(ImageInfo value, bool synchronousCall) {
|
||||||
if (_image == value)
|
if (_image == value)
|
||||||
return;
|
return;
|
||||||
_image = value;
|
_image = value;
|
||||||
assert(onChanged != null);
|
assert(onChanged != null);
|
||||||
|
if (!synchronousCall)
|
||||||
onChanged();
|
onChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,8 +46,11 @@ class ImageInfo {
|
|||||||
|
|
||||||
/// Signature for callbacks reporting that an image is available.
|
/// 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].
|
/// Used by [ImageStream].
|
||||||
typedef void ImageListener(ImageInfo image);
|
typedef void ImageListener(ImageInfo image, bool synchronousCall);
|
||||||
|
|
||||||
/// A handle to an image resource.
|
/// A handle to an image resource.
|
||||||
///
|
///
|
||||||
@ -154,11 +157,15 @@ class ImageStreamCompleter {
|
|||||||
/// Adds a listener callback that is called whenever a concrete [ImageInfo]
|
/// Adds a listener callback that is called whenever a concrete [ImageInfo]
|
||||||
/// object is available. If a concrete image is already available, this object
|
/// object is available. If a concrete image is already available, this object
|
||||||
/// will call the listener synchronously.
|
/// 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) {
|
void addListener(ImageListener listener) {
|
||||||
_listeners.add(listener);
|
_listeners.add(listener);
|
||||||
if (_current != null) {
|
if (_current != null) {
|
||||||
try {
|
try {
|
||||||
listener(_current);
|
listener(_current, true);
|
||||||
} catch (exception, stack) {
|
} catch (exception, stack) {
|
||||||
_handleImageError('by a synchronously-called image listener', 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);
|
List<ImageListener> localListeners = new List<ImageListener>.from(_listeners);
|
||||||
for (ImageListener listener in localListeners) {
|
for (ImageListener listener in localListeners) {
|
||||||
try {
|
try {
|
||||||
listener(image);
|
listener(image, false);
|
||||||
} catch (exception, stack) {
|
} catch (exception, stack) {
|
||||||
_handleImageError('by an image listener', 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(() {
|
setState(() {
|
||||||
_imageInfo = imageInfo;
|
_imageInfo = imageInfo;
|
||||||
});
|
});
|
||||||
|
@ -2,9 +2,48 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/painting.dart';
|
import 'package:flutter/painting.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:quiver/testing/async.dart';
|
||||||
|
|
||||||
import 'package:test/test.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() {
|
void main() {
|
||||||
test("Decoration.lerp()", () {
|
test("Decoration.lerp()", () {
|
||||||
@ -20,4 +59,44 @@ void main() {
|
|||||||
c = Decoration.lerp(a, b, 1.0);
|
c = Decoration.lerp(a, b, 1.0);
|
||||||
expect(c.backgroundColor, equals(b.backgroundColor));
|
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) {
|
Future<ImageInfo> extractOneFrame(ImageStream stream) {
|
||||||
Completer<ImageInfo> completer = new Completer<ImageInfo>();
|
Completer<ImageInfo> completer = new Completer<ImageInfo>();
|
||||||
void listener(ImageInfo image) {
|
void listener(ImageInfo image, bool synchronousCall) {
|
||||||
completer.complete(image);
|
completer.complete(image);
|
||||||
stream.removeListener(listener);
|
stream.removeListener(listener);
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ class ImageMap {
|
|||||||
Future<ui.Image> _loadImage(String url) async {
|
Future<ui.Image> _loadImage(String url) async {
|
||||||
ImageStream stream = new AssetImage(url, bundle: _bundle).resolve(ImageConfiguration.empty);
|
ImageStream stream = new AssetImage(url, bundle: _bundle).resolve(ImageConfiguration.empty);
|
||||||
Completer<ui.Image> completer = new Completer<ui.Image>();
|
Completer<ui.Image> completer = new Completer<ui.Image>();
|
||||||
void listener(ImageInfo frame) {
|
void listener(ImageInfo frame, bool synchronousCall) {
|
||||||
final ui.Image image = frame.image;
|
final ui.Image image = frame.image;
|
||||||
_images[url] = image;
|
_images[url] = image;
|
||||||
completer.complete(image);
|
completer.complete(image);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user