container.dart 16.9 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
import 'package:flutter/painting.dart';
import 'package:flutter/rendering.dart';

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

13 14 15
// Examples can assume:
// BuildContext context;

16 17 18 19 20 21
/// 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].
22
///
23 24 25
/// The [child] is not clipped. To clip a child to the shape of a particular
/// [ShapeDecoration], consider using a [ClipPath] widget.
///
26
/// {@tool sample}
27 28 29 30
///
/// This sample shows a radial gradient that draws a moon on a night sky:
///
/// ```dart
31 32 33
/// DecoratedBox(
///   decoration: BoxDecoration(
///     gradient: RadialGradient(
34
///       center: const Alignment(-0.5, -0.6),
35 36 37 38 39 40 41 42
///       radius: 0.15,
///       colors: <Color>[
///         const Color(0xFFEEEEEE),
///         const Color(0xFF111133),
///       ],
///       stops: <double>[0.9, 1.0],
///     ),
///   ),
43
/// )
44
/// ```
45
/// {@end-tool}
46
///
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
/// ## Layout behavior
///
/// _See [BoxConstraints] for an introduction to box layout models._
///
/// Since [Container] combines a number of other widgets each with their own
/// layout behavior, [Container]'s layout behavior is somewhat complicated.
///
/// Summary: [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].
///
91 92
/// See also:
///
Ian Hickson's avatar
Ian Hickson committed
93 94
///  * [Ink], which paints a [Decoration] on a [Material], allowing
///    [InkResponse] and [InkWell] splashes to paint over them.
95 96 97 98 99
///  * [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.
100 101 102 103 104
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.
105
  const DecoratedBox({
106 107
    Key key,
    @required this.decoration,
108
    this.position = DecorationPosition.background,
109
    Widget child,
110 111 112
  }) : assert(decoration != null),
       assert(position != null),
       super(key: key, child: child);
113 114 115 116 117 118 119 120 121 122 123

  /// 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) {
124
    return RenderDecoratedBox(
125 126
      decoration: decoration,
      position: position,
Ian Hickson's avatar
Ian Hickson committed
127
      configuration: createLocalImageConfiguration(context),
128 129 130 131 132 133 134 135 136 137
    );
  }

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

  @override
140 141
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
142 143 144 145 146 147 148 149 150 151 152 153 154
    String label;
    if (position != null) {
      switch (position) {
        case DecorationPosition.background:
          label = 'bg';
          break;
        case DecorationPosition.foreground:
          label = 'fg';
          break;
      }
    } else {
      label = 'decoration';
    }
155 156
    properties.add(EnumProperty<DecorationPosition>('position', position, level: position != null ? DiagnosticLevel.hidden : DiagnosticLevel.info));
    properties.add(DiagnosticsProperty<Decoration>(
157 158 159 160 161
      label,
      decoration,
      ifNull: 'no decoration',
      showName: decoration != null,
    ));
162
  }
163 164 165 166 167 168 169
}

/// 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
170
/// [constraints] to the padded extent (incorporating the `width` and `height`
171 172 173 174 175 176 177
/// 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.
178 179 180 181 182 183
///
/// 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
184
///
185 186 187 188 189
/// ## 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
190
/// layout behavior, [Container]'s layout behavior is somewhat complicated.
191
///
192 193 194
/// Summary: [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.
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
///
/// 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].
///
229 230
/// ## Example
///
231
/// {@tool sample}
232
/// This example shows a 48x48 amber square (placed inside a [Center] widget in
Ian Hickson's avatar
Ian Hickson committed
233 234 235 236
/// 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:
///
237 238
/// ![An amber colored container with the dimensions of 48 square pixels.](https://flutter.github.io/assets-for-api-docs/assets/widgets/container_a.png)
///
Ian Hickson's avatar
Ian Hickson committed
239
/// ```dart
240 241
/// Center(
///   child: Container(
Ian Hickson's avatar
Ian Hickson committed
242
///     margin: const EdgeInsets.all(10.0),
243
///     color: Colors.amber[600],
Ian Hickson's avatar
Ian Hickson committed
244 245 246 247 248
///     width: 48.0,
///     height: 48.0,
///   ),
/// )
/// ```
249
/// {@end-tool}
250
///
251
/// {@tool sample}
Ian Hickson's avatar
Ian Hickson committed
252 253 254 255 256
///
/// 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
257 258
/// [color] makes the box blue. The [alignment] causes the [child] to be
/// centered in the box. Finally, the [transform] applies a slight rotation to the
Ian Hickson's avatar
Ian Hickson committed
259 260
/// entire contraption to complete the effect.
///
261 262 263
/// ![A blue rectangular container with 'Hello World' in the center, rotated
/// slightly in the z axis.](https://flutter.github.io/assets-for-api-docs/assets/widgets/container_b.png)
///
Ian Hickson's avatar
Ian Hickson committed
264
/// ```dart
265 266
/// Container(
///   constraints: BoxConstraints.expand(
Ian Hickson's avatar
Ian Hickson committed
267 268 269
///     height: Theme.of(context).textTheme.display1.fontSize * 1.1 + 200.0,
///   ),
///   padding: const EdgeInsets.all(8.0),
270
///   color: Colors.blue[600],
271
///   alignment: Alignment.center,
272 273 274 275 276
///   child: Text('Hello World',
///     style: Theme.of(context)
///         .textTheme
///         .display1
///         .copyWith(color: Colors.white)),
277
///   transform: Matrix4.rotationZ(0.1),
Ian Hickson's avatar
Ian Hickson committed
278 279
/// )
/// ```
280
/// {@end-tool}
Ian Hickson's avatar
Ian Hickson committed
281 282 283 284 285 286
///
/// 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
287 288
///  * [Ink], which paints a [Decoration] on a [Material], allowing
///    [InkResponse] and [InkWell] splashes to paint over them.
289
///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
290 291
class Container extends StatelessWidget {
  /// Creates a widget that combines common painting, positioning, and sizing widgets.
292 293
  ///
  /// The `height` and `width` values include the padding.
294
  ///
Ian Hickson's avatar
Ian Hickson committed
295 296 297 298 299
  /// 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`.
300 301
  Container({
    Key key,
302
    this.alignment,
303
    this.padding,
304 305
    Color color,
    Decoration decoration,
306 307 308 309 310 311
    this.foregroundDecoration,
    double width,
    double height,
    BoxConstraints constraints,
    this.margin,
    this.transform,
312
    this.child,
313 314 315 316 317 318 319 320
  }) : 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)".'
       ),
