Image
显示图像的小部件。
const Image({
Key key,
@required this.image,// ImageProvider
this.frameBuilder,
this.loadingBuilder,
this.errorBuilder,
this.semanticLabel,
this.excludeFromSemantics = false,
this.width,//宽度
this.height,//高度
this.color,//背景颜色
this.colorBlendMode, // 详见
this.fit,// BoxFit fill contain cover fitWidth fitHeight none scaleDown
this.alignment = Alignment.center, //位置
this.repeat = ImageRepeat.noRepeat, //如何绘制未被图像覆盖的方框的任何部分。 repeat repeatX repeatY noRepeat
this.centerSlice,
this.matchTextDirection = false, //是否按[TextDirection]的方向绘制图像。
this.gaplessPlayback = false,
this.filterQuality = FilterQuality.low,
})
didChangeDependencies
@override
void didChangeDependencies() {
_updateInvertColors();
_resolveImage(); //
if (TickerMode.of(context))
_listenToStream();
else
_stopListeningToStream();
super.didChangeDependencies();
}
_resolveImage
void _resolveImage() {
final ScrollAwareImageProvider provider = ScrollAwareImageProvider<dynamic>(
context: _scrollAwareContext,
imageProvider: widget.image,
);
final ImageStream newStream =
provider.resolve(createLocalImageConfiguration(
context,
size: widget.width != null && widget.height != null ? Size(widget.width, widget.height) : null,
));
_updateSourceStream(newStream);
}
createLocalImageConfiguration
ImageConfiguration createLocalImageConfiguration(BuildContext context, { Size size }) {
return ImageConfiguration(
bundle: DefaultAssetBundle.of(context),
devicePixelRatio: MediaQuery.of(context, nullOk: true)?.devicePixelRatio ?? 1.0,
locale: Localizations.localeOf(context, nullOk: true),
textDirection: Directionality.of(context),
size: size,
platform: defaultTargetPlatform,
);
}
ImageConfiguration
const ImageConfiguration({
this.bundle,
this.devicePixelRatio,
this.locale,
this.textDirection,
this.size,
this.platform,
});
provider.resolve configuration 转化成 图片流
@nonVirtual
ImageStream resolve(ImageConfiguration configuration) {
assert(configuration != null);
final ImageStream stream = createStream(configuration);
_createErrorHandlerAndKey(
configuration,
(T key, ImageErrorListener errorHandler) {
resolveStreamForKey(configuration, stream, key, errorHandler);
},
(T key, dynamic exception, StackTrace stack) async {
await null; // wait an event turn in case a listener has been added to the image stream.
final _ErrorImageCompleter imageCompleter = _ErrorImageCompleter();
stream.setCompleter(imageCompleter);
InformationCollector collector;
assert(() {
collector = () sync* {
yield DiagnosticsProperty<ImageProvider>('Image provider', this);
yield DiagnosticsProperty<ImageConfiguration>('Image configuration', configuration);
yield DiagnosticsProperty<T>('Image key', key, defaultValue: null);
};
return true;
}());
imageCompleter.setError(
exception: exception,
stack: stack,
context: ErrorDescription('while resolving an image'),
silent: true, // could be a network error or whatnot
informationCollector: collector
);
},
);
return stream;
}
@protected
ImageStream createStream(ImageConfiguration configuration) {
return ImageStream();
}
void _createErrorHandlerAndKey(
ImageConfiguration configuration,
_KeyAndErrorHandlerCallback<T> successCallback,
_AsyncKeyErrorHandler<T> errorCallback,
) {
T obtainedKey;
bool didError = false;
Future<void> handleError(dynamic exception, StackTrace stack) async {
if (didError) {
return;
}
if (!didError) {
errorCallback(obtainedKey, exception, stack);
}
didError = true;
}
@protected
void resolveStreamForKey(ImageConfiguration configuration, ImageStream stream, T key, ImageErrorListener handleError) {
// This is an unusual edge case where someone has told us that they found
// the image we want before getting to this method. We should avoid calling
// load again, but still update the image cache with LRU information.
if (stream.completer != null) {
final ImageStreamCompleter completer = PaintingBinding.instance.imageCache.putIfAbsent(
key,
() => stream.completer,
onError: handleError,
);
assert(identical(completer, stream.completer));
return;
}
final ImageStreamCompleter completer = PaintingBinding.instance.imageCache.putIfAbsent(
key,
() => load(key, PaintingBinding.instance.instantiateImageCodec),
onError: handleError,
);
if (completer != null) {
stream.setCompleter(completer);
}
}
@protected
void resolveStreamForKey(ImageConfiguration configuration, ImageStream stream, T key, ImageErrorListener handleError) {
// This is an unusual edge case where someone has told us that they found
// the image we want before getting to this method. We should avoid calling
// load again, but still update the image cache with LRU information.
if (stream.completer != null) {
final ImageStreamCompleter completer = PaintingBinding.instance.imageCache.putIfAbsent(
key,
() => stream.completer,
onError: handleError,
);
assert(identical(completer, stream.completer));
return;
}
final ImageStreamCompleter completer = PaintingBinding.instance.imageCache.putIfAbsent(
key,
() => load(key, PaintingBinding.instance.instantiateImageCodec),
onError: handleError,
);
if (completer != null) {
stream.setCompleter(completer);
}
}
ImageStreamCompleter putIfAbsent(Object key, ImageStreamCompleter loader(), { ImageErrorListener onError }) {
assert(key != null);
assert(loader != null);
TimelineTask timelineTask;
TimelineTask listenerTask;
if (!kReleaseMode) {
timelineTask = TimelineTask()..start(
'ImageCache.putIfAbsent',
arguments: <String, dynamic>{
'key': key.toString(),
},
);
}
ImageStreamCompleter result = _pendingImages[key]?.completer;
// Nothing needs to be done because the image hasn't loaded yet.
if (result != null) {
if (!kReleaseMode) {
timelineTask.finish(arguments: <String, dynamic>{'result': 'pending'});
}
return result;
}
// Remove the provider from the list so that we can move it to the
// recently used position below.
// Don't use _touch here, which would trigger a check on cache size that is
// not needed since this is just moving an existing cache entry to the head.
final _CachedImage image = _cache.remove(key);
if (image != null) {
if (!kReleaseMode) {
timelineTask.finish(arguments: <String, dynamic>{'result': 'keepAlive'});
}
// The image might have been keptAlive but had no listeners (so not live).
// Make sure the cache starts tracking it as live again.
_trackLiveImage(key, _LiveImage(image.completer, image.sizeBytes, () => _liveImages.remove(key)));
_cache[key] = image;
return image.completer;
}
final _CachedImage liveImage = _liveImages[key];
if (liveImage != null) {
_touch(key, liveImage, timelineTask);
if (!kReleaseMode) {
timelineTask.finish(arguments: <String, dynamic>{'result': 'keepAlive'});
}
return liveImage.completer;
}
try {
result = loader();
_trackLiveImage(key, _LiveImage(result, null, () => _liveImages.remove(key)));
} catch (error, stackTrace) {
if (!kReleaseMode) {
timelineTask.finish(arguments: <String, dynamic>{
'result': 'error',
'error': error.toString(),
'stackTrace': stackTrace.toString(),
});
}
if (onError != null) {
onError(error, stackTrace);
return null;
} else {
rethrow;
}
}
if (!kReleaseMode) {
listenerTask = TimelineTask(parent: timelineTask)..start('listener');
}
// If we're doing tracing, we need to make sure that we don't try to finish
// the trace entry multiple times if we get re-entrant calls from a multi-
// frame provider here.
bool listenedOnce = false;
// We shouldn't use the _pendingImages map if the cache is disabled, but we
// will have to listen to the image at least once so we don't leak it in
// the live image tracking.
// If the cache is disabled, this variable will be set.
_PendingImage untrackedPendingImage;
void listener(ImageInfo info, bool syncCall) {
// Images that fail to load don't contribute to cache size.
final int imageSize = info?.image == null ? 0 : info.image.height * info.image.width * 4;
final _CachedImage image = _CachedImage(result, imageSize);
_trackLiveImage(
key,
_LiveImage(
result,
imageSize,
() => _liveImages.remove(key),
),
// This should result in a put if `loader()` above executed
// synchronously, in which case syncCall is true and we arrived here
// before we got a chance to track the image otherwise.
debugPutOk: syncCall,
);
final _PendingImage pendingImage = untrackedPendingImage ?? _pendingImages.remove(key);
if (pendingImage != null) {
pendingImage.removeListener();
}
// Only touch if the cache was enabled when resolve was initially called.
if (untrackedPendingImage == null) {
_touch(key, image, listenerTask);
}
if (!kReleaseMode && !listenedOnce) {
listenerTask.finish(arguments: <String, dynamic>{
'syncCall': syncCall,
'sizeInBytes': imageSize,
});
timelineTask.finish(arguments: <String, dynamic>{
'currentSizeBytes': currentSizeBytes,
'currentSize': currentSize,
});
}
listenedOnce = true;
}
final ImageStreamListener streamListener = ImageStreamListener(listener);
if (maximumSize > 0 && maximumSizeBytes > 0) {
_pendingImages[key] = _PendingImage(result, streamListener);
} else {
untrackedPendingImage = _PendingImage(result, streamListener);
}
// Listener is removed in [_PendingImage.removeListener].
result.addListener(streamListener);
return result;
}
ImageStream
图像资源的句柄。
ImageStreamCompleter
用于管理[飞镖:ui.Image][ImageStream]s的对象。
网友评论