image.dart 26.1 KB
Newer Older
1 2 3 4
// Copyright 2015 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.

5
import 'dart:async';
6
import 'dart:io' show File;
7
import 'dart:typed_data';
8

9
import 'package:flutter/foundation.dart';
10
import 'package:flutter/painting.dart';
11
import 'package:flutter/services.dart';
12
import 'package:flutter/semantics.dart';
13 14 15

import 'basic.dart';
import 'framework.dart';
Ian Hickson's avatar
Ian Hickson committed
16
import 'localizations.dart';
17
import 'media_query.dart';
18
import 'ticker_provider.dart';
19

20
export 'package:flutter/painting.dart' show
21 22
  AssetImage,
  ExactAssetImage,
23
  FileImage,
24
  FilterQuality,
25 26 27 28
  ImageConfiguration,
  ImageInfo,
  ImageStream,
  ImageProvider,
29
  MemoryImage,
30
  NetworkImage;
31

32 33 34 35 36
/// Creates an [ImageConfiguration] based on the given [BuildContext] (and
/// optionally size).
///
/// This is the object that must be passed to [BoxPainter.paint] and to
/// [ImageProvider.resolve].
37 38 39
///
/// If this is not called from a build method, then it should be reinvoked
/// whenever the dependencies change, e.g. by calling it from
40
/// [State.didChangeDependencies], so that any changes in the environment are
41 42 43 44 45
/// picked up (e.g. if the device pixel ratio changes).
///
/// See also:
///
///  * [ImageProvider], which has an example showing how this might be used.
46
ImageConfiguration createLocalImageConfiguration(BuildContext context, { Size size }) {
47
  return ImageConfiguration(
48
    bundle: DefaultAssetBundle.of(context),
49
    devicePixelRatio: MediaQuery.of(context, nullOk: true)?.devicePixelRatio ?? 1.0,
Ian Hickson's avatar
Ian Hickson committed
50 51
    locale: Localizations.localeOf(context, nullOk: true),
    textDirection: Directionality.of(context),
52
    size: size,
53
    platform: defaultTargetPlatform,
54 55 56
  );
}

57 58 59
/// Prefetches an image into the image cache.
///
/// Returns a [Future] that will complete when the first image yielded by the
60
/// [ImageProvider] is available or failed to load.
61 62
///
/// If the image is later used by an [Image] or [BoxDecoration] or [FadeInImage],
63 64
/// it will probably be loaded faster. The consumer of the image does not need
/// to use the same [ImageProvider] instance. The [ImageCache] will find the image
65 66 67 68 69
/// as long as both images share the same key.
///
/// The [BuildContext] and [Size] are used to select an image configuration
/// (see [createLocalImageConfiguration]).
///
70 71
/// The `onError` argument can be used to manually handle errors while
/// pre-caching.
72
///
73 74
/// See also:
///
75
///  * [ImageCache], which holds images that may be reused.
76
Future<void> precacheImage(
77 78 79 80 81
  ImageProvider provider,
  BuildContext context, {
  Size size,
  ImageErrorListener onError,
}) {
82
  final ImageConfiguration config = createLocalImageConfiguration(context, size: size);
83
  final Completer<void> completer = Completer<void>();
84 85 86
  final ImageStream stream = provider.resolve(config);
  void listener(ImageInfo image, bool sync) {
    completer.complete();
87
    stream.removeListener(listener);
88
  }
89 90
  void errorListener(dynamic exception, StackTrace stackTrace) {
    completer.complete();
91
    stream.removeListener(listener);
92 93 94
    if (onError != null) {
      onError(exception, stackTrace);
    } else {
95
      FlutterError.reportError(FlutterErrorDetails(
96 97 98 99 100 101 102 103 104
        context: 'image failed to precache',
        library: 'image resource service',
        exception: exception,
        stack: stackTrace,
        silent: true,
      ));
    }
  }
  stream.addListener(listener, onError: errorListener);
105 106 107
  return completer.future;
}

