image.dart 9.15 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.

Ian Hickson's avatar
Ian Hickson committed
5
import 'dart:io' show File, Platform;
6
import 'dart:typed_data';
7

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

import 'basic.dart';
import 'framework.dart';
import 'media_query.dart';

15
export 'package:flutter/services.dart' show
16 17
  AssetImage,
  ExactAssetImage,
18
  MemoryImage,
19 20
  NetworkImage,
  FileImage;
21

22 23 24 25 26 27 28 29 30
/// 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].
ImageConfiguration createLocalImageConfiguration(BuildContext context, { Size size }) {
  return new ImageConfiguration(
    bundle: DefaultAssetBundle.of(context),
    devicePixelRatio: MediaQuery.of(context).devicePixelRatio,
31
    // TODO(ianh): provide the locale
32 33 34 35 36 37 38 39 40 41
    size: size,
    platform: Platform.operatingSystem
  );
}

/// A widget that displays an image.
///
/// Several constructors are provided for the various ways that an image can be
/// specified:
///
42 43 44 45 46 47
///  * [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].
48 49 50 51 52 53 54
///
/// 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.
55 56 57 58
///
/// See also:
///
///  * [Icon]
59 60 61 62
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
63
  /// [new Image.network] and [new Image.asset] respectively.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
  ///
  /// The [image] and [repeat] arguments must not be null.
  Image({
    Key key,
    @required this.image,
    this.width,
    this.height,
    this.color,
    this.fit,
    this.alignment,
    this.repeat: ImageRepeat.noRepeat,
    this.centerSlice,
    this.gaplessPlayback: false
  }) : super(key: key) {
    assert(image != null);
  }

  /// Creates a widget that displays an [ImageStream] obtained from the network.
  ///
  /// The [src], [scale], and [repeat] arguments must not be null.
84
  Image.network(String src, {
85 86 87 88 89 90 91 92 93 94 95 96 97
    Key key,
    double scale: 1.0,
    this.width,
    this.height,
    this.color,
    this.fit,
    this.alignment,
    this.repeat: ImageRepeat.noRepeat,
    this.centerSlice,
    this.gaplessPlayback: false
  }) : image = new NetworkImage(src, scale: scale),
       super(key: key);

Ian Hickson's avatar
Ian Hickson committed
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
  /// Creates a widget that displays an [ImageStream] obtained from a [File].
  ///
  /// The [file], [scale], and [repeat] arguments must not be null.
  ///
  /// On Android, this may require the
  /// `android.permission.READ_EXTERNAL_STORAGE` permission.
  Image.file(File file, {
    Key key,
    double scale: 1.0,
    this.width,
    this.height,
    this.color,
    this.fit,
    this.alignment,
    this.repeat: ImageRepeat.noRepeat,
    this.centerSlice,
    this.gaplessPlayback: false
  }) : image = new FileImage(file, scale: scale),
       super(key: key);

118 119 120 121 122 123 124 125 126 127 128 129 130
  /// Creates a widget that displays an [ImageStream] obtained from an asset
  /// bundle. The key for the image is given by the `name` argument.
  ///
  /// If the `bundle` argument is omitted or null, then the
  /// [DefaultAssetBundle] will be used.
  ///
  /// If the `scale` argument is omitted or null, then pixel-density-aware asset
  /// resolution will be attempted.
  ///
  /// If [width] and [height] are both specified, and [scale] is not, then
  /// size-aware asset resolution will be attempted also.
  ///
  /// The [name] and [repeat] arguments must not be null.
131
  Image.asset(String name, {
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
    Key key,
    AssetBundle bundle,
    double scale,
    this.width,
    this.height,
    this.color,
    this.fit,
    this.alignment,
    this.repeat: ImageRepeat.noRepeat,
    this.centerSlice,
    this.gaplessPlayback: false
  }) : image = scale != null ? new ExactAssetImage(name, bundle: bundle, scale: scale)
                             : new AssetImage(name, bundle: bundle),
       super(key: key);

