1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// @docImport 'package:flutter/material.dart';
6///
7/// @docImport 'app.dart';
8/// @docImport 'fade_in_image.dart';
9/// @docImport 'icon.dart';
10/// @docImport 'transitions.dart';
11library;
12
13import 'dart:async';
14import 'dart:io' show File;
15
16import 'package:flutter/foundation.dart';
17import 'package:flutter/scheduler.dart';
18import 'package:flutter/semantics.dart';
19
20import '../painting/_web_image_info_io.dart'
21 if (dart.library.js_util) '../painting/_web_image_info_web.dart';
22import '_web_image_io.dart' if (dart.library.js_util) '_web_image_web.dart';
23import 'basic.dart';
24import 'binding.dart';
25import 'disposable_build_context.dart';
26import 'framework.dart';
27import 'localizations.dart';
28import 'media_query.dart';
29import 'placeholder.dart';
30import 'scroll_aware_image_provider.dart';
31import 'text.dart';
32import 'ticker_provider.dart';
33
34export 'package:flutter/painting.dart'
35 show
36 AssetImage,
37 ExactAssetImage,
38 FileImage,
39 FilterQuality,
40 ImageConfiguration,
41 ImageInfo,
42 ImageProvider,
43 ImageStream,
44 MemoryImage,
45 NetworkImage;
46
47// Examples can assume:
48// late Widget image;
49// late ImageProvider _image;
50
51/// Creates an [ImageConfiguration] based on the given [BuildContext] (and
52/// optionally size).
53///
54/// This is the object that must be passed to [BoxPainter.paint] and to
55/// [ImageProvider.resolve].
56///
57/// If this is not called from a build method, then it should be reinvoked
58/// whenever the dependencies change, e.g. by calling it from
59/// [State.didChangeDependencies], so that any changes in the environment are
60/// picked up (e.g. if the device pixel ratio changes).
61///
62/// See also:
63///
64/// * [ImageProvider], which has an example showing how this might be used.
65ImageConfiguration createLocalImageConfiguration(BuildContext context, {Size? size}) {
66 return ImageConfiguration(
67 bundle: DefaultAssetBundle.of(context),
68 devicePixelRatio: MediaQuery.maybeDevicePixelRatioOf(context) ?? 1.0,
69 locale: Localizations.maybeLocaleOf(context),
70 textDirection: Directionality.maybeOf(context),
71 size: size,
72 platform: defaultTargetPlatform,
73 );
74}
75
76/// Prefetches an image into the image cache.
77///
78/// Returns a [Future] that will complete when the first image yielded by the
79/// [ImageProvider] is available or failed to load.
80///
81/// If the image is later used by an [Image] or [BoxDecoration] or [FadeInImage],
82/// it will probably be loaded faster. The consumer of the image does not need
83/// to use the same [ImageProvider] instance. The [ImageCache] will find the image
84/// as long as both images share the same key, and the image is held by the
85/// cache.
86///
87/// The cache may refuse to hold the image if it is disabled, the image is too
88/// large, or some other criteria implemented by a custom [ImageCache]
89/// implementation.
90///
91/// The [ImageCache] holds a reference to all images passed to
92/// [ImageCache.putIfAbsent] as long as their [ImageStreamCompleter] has at
93/// least one listener. This method will wait until the end of the frame after
94/// its future completes before releasing its own listener. This gives callers a
95/// chance to listen to the stream if necessary. A caller can determine if the
96/// image ended up in the cache by calling [ImageProvider.obtainCacheStatus]. If
97/// it is only held as [ImageCacheStatus.live], and the caller wishes to keep
98/// the resolved image in memory, the caller should immediately call
99/// `provider.resolve` and add a listener to the returned [ImageStream]. The
100/// image will remain pinned in memory at least until the caller removes its
101/// listener from the stream, even if it would not otherwise fit into the cache.
102///
103/// Callers should be cautious about pinning large images or a large number of
104/// images in memory, as this can result in running out of memory and being
105/// killed by the operating system. The lower the available physical memory, the
106/// more susceptible callers will be to running into OOM issues. These issues
107/// manifest as immediate process death, sometimes with no other error messages.
108///
109/// The [BuildContext] and [Size] are used to select an image configuration
110/// (see [createLocalImageConfiguration]).
111///
112/// The returned future will not complete with error, even if precaching
113/// failed. The `onError` argument can be used to manually handle errors while
114/// pre-caching.
115///
116/// See also:
117///
118/// * [ImageCache], which holds images that may be reused.
119Future<void> precacheImage(
120 ImageProvider provider,
121 BuildContext context, {
122 Size? size,
123 ImageErrorListener? onError,
124}) {
125 final ImageConfiguration config = createLocalImageConfiguration(context, size: size);
126 final Completer<void> completer = Completer<void>();
127 final ImageStream stream = provider.resolve(config);
128 ImageStreamListener? listener;
129 listener = ImageStreamListener(
130 (ImageInfo? image, bool sync) {
131 if (!completer.isCompleted) {
132 completer.complete();
133 }
134 // Give callers until at least the end of the frame to subscribe to the
135 // image stream.
136 // See ImageCache._liveImages
137 SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) {
138 image?.dispose();
139 stream.removeListener(listener!);
140 }, debugLabel: 'precacheImage.removeListener');
141 },
142 onError: (Object exception, StackTrace? stackTrace) {
143 if (!completer.isCompleted) {
144 completer.complete();
145 }
146 stream.removeListener(listener!);
147 if (onError != null) {
148 onError(exception, stackTrace);
149 } else {
150 FlutterError.reportError(
151 FlutterErrorDetails(
152 context: ErrorDescription('image failed to precache'),
153 library: 'image resource service',
154 exception: exception,
155 stack: stackTrace,
156 silent: true,
157 ),
158 );
159 }
160 },
161 );
162 stream.addListener(listener);
163 return completer.future;
164}
165
166/// Signature used by [Image.frameBuilder] to control the widget that will be
167/// used when an [Image] is built.
168///
169/// The `child` argument contains the default image widget.
170/// Typically, this builder will wrap the `child` widget in some
171/// way and return the wrapped widget. If this builder returns `child` directly,
172/// it will yield the same result as if [Image.frameBuilder] was null.
173///
174/// The `frame` argument specifies the index of the current image frame being
175/// rendered. It will be null before the first image frame is ready, and zero
176/// for the first image frame. For single-frame images, it will never be greater
177/// than zero. For multi-frame images (such as animated GIFs), it will increase
178/// by one every time a new image frame is shown (including when the image
179/// animates in a loop).
180///
181/// The `wasSynchronouslyLoaded` argument specifies whether the image was
182/// available synchronously (on the same
183/// [rendering pipeline frame](rendering/RendererBinding/drawFrame.html) as the
184/// `Image` widget itself was created) and thus able to be painted immediately.
185/// If this is false, then there was one or more rendering pipeline frames where
186/// the image wasn't yet available to be painted. For multi-frame images (such
187/// as animated GIFs), the value of this argument will be the same for all image
188/// frames. In other words, if the first image frame was available immediately,
189/// then this argument will be true for all image frames.
190///
191/// See also:
192///
193/// * [Image.frameBuilder], which makes use of this signature in the [Image]
194/// widget.
195typedef ImageFrameBuilder =
196 Widget Function(BuildContext context, Widget child, int? frame, bool wasSynchronouslyLoaded);
197
198/// Signature used by [Image.loadingBuilder] to build a representation of the
199/// image's loading progress.
200///
201/// This is useful for images that are incrementally loaded (e.g. over a local
202/// file system or a network), and the application wishes to give the user an
203/// indication of when the image will be displayed.
204///
205/// The `child` argument contains the default image widget and is guaranteed to
206/// be non-null. Typically, this builder will wrap the `child` widget in some
207/// way and return the wrapped widget. If this builder returns `child` directly,
208/// it will yield the same result as if [Image.loadingBuilder] was null.
209///
210/// The `loadingProgress` argument contains the current progress towards loading
211/// the image. This argument will be non-null while the image is loading, but it
212/// will be null in the following cases:
213///
214/// * When the widget is first rendered before any bytes have been loaded.
215/// * When an image has been fully loaded and is available to be painted.
216///
217/// If callers are implementing custom [ImageProvider] and [ImageStream]
218/// instances (which is very rare), it's possible to produce image streams that
219/// continue to fire image chunk events after an image frame has been loaded.
220/// In such cases, the `child` parameter will represent the current
221/// fully-loaded image frame.
222///
223/// See also:
224///
225/// * [Image.loadingBuilder], which makes use of this signature in the [Image]
226/// widget.
227/// * [ImageChunkListener], a lower-level signature for listening to raw
228/// [ImageChunkEvent]s.
229typedef ImageLoadingBuilder =
230 Widget Function(BuildContext context, Widget child, ImageChunkEvent? loadingProgress);
231
232/// Signature used by [Image.errorBuilder] to create a replacement widget to
233/// render instead of the image.
234typedef ImageErrorWidgetBuilder =
235 Widget Function(BuildContext context, Object error, StackTrace? stackTrace);
236
237/// A widget that displays an image.
238///
239/// {@youtube 560 315 https://www.youtube.com/watch?v=7oIAs-0G4mw}
240///
241/// Several constructors are provided for the various ways that an image can be
242/// specified:
243///
244/// * [Image.new], for obtaining an image from an [ImageProvider].
245/// * [Image.asset], for obtaining an image from an [AssetBundle]
246/// using a key.
247/// * [Image.network], for obtaining an image from a URL.
248/// * [Image.file], for obtaining an image from a [File].
249/// * [Image.memory], for obtaining an image from a [Uint8List].
250///
251/// The following image formats are supported: {@macro dart.ui.imageFormats}
252///
253/// To automatically perform pixel-density-aware asset resolution, specify the
254/// image using an [AssetImage] and make sure that a [MaterialApp], [WidgetsApp],
255/// or [MediaQuery] widget exists above the [Image] widget in the widget tree.
256///
257/// The image is painted using [paintImage], which describes the meanings of the
258/// various fields on this class in more detail.
259///
260/// {@tool snippet}
261/// The default constructor can be used with any [ImageProvider], such as a
262/// [NetworkImage], to display an image from the internet.
263///
264/// ![An image of an owl displayed by the image widget](https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg)
265///
266/// ```dart
267/// const Image(
268/// image: NetworkImage('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg'),
269/// )
270/// ```
271/// {@end-tool}
272///
273/// {@tool snippet}
274/// The [Image] Widget also provides several constructors to display different
275/// types of images for convenience. In this example, use the [Image.network]
276/// constructor to display an image from the internet.
277///
278/// ![An image of an owl displayed by the image widget using the shortcut constructor](https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg)
279///
280/// ```dart
281/// Image.network('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg')
282/// ```
283/// {@end-tool}
284///
285/// ## Memory usage
286///
287/// The image is stored in memory in uncompressed form (so that it can be
288/// rendered). Large images will use a lot of memory: a 4K image (3840×2160)
289/// will use over 30MB of RAM (assuming 32 bits per pixel).
290///
291/// This problem is exacerbated by the images being cached in the [ImageCache],
292/// so large images can use memory for even longer than they are displayed.
293///
294/// The [Image.asset], [Image.network], [Image.file], and [Image.memory]
295/// constructors allow a custom decode size to be specified through `cacheWidth`
296/// and `cacheHeight` parameters. The engine will then decode and store the
297/// image at the specified size, instead of the image's natural size.
298///
299/// This can significantly reduce the memory usage. For example, a 4K image that
300/// will be rendered at only 384×216 pixels (one-tenth the horizontal and
301/// vertical dimensions) would only use 330KB if those dimensions are specified
302/// using the `cacheWidth` and `cacheHeight` parameters, a 100-fold reduction in
303/// memory usage.
304///
305/// ## Custom image providers
306///
307/// {@tool dartpad}
308/// In this example, a variant of [NetworkImage] is created that passes all the
309/// [ImageConfiguration] information (locale, platform, size, etc) to the server
310/// using query arguments in the image URL.
311///
312/// ** See code in examples/api/lib/painting/image_provider/image_provider.0.dart **
313/// {@end-tool}
314///
315/// See also:
316///
317/// * [Icon], which shows an image from a font.
318/// * [Ink.image], which is the preferred way to show an image in a
319/// material application (especially if the image is in a [Material] and will
320/// have an [InkWell] on top of it).
321/// * [Image](dart-ui/Image-class.html), the class in the [dart:ui] library.
322/// * Cookbook: [Display images from the internet](https://docs.flutter.dev/cookbook/images/network-image)
323/// * Cookbook: [Fade in images with a placeholder](https://docs.flutter.dev/cookbook/images/fading-in-images)
324class Image extends StatefulWidget {
325 /// Creates a widget that displays an image.
326 ///
327 /// To show an image from the network or from an asset bundle, consider using
328 /// [Image.network] and [Image.asset] respectively.
329 ///
330 /// The `scale` argument specifies the linear scale factor for drawing this
331 /// image at its intended size and applies to both the width and the height.
332 /// {@macro flutter.painting.imageInfo.scale}
333 ///
334 /// Either the [width] and [height] arguments should be specified, or the
335 /// widget should be placed in a context that sets tight layout constraints.
336 /// Otherwise, the image dimensions will change as the image is loaded, which
337 /// will result in ugly layout changes.
338 ///
339 /// {@template flutter.widgets.image.filterQualityParameter}
340 /// Use [filterQuality] to specify the rendering quality of the image.
341 /// {@endtemplate}
342 ///
343 /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
344 const Image({
345 super.key,
346 required this.image,
347 this.frameBuilder,
348 this.loadingBuilder,
349 this.errorBuilder,
350 this.semanticLabel,
351 this.excludeFromSemantics = false,
352 this.width,
353 this.height,
354 this.color,
355 this.opacity,
356 this.colorBlendMode,
357 this.fit,
358 this.alignment = Alignment.center,
359 this.repeat = ImageRepeat.noRepeat,
360 this.centerSlice,
361 this.matchTextDirection = false,
362 this.gaplessPlayback = false,
363 this.isAntiAlias = false,
364 this.filterQuality = FilterQuality.medium,
365 });
366
367 /// Creates a widget that displays an [ImageStream] obtained from the network.
368 ///
369 /// Either the [width] and [height] arguments should be specified, or the
370 /// widget should be placed in a context that sets tight layout constraints.
371 /// Otherwise, the image dimensions will change as the image is loaded, which
372 /// will result in ugly layout changes.
373 ///
374 /// The `scale` argument specifies the linear scale factor for drawing this
375 /// image at its intended size and applies to both the width and the height.
376 /// {@macro flutter.painting.imageInfo.scale}
377 ///
378 /// All network images are cached regardless of HTTP headers.
379 ///
380 /// An optional [headers] argument can be used to send custom HTTP headers
381 /// with the image request.
382 ///
383 /// {@macro flutter.widgets.image.filterQualityParameter}
384 ///
385 /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
386 ///
387 /// If `cacheWidth` or `cacheHeight` are provided, they indicate to the
388 /// engine that the image should be decoded at the specified size. The image
389 /// will be rendered to the constraints of the layout or [width] and [height]
390 /// regardless of these parameters. These parameters are primarily intended
391 /// to reduce the memory usage of [ImageCache].
392 ///
393 /// In the case where the network image is on the Web platform, the [cacheWidth]
394 /// and [cacheHeight] parameters are ignored as the web engine delegates
395 /// image decoding to the web which does not support custom decode sizes.
396 ///
397 /// ### Same-origin policy on Web
398 ///
399 /// Due to browser restriction on Cross-Origin Resource Sharing (CORS),
400 /// Flutter on the Web platform can not fetch images from other origins
401 /// (domain, scheme, or port) than the origin that hosts the app, unless the
402 /// image hosting origin explicitly allows so. CORS errors can be resolved
403 /// by configuring the image hosting server. More information can be
404 /// found at Mozilla's introduction on
405 /// [same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)
406 /// and
407 /// [CORS errors](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors).
408 ///
409 /// If it's not possible to configure the host, such as when images are hosted
410 /// on a CDN or from arbitrary URLs, the app can set the
411 /// `webHtmlElementStrategy` parameter of [Image.network] to display the image
412 /// in an HTML element, which bypasses the same-origin policy.
413 ///
414 /// The HTML element is placed in a platform view, and therefore has the
415 /// following drawbacks:
416 ///
417 /// * Suboptimal performance.
418 /// * Can't be captured by screenshot widgets.
419 /// * The `headers` argument must be null or empty.
420 /// * Some image options are ignored, including [opacity], [colorBlendMode],
421 /// [repeat], filtering, and blurring.
422 ///
423 /// By default, this feature is turned off ([WebHtmlElementStrategy.never]).
424 Image.network(
425 String src, {
426 super.key,
427 double scale = 1.0,
428 this.frameBuilder,
429 this.loadingBuilder,
430 this.errorBuilder,
431 this.semanticLabel,
432 this.excludeFromSemantics = false,
433 this.width,
434 this.height,
435 this.color,
436 this.opacity,
437 this.colorBlendMode,
438 this.fit,
439 this.alignment = Alignment.center,
440 this.repeat = ImageRepeat.noRepeat,
441 this.centerSlice,
442 this.matchTextDirection = false,
443 this.gaplessPlayback = false,
444 this.filterQuality = FilterQuality.medium,
445 this.isAntiAlias = false,
446 Map<String, String>? headers,
447 int? cacheWidth,
448 int? cacheHeight,
449 WebHtmlElementStrategy webHtmlElementStrategy = WebHtmlElementStrategy.never,
450 }) : image = ResizeImage.resizeIfNeeded(
451 cacheWidth,
452 cacheHeight,
453 NetworkImage(
454 src,
455 scale: scale,
456 headers: headers,
457 webHtmlElementStrategy: webHtmlElementStrategy,
458 ),
459 ),
460 assert(cacheWidth == null || cacheWidth > 0),
461 assert(cacheHeight == null || cacheHeight > 0);
462
463 /// Creates a widget that displays an [ImageStream] obtained from a [File].
464 ///
465 /// The `scale` argument specifies the linear scale factor for drawing this
466 /// image at its intended size and applies to both the width and the height.
467 /// {@macro flutter.painting.imageInfo.scale}
468 ///
469 /// Either the [width] and [height] arguments should be specified, or the
470 /// widget should be placed in a context that sets tight layout constraints.
471 /// Otherwise, the image dimensions will change as the image is loaded, which
472 /// will result in ugly layout changes.
473 ///
474 /// On Android, this may require the
475 /// `android.permission.READ_EXTERNAL_STORAGE` permission.
476 ///
477 /// {@macro flutter.widgets.image.filterQualityParameter}
478 ///
479 /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
480 ///
481 /// If `cacheWidth` or `cacheHeight` are provided, they indicate to the
482 /// engine that the image must be decoded at the specified size. The image
483 /// will be rendered to the constraints of the layout or [width] and [height]
484 /// regardless of these parameters. These parameters are primarily intended
485 /// to reduce the memory usage of [ImageCache].
486 ///
487 /// Loading an image from a file creates an in memory copy of the file,
488 /// which is retained in the [ImageCache]. The underlying file is not
489 /// monitored for changes. If it does change, the application should evict
490 /// the entry from the [ImageCache].
491 ///
492 /// See also:
493 ///
494 /// * [FileImage] provider for evicting the underlying file easily.
495 Image.file(
496 File file, {
497 super.key,
498 double scale = 1.0,
499 this.frameBuilder,
500 this.errorBuilder,
501 this.semanticLabel,
502 this.excludeFromSemantics = false,
503 this.width,
504 this.height,
505 this.color,
506 this.opacity,
507 this.colorBlendMode,
508 this.fit,
509 this.alignment = Alignment.center,
510 this.repeat = ImageRepeat.noRepeat,
511 this.centerSlice,
512 this.matchTextDirection = false,
513 this.gaplessPlayback = false,
514 this.isAntiAlias = false,
515 this.filterQuality = FilterQuality.medium,
516 int? cacheWidth,
517 int? cacheHeight,
518 }) : // FileImage is not supported on Flutter Web therefore neither this method.
519 assert(
520 !kIsWeb,
521 'Image.file is not supported on Flutter Web. '
522 'Consider using either Image.asset or Image.network instead.',
523 ),
524 image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, FileImage(file, scale: scale)),
525 loadingBuilder = null,
526 assert(cacheWidth == null || cacheWidth > 0),
527 assert(cacheHeight == null || cacheHeight > 0);
528
529 // TODO(ianh): Implement the following (see ../services/image_resolution.dart):
530 //
531 // * If [width] and [height] are both specified, and [scale] is not, then
532 // size-aware asset resolution will be attempted also, with the given
533 // dimensions interpreted as logical pixels.
534 //
535 // * If the images have platform, locale, or directionality variants, the
536 // current platform, locale, and directionality are taken into account
537 // during asset resolution as well.
538 /// Creates a widget that displays an [ImageStream] obtained from an asset
539 /// bundle. The key for the image is given by the `name` argument.
540 ///
541 /// The `package` argument must be non-null when displaying an image from a
542 /// package and null otherwise. See the `Assets in packages` section for
543 /// details.
544 ///
545 /// If the `bundle` argument is omitted or null, then the
546 /// [DefaultAssetBundle] will be used.
547 ///
548 /// The `scale` argument specifies the linear scale factor for drawing this
549 /// image at its intended size and applies to both the width and the height.
550 /// {@macro flutter.painting.imageInfo.scale}
551 ///
552 /// By default, the pixel-density-aware asset resolution will be attempted. In
553 /// addition:
554 ///
555 /// * If the `scale` argument is provided and is not null, then the exact
556 /// asset specified will be used. To display an image variant with a specific
557 /// density, the exact path must be provided (e.g. `images/2x/cat.png`).
558 ///
559 /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
560 ///
561 /// If `cacheWidth` or `cacheHeight` are provided, they indicate to the
562 /// engine that the image must be decoded at the specified size. The image
563 /// will be rendered to the constraints of the layout or [width] and [height]
564 /// regardless of these parameters. These parameters are primarily intended
565 /// to reduce the memory usage of [ImageCache].
566 ///
567 /// Either the [width] and [height] arguments should be specified, or the
568 /// widget should be placed in a context that sets tight layout constraints.
569 /// Otherwise, the image dimensions will change as the image is loaded, which
570 /// will result in ugly layout changes.
571 ///
572 /// {@macro flutter.widgets.image.filterQualityParameter}
573 ///
574 /// {@tool snippet}
575 ///
576 /// Suppose that the project's `pubspec.yaml` file contains the following:
577 ///
578 /// ```yaml
579 /// flutter:
580 /// assets:
581 /// - images/cat.png
582 /// - images/2x/cat.png
583 /// - images/3.5x/cat.png
584 /// ```
585 /// {@end-tool}
586 ///
587 /// On a screen with a device pixel ratio of 2.0, the following widget would
588 /// render the `images/2x/cat.png` file:
589 ///
590 /// ```dart
591 /// Image.asset('images/cat.png')
592 /// ```
593 ///
594 /// This corresponds to the file that is in the project's `images/2x/`
595 /// directory with the name `cat.png` (the paths are relative to the
596 /// `pubspec.yaml` file).
597 ///
598 /// On a device with a 4.0 device pixel ratio, the `images/3.5x/cat.png` asset
599 /// would be used. On a device with a 1.0 device pixel ratio, the
600 /// `images/cat.png` resource would be used.
601 ///
602 /// The `images/cat.png` image can be omitted from disk (though it must still
603 /// be present in the manifest). If it is omitted, then on a device with a 1.0
604 /// device pixel ratio, the `images/2x/cat.png` image would be used instead.
605 ///
606 ///
607 /// ## Assets in packages
608 ///
609 /// To create the widget with an asset from a package, the [package] argument
610 /// must be provided. For instance, suppose a package called `my_icons` has
611 /// `icons/heart.png` .
612 ///
613 /// {@tool snippet}
614 /// Then to display the image, use:
615 ///
616 /// ```dart
617 /// Image.asset('icons/heart.png', package: 'my_icons')
618 /// ```
619 /// {@end-tool}
620 ///
621 /// Assets used by the package itself should also be displayed using the
622 /// [package] argument as above.
623 ///
624 /// If the desired asset is specified in the `pubspec.yaml` of the package, it
625 /// is bundled automatically with the app. In particular, assets used by the
626 /// package itself must be specified in its `pubspec.yaml`.
627 ///
628 /// A package can also choose to have assets in its 'lib/' folder that are not
629 /// specified in its `pubspec.yaml`. In this case for those images to be
630 /// bundled, the app has to specify which ones to include. For instance a
631 /// package named `fancy_backgrounds` could have:
632 ///
633 /// lib/backgrounds/background1.png
634 /// lib/backgrounds/background2.png
635 /// lib/backgrounds/background3.png
636 ///
637 /// To include, say the first image, the `pubspec.yaml` of the app should
638 /// specify it in the assets section:
639 ///
640 /// ```yaml
641 /// assets:
642 /// - packages/fancy_backgrounds/backgrounds/background1.png
643 /// ```
644 ///
645 /// The `lib/` is implied, so it should not be included in the asset path.
646 ///
647 ///
648 /// See also:
649 ///
650 /// * [AssetImage], which is used to implement the behavior when the scale is
651 /// omitted.
652 /// * [ExactAssetImage], which is used to implement the behavior when the
653 /// scale is present.
654 /// * <https://docs.flutter.dev/ui/assets/assets-and-images>, an introduction to assets in
655 /// Flutter.
656 Image.asset(
657 String name, {
658 super.key,
659 AssetBundle? bundle,
660 this.frameBuilder,
661 this.errorBuilder,
662 this.semanticLabel,
663 this.excludeFromSemantics = false,
664 double? scale,
665 this.width,
666 this.height,
667 this.color,
668 this.opacity,
669 this.colorBlendMode,
670 this.fit,
671 this.alignment = Alignment.center,
672 this.repeat = ImageRepeat.noRepeat,
673 this.centerSlice,
674 this.matchTextDirection = false,
675 this.gaplessPlayback = false,
676 this.isAntiAlias = false,
677 String? package,
678 this.filterQuality = FilterQuality.medium,
679 int? cacheWidth,
680 int? cacheHeight,
681 }) : image = ResizeImage.resizeIfNeeded(
682 cacheWidth,
683 cacheHeight,
684 scale != null
685 ? ExactAssetImage(name, bundle: bundle, scale: scale, package: package)
686 : AssetImage(name, bundle: bundle, package: package),
687 ),
688 loadingBuilder = null,
689 assert(cacheWidth == null || cacheWidth > 0),
690 assert(cacheHeight == null || cacheHeight > 0);
691
692 /// Creates a widget that displays an [ImageStream] obtained from a [Uint8List].
693 ///
694 /// The `bytes` argument specifies encoded image bytes, which can be encoded
695 /// in any of the following supported image formats:
696 /// {@macro dart.ui.imageFormats}
697 ///
698 /// The `scale` argument specifies the linear scale factor for drawing this
699 /// image at its intended size and applies to both the width and the height.
700 /// {@macro flutter.painting.imageInfo.scale}
701 ///
702 /// This only accepts compressed image formats (e.g. PNG). Uncompressed
703 /// formats like rawRgba (the default format of [dart:ui.Image.toByteData])
704 /// will lead to exceptions.
705 ///
706 /// Either the [width] and [height] arguments should be specified, or the
707 /// widget should be placed in a context that sets tight layout constraints.
708 /// Otherwise, the image dimensions will change as the image is loaded, which
709 /// will result in ugly layout changes.
710 ///
711 /// {@macro flutter.widgets.image.filterQualityParameter}
712 ///
713 /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
714 ///
715 /// If `cacheWidth` or `cacheHeight` are provided, they indicate to the
716 /// engine that the image must be decoded at the specified size. The image
717 /// will be rendered to the constraints of the layout or [width] and [height]
718 /// regardless of these parameters. These parameters are primarily intended
719 /// to reduce the memory usage of [ImageCache].
720 Image.memory(
721 Uint8List bytes, {
722 super.key,
723 double scale = 1.0,
724 this.frameBuilder,
725 this.errorBuilder,
726 this.semanticLabel,
727 this.excludeFromSemantics = false,
728 this.width,
729 this.height,
730 this.color,
731 this.opacity,
732 this.colorBlendMode,
733 this.fit,
734 this.alignment = Alignment.center,
735 this.repeat = ImageRepeat.noRepeat,
736 this.centerSlice,
737 this.matchTextDirection = false,
738 this.gaplessPlayback = false,
739 this.isAntiAlias = false,
740 this.filterQuality = FilterQuality.medium,
741 int? cacheWidth,
742 int? cacheHeight,
743 }) : image = ResizeImage.resizeIfNeeded(
744 cacheWidth,
745 cacheHeight,
746 MemoryImage(bytes, scale: scale),
747 ),
748 loadingBuilder = null,
749 assert(cacheWidth == null || cacheWidth > 0),
750 assert(cacheHeight == null || cacheHeight > 0);
751
752 /// The image to display.
753 final ImageProvider image;
754
755 /// A builder function responsible for creating the widget that represents
756 /// this image.
757 ///
758 /// If this is null, this widget will display an image that is painted as
759 /// soon as the first image frame is available (and will appear to "pop" in
760 /// if it becomes available asynchronously). Callers might use this builder to
761 /// add effects to the image (such as fading the image in when it becomes
762 /// available) or to display a placeholder widget while the image is loading.
763 ///
764 /// For more information on how to interpret the arguments that are passed to
765 /// this builder, see the documentation on [ImageFrameBuilder].
766 ///
767 /// To have finer-grained control over the way that an image's loading
768 /// progress is communicated to the user, see [loadingBuilder].
769 ///
770 /// ## Chaining with [loadingBuilder]
771 ///
772 /// If a [loadingBuilder] has _also_ been specified for an image, the two
773 /// builders will be chained together: the _result_ of this builder will
774 /// be passed as the `child` argument to the [loadingBuilder]. For example,
775 /// consider the following builders used in conjunction:
776 ///
777 /// {@template flutter.widgets.Image.frameBuilder.chainedBuildersExample}
778 /// ```dart
779 /// Image(
780 /// image: _image,
781 /// frameBuilder: (BuildContext context, Widget child, int? frame, bool? wasSynchronouslyLoaded) {
782 /// return Padding(
783 /// padding: const EdgeInsets.all(8.0),
784 /// child: child,
785 /// );
786 /// },
787 /// loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
788 /// return Center(child: child);
789 /// },
790 /// )
791 /// ```
792 ///
793 /// In this example, the widget hierarchy will contain the following:
794 ///
795 /// ```dart
796 /// Center(
797 /// child: Padding(
798 /// padding: const EdgeInsets.all(8.0),
799 /// child: image,
800 /// ),
801 /// ),
802 /// ```
803 /// {@endtemplate}
804 ///
805 /// {@tool dartpad}
806 /// The following sample demonstrates how to use this builder to implement an
807 /// image that fades in once it's been loaded.
808 ///
809 /// This sample contains a limited subset of the functionality that the
810 /// [FadeInImage] widget provides out of the box.
811 ///
812 /// ** See code in examples/api/lib/widgets/image/image.frame_builder.0.dart **
813 /// {@end-tool}
814 final ImageFrameBuilder? frameBuilder;
815
816 /// A builder that specifies the widget to display to the user while an image
817 /// is still loading.
818 ///
819 /// If this is null, and the image is loaded incrementally (e.g. over a
820 /// network), the user will receive no indication of the progress as the
821 /// bytes of the image are loaded.
822 ///
823 /// For more information on how to interpret the arguments that are passed to
824 /// this builder, see the documentation on [ImageLoadingBuilder].
825 ///
826 /// ## Performance implications
827 ///
828 /// If a [loadingBuilder] is specified for an image, the [Image] widget is
829 /// likely to be rebuilt on every
830 /// [rendering pipeline frame](rendering/RendererBinding/drawFrame.html) until
831 /// the image has loaded. This is useful for cases such as displaying a loading
832 /// progress indicator, but for simpler cases such as displaying a placeholder
833 /// widget that doesn't depend on the loading progress (e.g. static "loading"
834 /// text), [frameBuilder] will likely work and not incur as much cost.
835 ///
836 /// ## Chaining with [frameBuilder]
837 ///
838 /// If a [frameBuilder] has _also_ been specified for an image, the two
839 /// builders will be chained together: the `child` argument to this
840 /// builder will contain the _result_ of the [frameBuilder]. For example,
841 /// consider the following builders used in conjunction:
842 ///
843 /// {@macro flutter.widgets.Image.frameBuilder.chainedBuildersExample}
844 ///
845 /// {@tool dartpad}
846 /// The following sample uses [loadingBuilder] to show a
847 /// [CircularProgressIndicator] while an image loads over the network.
848 ///
849 /// ** See code in examples/api/lib/widgets/image/image.loading_builder.0.dart **
850 /// {@end-tool}
851 ///
852 /// Run against a real-world image on a slow network, the previous example
853 /// renders the following loading progress indicator while the image loads
854 /// before rendering the completed image.
855 ///
856 /// {@animation 400 400 https://flutter.github.io/assets-for-api-docs/assets/widgets/loading_progress_image.mp4}
857 final ImageLoadingBuilder? loadingBuilder;
858
859 /// A builder function that is called if an error occurs during image loading.
860 ///
861 /// If this builder is not provided, any exceptions will be reported to
862 /// [FlutterError.onError]. If it is provided, the caller should either handle
863 /// the exception by providing a replacement widget, or rethrow the exception.
864 ///
865 /// {@tool dartpad}
866 /// The following sample uses [errorBuilder] to show a '😢' in place of the
867 /// image that fails to load, and prints the error to the console.
868 ///
869 /// ** See code in examples/api/lib/widgets/image/image.error_builder.0.dart **
870 /// {@end-tool}
871 final ImageErrorWidgetBuilder? errorBuilder;
872
873 /// If non-null, require the image to have this width (in logical pixels).
874 ///
875 /// If null, the image will pick a size that best preserves its intrinsic
876 /// aspect ratio.
877 ///
878 /// It is strongly recommended that either both the [width] and the [height]
879 /// be specified, or that the widget be placed in a context that sets tight
880 /// layout constraints, so that the image does not change size as it loads.
881 /// Consider using [fit] to adapt the image's rendering to fit the given width
882 /// and height if the exact image dimensions are not known in advance.
883 final double? width;
884
885 /// If non-null, require the image to have this height (in logical pixels).
886 ///
887 /// If null, the image will pick a size that best preserves its intrinsic
888 /// aspect ratio.
889 ///
890 /// It is strongly recommended that either both the [width] and the [height]
891 /// be specified, or that the widget be placed in a context that sets tight
892 /// layout constraints, so that the image does not change size as it loads.
893 /// Consider using [fit] to adapt the image's rendering to fit the given width
894 /// and height if the exact image dimensions are not known in advance.
895 final double? height;
896
897 /// If non-null, this color is blended with each image pixel using [colorBlendMode].
898 final Color? color;
899
900 /// If non-null, the value from the [Animation] is multiplied with the opacity
901 /// of each image pixel before painting onto the canvas.
902 ///
903 /// This is more efficient than using [FadeTransition] to change the opacity
904 /// of an image, since this avoids creating a new composited layer. Composited
905 /// layers may double memory usage as the image is painted onto an offscreen
906 /// render target.
907 ///
908 /// See also:
909 ///
910 /// * [AlwaysStoppedAnimation], which allows you to create an [Animation]
911 /// from a single opacity value.
912 final Animation<double>? opacity;
913
914 /// The rendering quality of the image.
915 ///
916 /// {@template flutter.widgets.image.filterQuality}
917 /// If the image is of a high quality and its pixels are perfectly aligned
918 /// with the physical screen pixels, extra quality enhancement may not be
919 /// necessary. If so, then [FilterQuality.none] would be the most efficient.
920 ///
921 /// If the pixels are not perfectly aligned with the screen pixels, or if the
922 /// image itself is of a low quality, [FilterQuality.none] may produce
923 /// undesirable artifacts. Consider using other [FilterQuality] values to
924 /// improve the rendered image quality in this case. Pixels may be misaligned
925 /// with the screen pixels as a result of transforms or scaling.
926 ///
927 /// Defaults to [FilterQuality.medium].
928 ///
929 /// See also:
930 ///
931 /// * [FilterQuality], the enum containing all possible filter quality
932 /// options.
933 /// {@endtemplate}
934 final FilterQuality filterQuality;
935
936 /// Used to combine [color] with this image.
937 ///
938 /// The default is [BlendMode.srcIn]. In terms of the blend mode, [color] is
939 /// the source and this image is the destination.
940 ///
941 /// See also:
942 ///
943 /// * [BlendMode], which includes an illustration of the effect of each blend mode.
944 final BlendMode? colorBlendMode;
945
946 /// How to inscribe the image into the space allocated during layout.
947 ///
948 /// The default varies based on the other fields. See the discussion at
949 /// [paintImage].
950 final BoxFit? fit;
951
952 /// How to align the image within its bounds.
953 ///
954 /// The alignment aligns the given position in the image to the given position
955 /// in the layout bounds. For example, an [Alignment] alignment of (-1.0,
956 /// -1.0) aligns the image to the top-left corner of its layout bounds, while an
957 /// [Alignment] alignment of (1.0, 1.0) aligns the bottom right of the
958 /// image with the bottom right corner of its layout bounds. Similarly, an
959 /// alignment of (0.0, 1.0) aligns the bottom middle of the image with the
960 /// middle of the bottom edge of its layout bounds.
961 ///
962 /// To display a subpart of an image, consider using a [CustomPainter] and
963 /// [Canvas.drawImageRect].
964 ///
965 /// If the [alignment] is [TextDirection]-dependent (i.e. if it is a
966 /// [AlignmentDirectional]), then an ambient [Directionality] widget
967 /// must be in scope.
968 ///
969 /// Defaults to [Alignment.center].
970 ///
971 /// See also:
972 ///
973 /// * [Alignment], a class with convenient constants typically used to
974 /// specify an [AlignmentGeometry].
975 /// * [AlignmentDirectional], like [Alignment] for specifying alignments
976 /// relative to text direction.
977 final AlignmentGeometry alignment;
978
979 /// How to paint any portions of the layout bounds not covered by the image.
980 final ImageRepeat repeat;
981
982 /// The center slice for a nine-patch image.
983 ///
984 /// The region of the image inside the center slice will be stretched both
985 /// horizontally and vertically to fit the image into its destination. The
986 /// region of the image above and below the center slice will be stretched
987 /// only horizontally and the region of the image to the left and right of
988 /// the center slice will be stretched only vertically.
989 final Rect? centerSlice;
990
991 /// Whether to paint the image in the direction of the [TextDirection].
992 ///
993 /// If this is true, then in [TextDirection.ltr] contexts, the image will be
994 /// drawn with its origin in the top left (the "normal" painting direction for
995 /// images); and in [TextDirection.rtl] contexts, the image will be drawn with
996 /// a scaling factor of -1 in the horizontal direction so that the origin is
997 /// in the top right.
998 ///
999 /// This is occasionally used with images in right-to-left environments, for
1000 /// images that were designed for left-to-right locales. Be careful, when
1001 /// using this, to not flip images with integral shadows, text, or other
1002 /// effects that will look incorrect when flipped.
1003 ///
1004 /// If this is true, there must be an ambient [Directionality] widget in
1005 /// scope.
1006 final bool matchTextDirection;
1007
1008 /// Whether to continue showing the old image (true), or briefly show nothing
1009 /// (false), when the image provider changes. The default value is false.
1010 ///
1011 /// ## Design discussion
1012 ///
1013 /// ### Why is the default value of [gaplessPlayback] false?
1014 ///
1015 /// Having the default value of [gaplessPlayback] be false helps prevent
1016 /// situations where stale or misleading information might be presented.
1017 /// Consider the following case:
1018 ///
1019 /// We have constructed a 'Person' widget that displays an avatar [Image] of
1020 /// the currently loaded person along with their name. We could request for a
1021 /// new person to be loaded into the widget at any time. Suppose we have a
1022 /// person currently loaded and the widget loads a new person. What happens
1023 /// if the [Image] fails to load?
1024 ///
1025 /// * Option A ([gaplessPlayback] = false): The new person's name is coupled
1026 /// with a blank image.
1027 ///
1028 /// * Option B ([gaplessPlayback] = true): The widget displays the avatar of
1029 /// the previous person and the name of the newly loaded person.
1030 ///
1031 /// This is why the default value is false. Most of the time, when you change
1032 /// the image provider you're not just changing the image, you're removing the
1033 /// old widget and adding a new one and not expecting them to have any
1034 /// relationship. With [gaplessPlayback] on you might accidentally break this
1035 /// expectation and re-use the old widget.
1036 final bool gaplessPlayback;
1037
1038 /// A Semantic description of the image.
1039 ///
1040 /// Used to provide a description of the image to TalkBack on Android, and
1041 /// VoiceOver on iOS.
1042 final String? semanticLabel;
1043
1044 /// Whether to exclude this image from semantics.
1045 ///
1046 /// Useful for images which do not contribute meaningful information to an
1047 /// application.
1048 final bool excludeFromSemantics;
1049
1050 /// Whether to paint the image with anti-aliasing.
1051 ///
1052 /// Anti-aliasing alleviates the sawtooth artifact when the image is rotated.
1053 final bool isAntiAlias;
1054
1055 @override
1056 State<Image> createState() => _ImageState();
1057
1058 @override
1059 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
1060 super.debugFillProperties(properties);
1061 properties.add(DiagnosticsProperty<ImageProvider>('image', image));
1062 properties.add(DiagnosticsProperty<Function>('frameBuilder', frameBuilder));
1063 properties.add(DiagnosticsProperty<Function>('loadingBuilder', loadingBuilder));
1064 properties.add(DoubleProperty('width', width, defaultValue: null));
1065 properties.add(DoubleProperty('height', height, defaultValue: null));
1066 properties.add(ColorProperty('color', color, defaultValue: null));
1067 properties.add(DiagnosticsProperty<Animation<double>?>('opacity', opacity, defaultValue: null));
1068 properties.add(EnumProperty<BlendMode>('colorBlendMode', colorBlendMode, defaultValue: null));
1069 properties.add(EnumProperty<BoxFit>('fit', fit, defaultValue: null));
1070 properties.add(
1071 DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null),
1072 );
1073 properties.add(EnumProperty<ImageRepeat>('repeat', repeat, defaultValue: ImageRepeat.noRepeat));
1074 properties.add(DiagnosticsProperty<Rect>('centerSlice', centerSlice, defaultValue: null));
1075 properties.add(
1076 FlagProperty('matchTextDirection', value: matchTextDirection, ifTrue: 'match text direction'),
1077 );
1078 properties.add(StringProperty('semanticLabel', semanticLabel, defaultValue: null));
1079 properties.add(DiagnosticsProperty<bool>('this.excludeFromSemantics', excludeFromSemantics));
1080 properties.add(EnumProperty<FilterQuality>('filterQuality', filterQuality));
1081 }
1082}
1083
1084class _ImageState extends State<Image> with WidgetsBindingObserver {
1085 ImageStream? _imageStream;
1086 ImageInfo? _imageInfo;
1087 ImageChunkEvent? _loadingProgress;
1088 bool _isListeningToStream = false;
1089 late bool _invertColors;
1090 int? _frameNumber;
1091 bool _wasSynchronouslyLoaded = false;
1092 late DisposableBuildContext<State<Image>> _scrollAwareContext;
1093 Object? _lastException;
1094 StackTrace? _lastStack;
1095 ImageStreamCompleterHandle? _completerHandle;
1096
1097 @override
1098 void initState() {
1099 super.initState();
1100 WidgetsBinding.instance.addObserver(this);
1101 _scrollAwareContext = DisposableBuildContext<State<Image>>(this);
1102 }
1103
1104 @override
1105 void dispose() {
1106 assert(_imageStream != null);
1107 WidgetsBinding.instance.removeObserver(this);
1108 _stopListeningToStream();
1109 _completerHandle?.dispose();
1110 _scrollAwareContext.dispose();
1111 _replaceImage(info: null);
1112 super.dispose();
1113 }
1114
1115 @override
1116 void didChangeDependencies() {
1117 _updateInvertColors();
1118 _resolveImage();
1119
1120 if (TickerMode.of(context)) {
1121 _listenToStream();
1122 } else {
1123 _stopListeningToStream(keepStreamAlive: true);
1124 }
1125
1126 super.didChangeDependencies();
1127 }
1128
1129 @override
1130 void didUpdateWidget(Image oldWidget) {
1131 super.didUpdateWidget(oldWidget);
1132 if (_isListeningToStream &&
1133 (widget.loadingBuilder == null) != (oldWidget.loadingBuilder == null)) {
1134 final ImageStreamListener oldListener = _getListener();
1135 _imageStream!.addListener(_getListener(recreateListener: true));
1136 _imageStream!.removeListener(oldListener);
1137 }
1138 if (widget.image != oldWidget.image) {
1139 _resolveImage();
1140 }
1141 }
1142
1143 @override
1144 void didChangeAccessibilityFeatures() {
1145 super.didChangeAccessibilityFeatures();
1146 setState(() {
1147 _updateInvertColors();
1148 });
1149 }
1150
1151 @override
1152 void reassemble() {
1153 _resolveImage(); // in case the image cache was flushed
1154 super.reassemble();
1155 }
1156
1157 void _updateInvertColors() {
1158 _invertColors =
1159 MediaQuery.maybeInvertColorsOf(context) ??
1160 SemanticsBinding.instance.accessibilityFeatures.invertColors;
1161 }
1162
1163 void _resolveImage() {
1164 final ScrollAwareImageProvider provider = ScrollAwareImageProvider<Object>(
1165 context: _scrollAwareContext,
1166 imageProvider: widget.image,
1167 );
1168 final ImageStream newStream = provider.resolve(
1169 createLocalImageConfiguration(
1170 context,
1171 size:
1172 widget.width != null && widget.height != null
1173 ? Size(widget.width!, widget.height!)
1174 : null,
1175 ),
1176 );
1177 _updateSourceStream(newStream);
1178 }
1179
1180 ImageStreamListener? _imageStreamListener;
1181 ImageStreamListener _getListener({bool recreateListener = false}) {
1182 if (_imageStreamListener == null || recreateListener) {
1183 _lastException = null;
1184 _lastStack = null;
1185 _imageStreamListener = ImageStreamListener(
1186 _handleImageFrame,
1187 onChunk: widget.loadingBuilder == null ? null : _handleImageChunk,
1188 onError:
1189 widget.errorBuilder != null || kDebugMode
1190 ? (Object error, StackTrace? stackTrace) {
1191 setState(() {
1192 _lastException = error;
1193 _lastStack = stackTrace;
1194 });
1195 assert(() {
1196 if (widget.errorBuilder == null) {
1197 // ignore: only_throw_errors, since we're just proxying the error.
1198 throw error; // Ensures the error message is printed to the console.
1199 }
1200 return true;
1201 }());
1202 }
1203 : null,
1204 );
1205 }
1206 return _imageStreamListener!;
1207 }
1208
1209 void _handleImageFrame(ImageInfo imageInfo, bool synchronousCall) {
1210 setState(() {
1211 _replaceImage(info: imageInfo);
1212 _loadingProgress = null;
1213 _lastException = null;
1214 _lastStack = null;
1215 _frameNumber = _frameNumber == null ? 0 : _frameNumber! + 1;
1216 _wasSynchronouslyLoaded = _wasSynchronouslyLoaded | synchronousCall;
1217 });
1218 }
1219
1220 void _handleImageChunk(ImageChunkEvent event) {
1221 assert(widget.loadingBuilder != null);
1222 setState(() {
1223 _loadingProgress = event;
1224 _lastException = null;
1225 _lastStack = null;
1226 });
1227 }
1228
1229 void _replaceImage({required ImageInfo? info}) {
1230 final ImageInfo? oldImageInfo = _imageInfo;
1231 SchedulerBinding.instance.addPostFrameCallback(
1232 (_) => oldImageInfo?.dispose(),
1233 debugLabel: 'Image.disposeOldInfo',
1234 );
1235 _imageInfo = info;
1236 }
1237
1238 // Updates _imageStream to newStream, and moves the stream listener
1239 // registration from the old stream to the new stream (if a listener was
1240 // registered).
1241 void _updateSourceStream(ImageStream newStream) {
1242 if (_imageStream?.key == newStream.key) {
1243 return;
1244 }
1245
1246 if (_isListeningToStream) {
1247 _imageStream!.removeListener(_getListener());
1248 }
1249
1250 if (!widget.gaplessPlayback) {
1251 setState(() {
1252 _replaceImage(info: null);
1253 });
1254 }
1255
1256 setState(() {
1257 _loadingProgress = null;
1258 _frameNumber = null;
1259 _wasSynchronouslyLoaded = false;
1260 });
1261
1262 _imageStream = newStream;
1263 if (_isListeningToStream) {
1264 _imageStream!.addListener(_getListener());
1265 }
1266 }
1267
1268 void _listenToStream() {
1269 if (_isListeningToStream) {
1270 return;
1271 }
1272
1273 _imageStream!.addListener(_getListener());
1274 _completerHandle?.dispose();
1275 _completerHandle = null;
1276
1277 _isListeningToStream = true;
1278 }
1279
1280 /// Stops listening to the image stream, if this state object has attached a
1281 /// listener.
1282 ///
1283 /// If the listener from this state is the last listener on the stream, the
1284 /// stream will be disposed. To keep the stream alive, set `keepStreamAlive`
1285 /// to true, which create [ImageStreamCompleterHandle] to keep the completer
1286 /// alive and is compatible with the [TickerMode] being off.
1287 void _stopListeningToStream({bool keepStreamAlive = false}) {
1288 if (!_isListeningToStream) {
1289 return;
1290 }
1291
1292 if (keepStreamAlive && _completerHandle == null && _imageStream?.completer != null) {
1293 _completerHandle = _imageStream!.completer!.keepAlive();
1294 }
1295
1296 _imageStream!.removeListener(_getListener());
1297 _isListeningToStream = false;
1298 }
1299
1300 Widget _debugBuildErrorWidget(BuildContext context, Object error) {
1301 return Stack(
1302 alignment: Alignment.center,
1303 children: <Widget>[
1304 const Positioned.fill(child: Placeholder(color: Color(0xCF8D021F))),
1305 Padding(
1306 padding: const EdgeInsets.all(4.0),
1307 child: FittedBox(
1308 child: Text(
1309 '$error',
1310 textAlign: TextAlign.center,
1311 textDirection: TextDirection.ltr,
1312 style: const TextStyle(shadows: <Shadow>[Shadow(blurRadius: 1.0)]),
1313 ),
1314 ),
1315 ),
1316 ],
1317 );
1318 }
1319
1320 @override
1321 Widget build(BuildContext context) {
1322 if (_lastException != null) {
1323 if (widget.errorBuilder != null) {
1324 return widget.errorBuilder!(context, _lastException!, _lastStack);
1325 }
1326 if (kDebugMode) {
1327 return _debugBuildErrorWidget(context, _lastException!);
1328 }
1329 }
1330
1331 late Widget result;
1332 if (_imageInfo case final WebImageInfo webImage) {
1333 // TODO(harryterkelsen): Support the remaining properties that are
1334 // supported by `RawImage` but not `RawWebImage`. See the following issue
1335 // above for a discussion of the missing properties and suggestions for
1336 // how they can be implemented, https://github.com/flutter/flutter/issues/159565.
1337 result = RawWebImage(
1338 image: webImage,
1339 debugImageLabel: _imageInfo?.debugLabel,
1340 width: widget.width,
1341 height: widget.height,
1342 fit: widget.fit,
1343 alignment: widget.alignment,
1344 matchTextDirection: widget.matchTextDirection,
1345 );
1346 } else {
1347 result = RawImage(
1348 // Do not clone the image, because RawImage is a stateless wrapper.
1349 // The image will be disposed by this state object when it is not needed
1350 // anymore, such as when it is unmounted or when the image stream pushes
1351 // a new image.
1352 image: _imageInfo?.image,
1353 debugImageLabel: _imageInfo?.debugLabel,
1354 width: widget.width,
1355 height: widget.height,
1356 scale: _imageInfo?.scale ?? 1.0,
1357 color: widget.color,
1358 opacity: widget.opacity,
1359 colorBlendMode: widget.colorBlendMode,
1360 fit: widget.fit,
1361 alignment: widget.alignment,
1362 repeat: widget.repeat,
1363 centerSlice: widget.centerSlice,
1364 matchTextDirection: widget.matchTextDirection,
1365 invertColors: _invertColors,
1366 isAntiAlias: widget.isAntiAlias,
1367 filterQuality: widget.filterQuality,
1368 );
1369 }
1370
1371 if (!widget.excludeFromSemantics) {
1372 result = Semantics(
1373 container: widget.semanticLabel != null,
1374 image: true,
1375 label: widget.semanticLabel ?? '',
1376 child: result,
1377 );
1378 }
1379
1380 if (widget.frameBuilder != null) {
1381 result = widget.frameBuilder!(context, result, _frameNumber, _wasSynchronouslyLoaded);
1382 }
1383
1384 if (widget.loadingBuilder != null) {
1385 result = widget.loadingBuilder!(context, result, _loadingProgress);
1386 }
1387
1388 return result;
1389 }
1390
1391 @override
1392 void debugFillProperties(DiagnosticPropertiesBuilder description) {
1393 super.debugFillProperties(description);
1394 description.add(DiagnosticsProperty<ImageStream>('stream', _imageStream));
1395 description.add(DiagnosticsProperty<ImageInfo>('pixels', _imageInfo));
1396 description.add(DiagnosticsProperty<ImageChunkEvent>('loadingProgress', _loadingProgress));
1397 description.add(DiagnosticsProperty<int>('frameNumber', _frameNumber));
1398 description.add(DiagnosticsProperty<bool>('wasSynchronouslyLoaded', _wasSynchronouslyLoaded));
1399 }
1400}
1401

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com