108 109 110 111 112
/// A widget that displays an image.
///
/// Several constructors are provided for the various ways that an image can be
/// specified:
///
113 114 115 116 117 118
///  * [new Image], for obtaining an image from an [ImageProvider].
///  * [new Image.asset], for obtaining an image from an [AssetBundle]
///    using a key.
///  * [new Image.network], for obtaining an image from a URL.
///  * [new Image.file], for obtaining an image from a [File].
///  * [new Image.memory], for obtaining an image from a [Uint8List].
119
///
120 121
/// The following image formats are supported: {@macro flutter.dart:ui.imageFormats}
///
122 123 124 125 126 127
/// To automatically perform pixel-density-aware asset resolution, specify the
/// image using an [AssetImage] and make sure that a [MaterialApp], [WidgetsApp],
/// or [MediaQuery] widget exists above the [Image] widget in the widget tree.
///
/// The image is painted using [paintImage], which describes the meanings of the
/// various fields on this class in more detail.
128 129 130
///
/// See also:
///
Ian Hickson's avatar
Ian Hickson committed
131 132 133 134
///  * [Icon], which shows an image from a font.
///  * [new Ink.image], which is the preferred way to show an image in a
///    material application (especially if the image is in a [Material] and will
///    have an [InkWell] on top of it).
135 136
///  * [Image](https://api.flutter.dev/flutter/dart-ui/Image-class.html), the class in the [dart:ui] library.
///
137 138 139 140
class Image extends StatefulWidget {
  /// Creates a widget that displays an image.
  ///
  /// To show an image from the network or from an asset bundle, consider using
141
  /// [new Image.network] and [new Image.asset] respectively.
142
  ///
Ian Hickson's avatar
Ian Hickson committed
143 144
  /// The [image], [alignment], [repeat], and [matchTextDirection] arguments
  /// must not be null.
Ian Hickson's avatar
Ian Hickson committed
145 146 147 148 149
  ///
  /// Either the [width] and [height] arguments should be specified, or the
  /// widget should be placed in a context that sets tight layout constraints.
  /// Otherwise, the image dimensions will change as the image is loaded, which
  /// will result in ugly layout changes.
150
  ///
151 152 153 154 155
  /// Use [filterQuality] to change the quality when scaling an image.
  /// Use the [FilterQuality.low] quality setting to scale the image,
  /// which corresponds to bilinear interpolation, rather than the default
  /// [FilterQuality.none] which corresponds to nearest-neighbor.
  ///
156
  /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
157
  const Image({
158 159
    Key key,
    @required this.image,
160 161
    this.semanticLabel,
    this.excludeFromSemantics = false,
162 163 164
    this.width,
    this.height,
    this.color,
165
    this.colorBlendMode,
166
    this.fit,
167 168
    this.alignment = Alignment.center,
    this.repeat = ImageRepeat.noRepeat,
169
    this.centerSlice,
170 171
    this.matchTextDirection = false,
    this.gaplessPlayback = false,
172
    this.filterQuality = FilterQuality.low,
173
  }) : assert(image != null),
Ian Hickson's avatar
Ian Hickson committed
174 175
       assert(alignment != null),
       assert(repeat != null),
176
       assert(filterQuality != null),
Ian Hickson's avatar
Ian Hickson committed
177
       assert(matchTextDirection != null),
178
       super(key: key);
179 180 181 182