321
       decoration = decoration ?? (color != null ? BoxDecoration(color: color) : null),
322
       constraints =
323 324
        (width != null || height != null)
          ? constraints?.tighten(width: width, height: height)
325
            ?? BoxConstraints.tightFor(width: width, height: height)
326
          : constraints,
327
       super(key: key);
328

329
  /// The [child] contained by the container.
330
  ///
331 332 333 334
  /// 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.
335 336
  ///
  /// {@macro flutter.widgets.child}
337 338
  final Widget child;

339
  /// Align the [child] within the container.
340 341
  ///
  /// If non-null, the container will expand to fill its parent and position its
342 343 344 345
  /// 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.
346 347 348 349 350 351 352
  ///
  /// See also:
  ///
  ///  * [Alignment], a class with convenient constants typically used to
  ///    specify an [AlignmentGeometry].
  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
  ///    relative to text direction.
353
  final AlignmentGeometry alignment;
354

355 356
  /// Empty space to inscribe inside the [decoration]. The [child], if any, is
  /// placed inside this padding.
Ian Hickson's avatar
Ian Hickson committed
357 358 359
  ///
  /// This padding is in addition to any padding inherent in the [decoration];
  /// see [Decoration.padding].
360
  final EdgeInsetsGeometry padding;
361

362
  /// The decoration to paint behind the [child].
363 364 365 366
  ///
  /// A shorthand for specifying just a solid color is available in the
  /// constructor: set the `color` argument instead of the `decoration`
  /// argument.
367 368 369
  ///
  /// The [child] is not clipped to the decoration. To clip a child to the shape
  /// of a particular [ShapeDecoration], consider using a [ClipPath] widget.
370 371
  final Decoration decoration;

372
  /// The decoration to paint in front of the [child].
373 374 375
  final Decoration foregroundDecoration;

  /// Additional constraints to apply to the child.
376
  ///
377 378 379
  /// The constructor `width` and `height` arguments are combined with the
  /// `constraints` argument to set this property.
  ///
380
  /// The [padding] goes inside the constraints.
381 382
  final BoxConstraints constraints;

383
  /// Empty space to surround the [decoration] and [child].
384
  final EdgeInsetsGeometry margin;
385 386 387 388

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

389
  EdgeInsetsGeometry get _paddingIncludingDecoration {
390 391
    if (decoration == null || decoration.padding == null)
      return padding;
392
    final EdgeInsetsGeometry decorationPadding = decoration.padding;
393 394
    if (padding == null)
      return decorationPadding;
395
    return padding.add(decorationPadding);
396 397 398 399 400 401
  }

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

402
    if (child == null && (constraints == null || !constraints.isTight)) {
403
      current = LimitedBox(
404 405
        maxWidth: 0.0,
        maxHeight: 0.0,
406
        child: ConstrainedBox(constraints: const BoxConstraints.expand()),
407 408
      );
    }
409

410
    if (alignment != null)
411
      current = Align(alignment: alignment, child: current);
412

413
    final EdgeInsetsGeometry effectivePadding = _paddingIncludingDecoration;
414
    if (effectivePadding != null)
415
      current = Padding(padding: effectivePadding, child: current);
416 417

    if (decoration != null)
418
      current = DecoratedBox(decoration: decoration, child: current);
419 420

    if (foregroundDecoration != null) {
421
      current = DecoratedBox(
422 423
        decoration: foregroundDecoration,
        position: DecorationPosition.foreground,
424
        child: current,
425 426 427 428
      );
    }

    if (constraints != null)
429
      current = ConstrainedBox(constraints: constraints, child: current);
430 431

    if (margin != null)
432
      current = Padding(padding: margin, child: current);
433 434

    if (transform != null)
435
      current = Transform(transform: transform, child: current);
436 437 438 439 440

    return current;
  }

  @override
441 442
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
443 444 445 446 447 448 449
    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, showName: false, defaultValue: null));
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
    properties.add(DiagnosticsProperty<Decoration>('bg', decoration, defaultValue: null));
    properties.add(DiagnosticsProperty<Decoration>('fg', foregroundDecoration, defaultValue: null));
    properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null));
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('margin', margin, defaultValue: null));
    properties.add(ObjectFlagProperty<Matrix4>.has('transform', transform));
450 451
  }
}