container.dart 14.5 KB
Newer Older
1 2 3 4
// Copyright 2016 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 'package:flutter/foundation.dart';
6 7 8 9 10 11 12 13 14 15 16 17 18
import 'package:flutter/painting.dart';
import 'package:flutter/rendering.dart';

import 'basic.dart';
import 'framework.dart';
import 'image.dart';

/// A widget that paints a [Decoration] either before or after its child paints.
///
/// [Container] insets its child by the widths of the borders; this widget does
/// not.
///
/// Commonly used with [BoxDecoration].
19 20 21 22 23 24 25 26 27
///
/// ## Sample code
///
/// This sample shows a radial gradient that draws a moon on a night sky:
///
/// ```dart
/// new DecoratedBox(
///   decoration: new BoxDecoration(
///     gradient: new RadialGradient(
28
///       center: const Alignment(-0.5, -0.6),
29 30 31 32 33 34 35 36
///       radius: 0.15,
///       colors: <Color>[
///         const Color(0xFFEEEEEE),
///         const Color(0xFF111133),
///       ],
///       stops: <double>[0.9, 1.0],
///     ),
///   ),
37
/// )
38 39
/// ```
///
40 41
/// See also:
///
Ian Hickson's avatar
Ian Hickson committed
42 43
///  * [Ink], which paints a [Decoration] on a [Material], allowing
///    [InkResponse] and [InkWell] splashes to paint over them.
44 45 46 47 48
///  * [DecoratedBoxTransition], the version of this class that animates on the
///    [decoration] property.
///  * [Decoration], which you can extend to provide other effects with
///    [DecoratedBox].
///  * [CustomPaint], another way to draw custom effects from the widget layer.
49 50 51 52 53
class DecoratedBox extends SingleChildRenderObjectWidget {
  /// Creates a widget that paints a [Decoration].
  ///
  /// The [decoration] and [position] arguments must not be null. By default the
  /// decoration paints behind the child.
54
  const DecoratedBox({
55 56
    Key key,
    @required this.decoration,
57
    this.position = DecorationPosition.background,
58
    Widget child
59 60 61
  }) : assert(decoration != null),
       assert(position != null),
       super(key: key, child: child);
62 63 64 65 66 67 68 69 70 71 72 73 74 75

  /// What decoration to paint.
  ///
  /// Commonly a [BoxDecoration].
  final Decoration decoration;

  /// Whether to paint the box decoration behind or in front of the child.
  final DecorationPosition position;

  @override
  RenderDecoratedBox createRenderObject(BuildContext context) {
    return new RenderDecoratedBox(
      decoration: decoration,
      position: position,
Ian Hickson's avatar
Ian Hickson committed
76
      configuration: createLocalImageConfiguration(context),
77 78 79 80 81 82 83 84 85 86
    );
  }

  @override
  void updateRenderObject(BuildContext context, RenderDecoratedBox renderObject) {
    renderObject
      ..decoration = decoration
      ..configuration = createLocalImageConfiguration(context)
      ..position = position;
  }
87 88

  @override
89 90
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
91 92 93 94 95 96 97 98 99 100 101 102 103
    String label;
    if (position != null) {
      switch (position) {
        case DecorationPosition.background:
          label = 'bg';
          break;
        case DecorationPosition.foreground:
          label = 'fg';
          break;
      }
    } else {
      label = 'decoration';
    }
104 105
    properties.add(new EnumProperty<DecorationPosition>('position', position, level: position != null ? DiagnosticLevel.hidden : DiagnosticLevel.info));
    properties.add(new DiagnosticsProperty<Decoration>(
106 107 108 109 110
      label,
      decoration,
      ifNull: 'no decoration',
      showName: decoration != null,
    ));
111
  }
112 113 114 115 116 117 118
}