  /// Creates a widget that displays an [ImageStream] obtained from the network.
  ///
  /// The [src], [scale], and [repeat] arguments must not be null.
Ian Hickson's avatar
Ian Hickson committed
183 184 185 186 187
  ///
  /// Either the [width] and [height] arguments should be specified, or the
  /// widget should be placed in a context that sets tight layout constraints.
  /// Otherwise, the image dimensions will change as the image is loaded, which
  /// will result in ugly layout changes.
188 189
  ///
  /// All network images are cached regardless of HTTP headers.
Ian Hickson's avatar
Ian Hickson committed
190 191 192
  ///
  /// An optional [headers] argument can be used to send custom HTTP headers
  /// with the image request.
193
  ///
194 195 196 197 198
  /// Use [filterQuality] to change the quality when scaling an image.
  /// Use the [FilterQuality.low] quality setting to scale the image,
  /// which corresponds to bilinear interpolation, rather than the default
  /// [FilterQuality.none] which corresponds to nearest-neighbor.
  ///
199
  /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
200 201
  Image.network(
    String src, {
202
    Key key,
203
    double scale = 1.0,
204 205
    this.semanticLabel,
    this.excludeFromSemantics = false,
206 207 208
    this.width,
    this.height,
    this.color,
209
    this.colorBlendMode,
210
    this.fit,
211 212
    this.alignment = Alignment.center,
    this.repeat = ImageRepeat.noRepeat,
213
    this.centerSlice,
214 215
    this.matchTextDirection = false,
    this.gaplessPlayback = false,
216
    this.filterQuality = FilterQuality.low,
217
    Map<String, String> headers,
218
  }) : image = NetworkImage(src, scale: scale, headers: headers),
Ian Hickson's avatar
Ian Hickson committed
219 220 221
       assert(alignment != null),
       assert(repeat != null),
       assert(matchTextDirection != null),
222 223
       super(key: key);

Ian Hickson's avatar
Ian Hickson committed
224 225 226 227
  /// Creates a widget that displays an [ImageStream] obtained from a [File].
  ///
  /// The [file], [scale], and [repeat] arguments must not be null.
  ///
Ian Hickson's avatar
Ian Hickson committed
228 229 230 231 232
  /// Either the [width] and [height] arguments should be specified, or the
  /// widget should be placed in a context that sets tight layout constraints.
  /// Otherwise, the image dimensions will change as the image is loaded, which
  /// will result in ugly layout changes.
  ///
Ian Hickson's avatar
Ian Hickson committed
233 234
  /// On Android, this may require the
  /// `android.permission.READ_EXTERNAL_STORAGE` permission.
235
  ///
236 237 238 239 240
  /// Use [filterQuality] to change the quality when scaling an image.
  /// Use the [FilterQuality.low] quality setting to scale the image,
  /// which corresponds to bilinear interpolation, rather than the default
  /// [FilterQuality.none] which corresponds to nearest-neighbor.
  ///
241
  /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
242 243
  Image.file(
    File file, {
Ian Hickson's avatar
Ian Hickson committed
244
    Key key,
245
    double scale = 1.0,
246 247
    this.semanticLabel,
    this.excludeFromSemantics = false,
Ian Hickson's avatar
Ian Hickson committed
248 249 250
    this.width,
    this.height,
    this.color,
251
    this.colorBlendMode,
Ian Hickson's avatar
Ian Hickson committed
252
    this.fit,
253 254
    this.alignment = Alignment.center,
    this.repeat = ImageRepeat.noRepeat,
Ian Hickson's avatar
Ian Hickson committed
255
    this.centerSlice,
256 257
    this.matchTextDirection = false,
    this.gaplessPlayback = false,
258
    this.filterQuality = FilterQuality.low,
259
  }) : image = FileImage(file, scale: scale),
Ian Hickson's avatar
Ian Hickson committed
260 261
       assert(alignment != null),
       assert(repeat != null),
262
       assert(filterQuality != null),
Ian Hickson's avatar
Ian Hickson committed
263
       assert(matchTextDirection != null),
Ian Hickson's avatar
Ian Hickson committed
264 265
       super(key: key);

266 267 268
  /// Creates a widget that displays an [ImageStream] obtained from an asset
  /// bundle. The key for the image is given by the `name` argument.
  ///
269 270 271 272
  /// The `package` argument must be non-null when displaying an image from a
  /// package and null otherwise. See the `Assets in packages` section for
  /// details.
  ///
273 274 275
  /// If the `bundle` argument is omitted or null, then the
  /// [DefaultAssetBundle] will be used.
  ///
276 277
  /// By default, the pixel-density-aware asset resolution will be attempted. In
  /// addition:
278
  ///
279
  /// * If the `scale` argument is provided and is not null, then the exact
280 281
  /// asset specified will be used. To display an image variant with a specific
  /// density, the exact path must be provided (e.g. `images/2x/cat.png`).
282 283
  ///
  /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
284 285 286 287 288 289 290
  //
  // TODO(ianh): Implement the following (see ../services/image_resolution.dart):
  // ///
  // /// * If [width] and [height] are both specified, and [scale] is not, then
  // ///   size-aware asset resolution will be attempted also, with the given
  // ///   dimensions interpreted as logical pixels.
  // ///
Ian Hickson's avatar
Ian Hickson committed
291 292 293
  // /// * If the images have platform, locale, or directionality variants, the
  // ///   current platform, locale, and directionality are taken into account
  // ///   during asset resolution as well.
294 295
  ///
  /// The [name] and [repeat] arguments must not be null.
296
  ///
Ian Hickson's avatar
Ian Hickson committed
297 298 299 300 301
  /// Either the [width] and [height] arguments should be specified, or the
  /// widget should be placed in a context that sets tight layout constraints.
  /// Otherwise, the image dimensions will change as the image is loaded, which
  /// will result in ugly layout changes.
  ///
302 303 304 305 306
  /// Use [filterQuality] to change the quality when scaling an image.
  /// Use the [FilterQuality.low] quality setting to scale the image,
  /// which corresponds to bilinear interpolation, rather than the default
  /// [FilterQuality.none] which corresponds to nearest-neighbor.
  ///
307
  /// {@tool sample}
308 309 310 311 312 313 314 315 316 317
  ///
  /// Suppose that the project's `pubspec.yaml` file contains the following:
  ///
  /// ```yaml
  /// flutter:
  ///   assets:
  ///     - images/cat.png
  ///     - images/2x/cat.png
  ///     - images/3.5x/cat.png
  /// ```
318
  /// {@end-tool}
319 320 321 322 323
  ///
  /// On a screen with a device pixel ratio of 2.0, the following widget would
  /// render the `images/2x/cat.png` file:
  ///
  /// ```dart
324
  /// Image.asset('images/cat.png')
325 326 327 328 329 330
  /// ```
  ///
  /// This corresponds to the file that is in the project's `images/2x/`
  /// directory with the name `cat.png` (the paths are relative to the
  /// `pubspec.yaml` file).
  ///
331 332 333 334 335 336 337 338
  /// On a device with a 4.0 device pixel ratio, the `images/3.5x/cat.png` asset
  /// would be used. On a device with a 1.0 device pixel ratio, the
  /// `images/cat.png` resource would be used.
  ///
  /// The `images/cat.png` image can be omitted from disk (though it must still
  /// be present in the manifest). If it is omitted, then on a device with a 1.0
  /// device pixel ratio, the `images/2x/cat.png` image would be used instead.
  ///
339 340 341 342 343 344 345
  ///
  /// ## Assets in packages
  ///
  /// To create the widget with an asset from a package, the [package] argument
  /// must be provided. For instance, suppose a package called `my_icons` has
  /// `icons/heart.png` .
  ///
346
  /// {@tool sample}
347 348 349
  /// Then to display the image, use:
  ///
  /// ```dart
350
  /// Image.asset('icons/heart.png', package: 'my_icons')
351
  /// ```
352
  /// {@end-tool}
353 354 355 356
  ///
  /// Assets used by the package itself should also be displayed using the
  /// [package] argument as above.
  ///
357
  /// If the desired asset is specified in the `pubspec.yaml` of the package, it
358
  /// is bundled automatically with the app. In particular, assets used by the
359
  /// package itself must be specified in its `pubspec.yaml`.
360 361
  ///
  /// A package can also choose to have assets in its 'lib/' folder that are not
362
  /// specified in its `pubspec.yaml`. In this case for those images to be
363 364 365 366 367 368 369
  /// bundled, the app has to specify which ones to include. For instance a
  /// package named `fancy_backgrounds` could have:
  ///
  /// ```
  /// lib/backgrounds/background1.png
  /// lib/backgrounds/background2.png
  /// lib/backgrounds/background3.png
370
  /// ```
371
  ///
372
  /// To include, say the first image, the `pubspec.yaml` of the app should
373 374 375
  /// specify it in the assets section:
  ///
  /// ```yaml
376 377
  ///   assets:
  ///     - packages/fancy_backgrounds/backgrounds/background1.png
378 379
  /// ```
  ///
Ian Hickson's avatar
Ian Hickson committed
380
  /// The `lib/` is implied, so it should not be included in the asset path.
381 382
  ///
  ///
383 384 385 386 387 388
  /// See also:
  ///
  ///  * [AssetImage], which is used to implement the behavior when the scale is
  ///    omitted.
  ///  * [ExactAssetImage], which is used to implement the behavior when the
  ///    scale is present.
389 390
  ///  * <https://flutter.io/assets-and-images/>, an introduction to assets in
  ///    Flutter.
391 392
  Image.asset(
    String name, {
393 394
    Key key,
    AssetBundle bundle,
395 396
    this.semanticLabel,
    this.excludeFromSemantics = false,
397 398 399 400
    double scale,
    this.width,
    this.height,
    this.color,
401
    this.colorBlendMode,
402
    this.fit,
403 404
    this.alignment = Alignment.center,
    this.repeat = ImageRepeat.noRepeat,
405
    this.centerSlice,
406 407
    this.matchTextDirection = false,
    this.gaplessPlayback = false,
Ian Hickson's avatar
Ian Hickson committed
408
    String package,
409
    this.filterQuality = FilterQuality.low,
410
  }) : image = scale != null
411 412
         ? ExactAssetImage(name, bundle: bundle, scale: scale, package: package)
         : AssetImage(name, bundle: bundle, package: package),
Ian Hickson's avatar
Ian Hickson committed
413 414 415 416
       assert(alignment != null),
       assert(repeat != null),
       assert(matchTextDirection != null),
       super(key: key);
417

418 419 420
  /// Creates a widget that displays an [ImageStream] obtained from a [Uint8List].
  ///
  /// The [bytes], [scale], and [repeat] arguments must not be null.
Ian Hickson's avatar
Ian Hickson committed
421 422 423 424 425
  ///
  /// Either the [width] and [height] arguments should be specified, or the
  /// widget should be placed in a context that sets tight layout constraints.
  /// Otherwise, the image dimensions will change as the image is loaded, which
  /// will result in ugly layout changes.
426
  ///
427 428 429 430 431
  /// Use [filterQuality] to change the quality when scaling an image.
  /// Use the [FilterQuality.low] quality setting to scale the image,
  /// which corresponds to bilinear interpolation, rather than the default
  /// [FilterQuality.none] which corresponds to nearest-neighbor.
  ///
432
  /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
433 434
  Image.memory(
    Uint8List bytes, {
435
    Key key,
436
    double scale = 1.0,
437 438
    this.semanticLabel,
    this.excludeFromSemantics = false,
439 440 441
    this.width,
    this.height,
    this.color,
442
    this.colorBlendMode,
443
    this.fit,
444 445
    this.alignment = Alignment.center,
    this.repeat = ImageRepeat.noRepeat,
446
    this.centerSlice,
447 448
    this.matchTextDirection = false,
    this.gaplessPlayback = false,
449
    this.filterQuality = FilterQuality.low,
450
  }) : image = MemoryImage(bytes, scale: scale),
Ian Hickson's avatar
Ian Hickson committed
451 452 453
       assert(alignment != null),
       assert(repeat != null),
       assert(matchTextDirection != null),
454 455
       super(key: key);

456 457 458 459 460 461 462
  /// The image to display.
  final ImageProvider image;

