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

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

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

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

20 21 22 23 24 25 26 27 28
/// 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,
29
    // TODO(ianh): provide the locale
30 31 32 33 34 35 36 37 38 39 40
    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:
///
/// * [new Image], for obtaining an image from an [ImageProvider].
41
/// * [new Image.asset], for obtaining an image from an [AssetBundle]
42
///   using a key.
Ian Hickson's avatar
Ian Hickson committed
43 44
/// * [new Image.network], for obtaining an image from a URL.
/// * [new Image.file], for obtaining an image from a [File].
45 46 47 48 49 50 51
///
/// 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.
52 53 54 55
///
/// See also:
///
///  * [Icon]
56 57 58 59
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
60
  /// [new Image.network] and [new Image.asset] respectively.
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
  ///
  /// 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.
81
  Image.network(String src, {
82 83 84 85 86 87 88 89 90 91 92 93 94
    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
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
  /// 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);

115 116 117 118 119 120 121 122 123 124 125 126 127
  /// 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.
128
  Image.asset(String name, {
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
    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);

  /// 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
162
  /// How to inscribe the image into the space allocated during layout.
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 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
  ///
  /// The default varies based on the other fields. See the discussion at
  /// [paintImage].
  final ImageFit fit;

  /// 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
  void dependenciesChanged() {
    _resolveImage();
    super.dependenciesChanged();
  }

225 226 227 228 229 230
  @override
  void didUpdateConfig(Image oldConfig) {
    if (config.image != oldConfig.image)
      _resolveImage();
  }

231 232
  @override
  void reassemble() {
233
    _resolveImage(); // in case the image cache was flushed
234 235 236
    super.reassemble();
  }

237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
  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);
    }
  }

252
  void _handleImageChanged(ImageInfo imageInfo, bool synchronousCall) {
253 254 255 256 257 258
    setState(() {
      _imageInfo = imageInfo;
    });
  }

  @override
259 260 261 262 263
  void dispose() {
    assert(_imageStream != null);
    _imageStream.removeListener(_handleImageChanged);
    super.dispose();
  }
264

265 266
  @override
  Widget build(BuildContext context) {
267 268 269 270 271 272 273 274 275 276 277 278
    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
    );
  }
279 280 281 282 283 284 285

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