147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
  /// Creates a widget that displays an [ImageStream] obtained from a [Uint8List].
  ///
  /// The [bytes], [scale], and [repeat] arguments must not be null.
  Image.memory(Uint8List bytes, {
    Key key,
    double scale: 1.0,
    this.width,
    this.height,
    this.color,
    this.fit,
    this.alignment,
    this.repeat: ImageRepeat.noRepeat,
    this.centerSlice,
    this.gaplessPlayback: false
  }) : image = new MemoryImage(bytes, scale: scale),
       super(key: key);

164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
  /// 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.
  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.
  final double height;

  /// If non-null, apply this color filter to the image before painting.
  final Color color;

Adam Barth's avatar
Adam Barth committed
182
  /// How to inscribe the image into the space allocated during layout.
183 184 185
  ///
  /// The default varies based on the other fields. See the discussion at
  /// [paintImage].
186
  final BoxFit fit;
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239

  /// How to align the image within its bounds.
  ///
  /// An alignment of (0.0, 0.0) aligns the image to the top-left corner of its
  /// layout bounds.  An alignment of (1.0, 0.5) aligns the image to the middle
  /// of the right edge of its layout bounds.
  final FractionalOffset alignment;

  /// 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;

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

  @override
  _ImageState createState() => new _ImageState();

  @override
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('image: $image');
    if (width != null)
      description.add('width: $width');
    if (height != null)
      description.add('height: $height');
    if (color != null)
      description.add('color: $color');
    if (fit != null)
      description.add('fit: $fit');
    if (alignment != null)
      description.add('alignment: $alignment');
    if (repeat != ImageRepeat.noRepeat)
      description.add('repeat: $repeat');
    if (centerSlice != null)
      description.add('centerSlice: $centerSlice');
  }
}

class _ImageState extends State<Image> {
  ImageStream _imageStream;
  ImageInfo _imageInfo;

  @override
240
  void didChangeDependencies() {
241
    _resolveImage();
242
    super.didChangeDependencies();
243 244
  }

245 246 247 248 249 250
  @override
  void didUpdateConfig(Image oldConfig) {
    if (config.image != oldConfig.image)
      _resolveImage();
  }

251 252
  @override
  void reassemble() {
253
    _resolveImage(); // in case the image cache was flushed
254 255 256
    super.reassemble();
  }

257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
  void _resolveImage() {
    final ImageStream oldImageStream = _imageStream;
    _imageStream = config.image.resolve(createLocalImageConfiguration(
      context,
      size: config.width != null && config.height != null ? new Size(config.width, config.height) : null
    ));
    assert(_imageStream != null);
    if (_imageStream.key != oldImageStream?.key) {
      oldImageStream?.removeListener(_handleImageChanged);
      if (!config.gaplessPlayback)
        setState(() { _imageInfo = null; });
      _imageStream.addListener(_handleImageChanged);
    }
  }

272
  void _handleImageChanged(ImageInfo imageInfo, bool synchronousCall) {
273 274 275 276 277 278
    setState(() {
      _imageInfo = imageInfo;
    });
  }

  @override
279 280 281 282 283
  void dispose() {
    assert(_imageStream != null);
    _imageStream.removeListener(_handleImageChanged);
    super.dispose();
  }
284

285 286
  @override
  Widget build(BuildContext context) {
287 288 289 290 291 292 293 294 295 296 297 298
    return new RawImage(
      image: _imageInfo?.image,
      width: config.width,
      height: config.height,
      scale: _imageInfo?.scale ?? 1.0,
      color: config.color,
      fit: config.fit,
      alignment: config.alignment,
      repeat: config.repeat,
      centerSlice: config.centerSlice
    );
  }
299 300 301 302 303 304 305

  @override
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('stream: $_imageStream');
    description.add('pixels: $_imageInfo');
  }
306
}