  /// If non-null, require the image to have this width.
  ///
  /// If null, the image will pick a size that best preserves its intrinsic
  /// aspect ratio.
Ian Hickson's avatar
Ian Hickson committed
463 464 465 466 467 468
  ///
  /// It is strongly recommended that either both the [width] and the [height]
  /// be specified, or that the widget be placed in a context that sets tight
  /// layout constraints, so that the image does not change size as it loads.
  /// Consider using [fit] to adapt the image's rendering to fit the given width
  /// and height if the exact image dimensions are not known in advance.
469 470 471 472 473 474
  final double width;

  /// If non-null, require the image to have this height.
  ///
  /// If null, the image will pick a size that best preserves its intrinsic
  /// aspect ratio.
Ian Hickson's avatar
Ian Hickson committed
475 476 477 478 479 480
  ///
  /// It is strongly recommended that either both the [width] and the [height]
  /// be specified, or that the widget be placed in a context that sets tight
  /// layout constraints, so that the image does not change size as it loads.
  /// Consider using [fit] to adapt the image's rendering to fit the given width
  /// and height if the exact image dimensions are not known in advance.
481 482
  final double height;

483
  /// If non-null, this color is blended with each image pixel using [colorBlendMode].
484 485
  final Color color;

486 487 488 489
  /// Used to set the [FilterQuality] of the image.
  ///
  /// Use the [FilterQuality.low] quality setting to scale the image with
  /// bilinear interpolation, or the [FilterQuality.none] which corresponds
490 491 492
  /// to nearest-neighbor.
  final FilterQuality filterQuality;

493 494 495 496 497 498 499 500 501 502
  /// Used to combine [color] with this image.
  ///
  /// The default is [BlendMode.srcIn]. In terms of the blend mode, [color] is
  /// the source and this image is the destination.
  ///
  /// See also:
  ///
  ///  * [BlendMode], which includes an illustration of the effect of each blend mode.
  final BlendMode colorBlendMode;

Adam Barth's avatar
Adam Barth committed
503
  /// How to inscribe the image into the space allocated during layout.
504 505 506
  ///
  /// The default varies based on the other fields. See the discussion at
  /// [paintImage].
507
  final BoxFit fit;
508 509 510