/// A convenience widget that combines common painting, positioning, and sizing
/// widgets.
///
/// A container first surrounds the child with [padding] (inflated by any
/// borders present in the [decoration]) and then applies additional
119
/// [constraints] to the padded extent (incorporating the `width` and `height`
120 121 122 123 124 125 126
/// as constraints, if either is non-null). The container is then surrounded by
/// additional empty space described from the [margin].
///
/// During painting, the container first applies the given [transform], then
/// paints the [decoration] to fill the padded extent, then it paints the child,
/// and finally paints the [foregroundDecoration], also filling the padded
/// extent.
127 128 129 130 131 132
///
/// Containers with no children try to be as big as possible unless the incoming
/// constraints are unbounded, in which case they try to be as small as
/// possible. Containers with children size themselves to their children. The
/// `width`, `height`, and [constraints] arguments to the constructor override
/// this.
Ian Hickson's avatar
Ian Hickson committed
133
///
134 135 136 137 138
/// ## Layout behavior
///
/// _See [BoxConstraints] for an introduction to box layout models._
///
/// Since [Container] combines a number of other widgets each with their own
Ian Hickson's avatar
Ian Hickson committed
139
/// layout behavior, [Container]'s layout behavior is somewhat complicated.
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
///
/// tl;dr: [Container] tries, in order: to honor [alignment], to size itself to
/// the [child], to honor the `width`, `height`, and [constraints], to expand to
/// fit the parent, to be as small as possible.
///
/// More specifically:
///
/// If the widget has no child, no `height`, no `width`, no [constraints],
/// and the parent provides unbounded constraints, then [Container] tries to
/// size as small as possible.
///
/// If the widget has no child and no [alignment], but a `height`, `width`, or
/// [constraints] are provided, then the [Container] tries to be as small as
/// possible given the combination of those constraints and the parent's
/// constraints.
///
/// If the widget has no child, no `height`, no `width`, no [constraints], and
/// no [alignment], but the parent provides bounded constraints, then
/// [Container] expands to fit the constraints provided by the parent.
///
/// If the widget has an [alignment], and the parent provides unbounded
/// constraints, then the [Container] tries to size itself around the child.
///
/// If the widget has an [alignment], and the parent provides bounded
/// constraints, then the [Container] tries to expand to fit the parent, and
/// then positions the child within itself as per the [alignment].
///
/// Otherwise, the widget has a [child] but no `height`, no `width`, no
/// [constraints], and no [alignment], and the [Container] passes the
/// constraints from the parent to the child and sizes itself to match the
/// child.
///
/// The [margin] and [padding] properties also affect the layout, as described
/// in the documentation for those properties. (Their effects merely augment the
/// rules described above.) The [decoration] can implicitly increase the
/// [padding] (e.g. borders in a [BoxDecoration] contribute to the [padding]);
/// see [Decoration.padding].
///
Ian Hickson's avatar
Ian Hickson committed
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
/// ## Sample code
///
/// This example shows a 48x48 green square (placed inside a [Center] widget in
/// case the parent widget has its own opinions regarding the size that the
/// [Container] should take), with a margin so that it stays away from
/// neighboring widgets:
///
/// ```dart
/// new Center(
///   child: new Container(
///     margin: const EdgeInsets.all(10.0),
///     color: const Color(0xFF00FF00),
///     width: 48.0,
///     height: 48.0,
///   ),
/// )
/// ```
///
/// This example shows how to use many of the features of [Container] at once.
/// The [constraints] are set to fit the font size plus ample headroom
/// vertically, while expanding horizontally to fit the parent. The [padding] is
/// used to make sure there is space between the contents and the text. The
200
/// `color` makes the box teal. The [alignment] causes the [child] to be
Ian Hickson's avatar
Ian Hickson committed
201 202 203 204 205 206 207 208 209 210 211
/// centered in the box. The [foregroundDecoration] overlays a nine-patch image
/// onto the text. Finally, the [transform] applies a slight rotation to the
/// entire contraption to complete the effect.
///
/// ```dart
/// new Container(
///   constraints: new BoxConstraints.expand(
///     height: Theme.of(context).textTheme.display1.fontSize * 1.1 + 200.0,
///   ),
///   padding: const EdgeInsets.all(8.0),
///   color: Colors.teal.shade700,
212
///   alignment: Alignment.center,
Ian Hickson's avatar
Ian Hickson committed
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
///   child: new Text('Hello World', style: Theme.of(context).textTheme.display1.copyWith(color: Colors.white)),
///   foregroundDecoration: new BoxDecoration(
///     image: new DecorationImage(
///       image: new NetworkImage('https://www.example.com/images/frame.png'),
///       centerSlice: new Rect.fromLTRB(270.0, 180.0, 1360.0, 730.0),
///     ),
///   ),
///   transform: new Matrix4.rotationZ(0.1),
/// )
/// ```
///
/// See also:
///
///  * [AnimatedContainer], a variant that smoothly animates the properties when
///    they change.
///  * [Border], which has a sample which uses [Container] heavily.
Ian Hickson's avatar
Ian Hickson committed
229 230
///  * [Ink], which paints a [Decoration] on a [Material], allowing
///    [InkResponse] and [InkWell] splashes to paint over them.
231
///  * The [catalog of layout widgets](https://flutter.io/widgets/layout/).
232 233
class Container extends StatelessWidget {
  /// Creates a widget that combines common painting, positioning, and sizing widgets.
234 235
  ///
  /// The `height` and `width` values include the padding.
236
  ///
Ian Hickson's avatar
Ian Hickson committed
237 238 239 240 241
  /// The `color` argument is a shorthand for `decoration: new
  /// BoxDecoration(color: color)`, which means you cannot supply both a `color`
  /// and a `decoration` argument. If you want to have both a `color` and a
  /// `decoration`, you can pass the color as the `color` argument to the
  /// `BoxDecoration`.
242 243
  Container({
    Key key,
244
    this.alignment,
245
    this.padding,
246 247
    Color color,
    Decoration decoration,
248 249 250 251 252 253
    this.foregroundDecoration,
    double width,
    double height,
    BoxConstraints constraints,
    this.margin,
    this.transform,
254
    this.child,
255 256 257 258 259 260 261 262 263
  }) : assert(margin == null || margin.isNonNegative),
       assert(padding == null || padding.isNonNegative),
       assert(decoration == null || decoration.debugAssertIsValid()),
       assert(constraints == null || constraints.debugAssertIsValid()),
       assert(color == null || decoration == null,
         'Cannot provide both a color and a decoration\n'
         'The color argument is just a shorthand for "decoration: new BoxDecoration(color: color)".'
       ),
       decoration = decoration ?? (color != null ? new BoxDecoration(color: color) : null),
264
       constraints =
265 266 267 268
        (width != null || height != null)
          ? constraints?.tighten(width: width, height: height)
            ?? new BoxConstraints.tightFor(width: width, height: height)
          : constraints,
269
       super(key: key);
270

271
  /// The [child] contained by the container.
272
  ///
273 274 275 276
  /// If null, and if the [constraints] are unbounded or also null, the
  /// container will expand to fill all available space in its parent, unless
  /// the parent provides unbounded constraints, in which case the container
  /// will attempt to be as small as possible.
277 278
  ///
  /// {@macro flutter.widgets.child}
279 280
  final Widget child;

281
  /// Align the [child] within the container.
282 283
  ///
  /// If non-null, the container will expand to fill its parent and position its
284 285 286 287
  /// child within itself according to the given value. If the incoming
  /// constraints are unbounded, then the child will be shrink-wrapped instead.
  ///
  /// Ignored if [child] is null.
288 289 290 291 292 293 294
  ///
  /// See also:
  ///
  ///  * [Alignment], a class with convenient constants typically used to
  ///    specify an [AlignmentGeometry].
  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
  ///    relative to text direction.
295
  final AlignmentGeometry alignment;
296

297 298
  /// Empty space to inscribe inside the [decoration]. The [child], if any, is
  /// placed inside this padding.
Ian Hickson's avatar
Ian Hickson committed
299 300 301
  ///
  /// This padding is in addition to any padding inherent in the [decoration];
  /// see [Decoration.padding].
302
  final EdgeInsetsGeometry padding;
303

304
  /// The decoration to paint behind the [child].
305 306 307 308
  ///
  /// A shorthand for specifying just a solid color is available in the
  /// constructor: set the `color` argument instead of the `decoration`
  /// argument.
309 310
  final Decoration decoration;

311
  /// The decoration to paint in front of the [child].
312 313 314
  final Decoration foregroundDecoration;