  /// How to align the image within its bounds.
  ///
Ian Hickson's avatar
Ian Hickson committed
511
  /// The alignment aligns the given position in the image to the given position
512 513
  /// in the layout bounds. For example, an [Alignment] alignment of (-1.0,
  /// -1.0) aligns the image to the top-left corner of its layout bounds, while an
514
  /// [Alignment] alignment of (1.0, 1.0) aligns the bottom right of the
Ian Hickson's avatar
Ian Hickson committed
515
  /// image with the bottom right corner of its layout bounds. Similarly, an
516
  /// alignment of (0.0, 1.0) aligns the bottom middle of the image with the
Ian Hickson's avatar
Ian Hickson committed
517 518 519 520 521 522
  /// middle of the bottom edge of its layout bounds.
  ///
  /// To display a subpart of an image, consider using a [CustomPainter] and
  /// [Canvas.drawImageRect].
  ///
  /// If the [alignment] is [TextDirection]-dependent (i.e. if it is a
523
  /// [AlignmentDirectional]), then an ambient [Directionality] widget
Ian Hickson's avatar
Ian Hickson committed
524 525
  /// must be in scope.
  ///
526
  /// Defaults to [Alignment.center].
527 528 529 530 531 532 533
  ///
  /// See also:
  ///
  ///  * [Alignment], a class with convenient constants typically used to
  ///    specify an [AlignmentGeometry].
  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
  ///    relative to text direction.
534
  final AlignmentGeometry alignment;
535 536 537 538 539 540 541 542 543 544 545 546 547

  /// How to paint any portions of the layout bounds not covered by the image.
  final ImageRepeat repeat;

  /// The center slice for a nine-patch image.
  ///
  /// The region of the image inside the center slice will be stretched both
  /// horizontally and vertically to fit the image into its destination. The
  /// region of the image above and below the center slice will be stretched
  /// only horizontally and the region of the image to the left and right of
  /// the center slice will be stretched only vertically.
  final Rect centerSlice;

Ian Hickson's avatar
Ian Hickson committed
548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564
  /// Whether to paint the image in the direction of the [TextDirection].
  ///
  /// If this is true, then in [TextDirection.ltr] contexts, the image will be
  /// drawn with its origin in the top left (the "normal" painting direction for
  /// images); and in [TextDirection.rtl] contexts, the image will be drawn with
  /// a scaling factor of -1 in the horizontal direction so that the origin is
  /// in the top right.
  ///
  /// This is occasionally used with images in right-to-left environments, for
  /// images that were designed for left-to-right locales. Be careful, when
  /// using this, to not flip images with integral shadows, text, or other
  /// effects that will look incorrect when flipped.
  ///
  /// If this is true, there must be an ambient [Directionality] widget in
  /// scope.
  final bool matchTextDirection;

565 566 567 568
  /// Whether to continue showing the old image (true), or briefly show nothing
  /// (false), when the image provider changes.
  final bool gaplessPlayback;

569 570
  /// A Semantic description of the image.
  ///
571
  /// Used to provide a description of the image to TalkBack on Android, and
572 573 574 575 576 577 578 579 580
  /// VoiceOver on iOS.
  final String semanticLabel;

  /// Whether to exclude this image from semantics.
  ///
  /// Useful for images which do not contribute meaningful information to an
  /// application.
  final bool excludeFromSemantics;

581
  @override
582
  _ImageState createState() => _ImageState();
583 584

  @override
585 586
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
587 588 589 590 591 592 593 594 595 596 597 598
    properties.add(DiagnosticsProperty<ImageProvider>('image', image));
    properties.add(DoubleProperty('width', width, defaultValue: null));
    properties.add(DoubleProperty('height', height, defaultValue: null));
    properties.add(DiagnosticsProperty<Color>('color', color, defaultValue: null));
    properties.add(EnumProperty<BlendMode>('colorBlendMode', colorBlendMode, defaultValue: null));
    properties.add(EnumProperty<BoxFit>('fit', fit, defaultValue: null));
    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
    properties.add(EnumProperty<ImageRepeat>('repeat', repeat, defaultValue: ImageRepeat.noRepeat));
    properties.add(DiagnosticsProperty<Rect>('centerSlice', centerSlice, defaultValue: null));
    properties.add(FlagProperty('matchTextDirection', value: matchTextDirection, ifTrue: 'match text direction'));
    properties.add(StringProperty('semanticLabel', semanticLabel, defaultValue: null));
    properties.add(DiagnosticsProperty<bool>('this.excludeFromSemantics', excludeFromSemantics));
599
    properties.add(EnumProperty<FilterQuality>('filterQuality', filterQuality));
600 601 602 603 604 605
  }
}