  /// Additional constraints to apply to the child.
315
  ///
316 317 318
  /// The constructor `width` and `height` arguments are combined with the
  /// `constraints` argument to set this property.
  ///
319
  /// The [padding] goes inside the constraints.
320 321
  final BoxConstraints constraints;

322
  /// Empty space to surround the [decoration] and [child].
323
  final EdgeInsetsGeometry margin;
324 325 326 327

  /// The transformation matrix to apply before painting the container.
  final Matrix4 transform;

328
  EdgeInsetsGeometry get _paddingIncludingDecoration {
329 330
    if (decoration == null || decoration.padding == null)
      return padding;
331
    final EdgeInsetsGeometry decorationPadding = decoration.padding;
332 333
    if (padding == null)
      return decorationPadding;
334
    return padding.add(decorationPadding);
335 336 337 338 339 340
  }

  @override
  Widget build(BuildContext context) {
    Widget current = child;

341 342 343 344 345 346 347
    if (child == null && (constraints == null || !constraints.isTight)) {
      current = new LimitedBox(
        maxWidth: 0.0,
        maxHeight: 0.0,
        child: new ConstrainedBox(constraints: const BoxConstraints.expand())
      );
    }
348

349 350 351
    if (alignment != null)
      current = new Align(alignment: alignment, child: current);

352
    final EdgeInsetsGeometry effectivePadding = _paddingIncludingDecoration;
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
    if (effectivePadding != null)
      current = new Padding(padding: effectivePadding, child: current);

    if (decoration != null)
      current = new DecoratedBox(decoration: decoration, child: current);

    if (foregroundDecoration != null) {
      current = new DecoratedBox(
        decoration: foregroundDecoration,
        position: DecorationPosition.foreground,
        child: current
      );
    }

    if (constraints != null)
      current = new ConstrainedBox(constraints: constraints, child: current);

    if (margin != null)
      current = new Padding(padding: margin, child: current);

    if (transform != null)
      current = new Transform(transform: transform, child: current);

    return current;
  }

  @override
380 381 382 383 384 385 386 387 388
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, showName: false, defaultValue: null));
    properties.add(new DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
    properties.add(new DiagnosticsProperty<Decoration>('bg', decoration, defaultValue: null));
    properties.add(new DiagnosticsProperty<Decoration>('fg', foregroundDecoration, defaultValue: null));
    properties.add(new DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null));
    properties.add(new DiagnosticsProperty<EdgeInsetsGeometry>('margin', margin, defaultValue: null));
    properties.add(new ObjectFlagProperty<Matrix4>.has('transform', transform));
389 390
  }
}