class _ImageState extends State<Image> {
  ImageStream _imageStream;
  ImageInfo _imageInfo;
606
  bool _isListeningToStream = false;
607
  bool _invertColors;
608 609

  @override
610
  void didChangeDependencies() {
611 612
    _invertColors = MediaQuery.of(context, nullOk: true)?.invertColors
      ?? SemanticsBinding.instance.accessibilityFeatures.invertColors;
613
    _resolveImage();
614 615 616 617 618 619

    if (TickerMode.of(context))
      _listenToStream();
    else
      _stopListeningToStream();

620
    super.didChangeDependencies();
621 622
  }

623
  @override
624
  void didUpdateWidget(Image oldWidget) {
625
    super.didUpdateWidget(oldWidget);
626
    if (widget.image != oldWidget.image)
627 628 629
      _resolveImage();
  }

630 631
  @override
  void reassemble() {
632
    _resolveImage(); // in case the image cache was flushed
633 634 635
    super.reassemble();
  }

636
  void _resolveImage() {
637 638 639
    final ImageStream newStream =
      widget.image.resolve(createLocalImageConfiguration(
          context,
640
          size: widget.width != null && widget.height != null ? Size(widget.width, widget.height) : null,
641 642 643
      ));
    assert(newStream != null);
    _updateSourceStream(newStream);
644 645
  }

646
  void _handleImageChanged(ImageInfo imageInfo, bool synchronousCall) {
647 648 649 650 651
    setState(() {
      _imageInfo = imageInfo;
    });
  }

652 653 654 655 656 657 658 659 660 661
  // Update _imageStream to newStream, and moves the stream listener
  // registration from the old stream to the new stream (if a listener was
  // registered).
  void _updateSourceStream(ImageStream newStream) {
    if (_imageStream?.key == newStream?.key)
      return;

    if (_isListeningToStream)
      _imageStream.removeListener(_handleImageChanged);

662 663 664
    if (!widget.gaplessPlayback)
      setState(() { _imageInfo = null; });

665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683
    _imageStream = newStream;
    if (_isListeningToStream)
      _imageStream.addListener(_handleImageChanged);
  }

  void _listenToStream() {
    if (_isListeningToStream)
      return;
    _imageStream.addListener(_handleImageChanged);
    _isListeningToStream = true;
  }

  void _stopListeningToStream() {
    if (!_isListeningToStream)
      return;
    _imageStream.removeListener(_handleImageChanged);
    _isListeningToStream = false;
  }

684
  @override
685 686
  void dispose() {
    assert(_imageStream != null);
687
    _stopListeningToStream();
688 689
    super.dispose();
  }
690

691 692
  @override
  Widget build(BuildContext context) {
693
    final RawImage image = RawImage(
694
      image: _imageInfo?.image,
695 696
      width: widget.width,
      height: widget.height,
697
      scale: _imageInfo?.scale ?? 1.0,
698
      color: widget.color,
699
      colorBlendMode: widget.colorBlendMode,
700 701 702
      fit: widget.fit,
      alignment: widget.alignment,
      repeat: widget.repeat,
Ian Hickson's avatar
Ian Hickson committed
703 704
      centerSlice: widget.centerSlice,
      matchTextDirection: widget.matchTextDirection,
705
      invertColors: _invertColors,
706
      filterQuality: widget.filterQuality,
707
    );
708 709
    if (widget.excludeFromSemantics)
      return image;
710
    return Semantics(
711 712 713 714 715
      container: widget.semanticLabel != null,
      image: true,
      label: widget.semanticLabel == null ? '' : widget.semanticLabel,
      child: image,
    );
716
  }
717 718

  @override
719
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
720
    super.debugFillProperties(description);
721 722
    description.add(DiagnosticsProperty<ImageStream>('stream', _imageStream));
    description.add(DiagnosticsProperty<ImageInfo>('pixels', _imageInfo));
723
  }
724
}