container.dart 18.5 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// 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 snippet}
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
}

/// A convenience widget that combines common painting, positioning, and sizing
/// widgets.
///
168 169
/// {@youtube 560 315 https://www.youtube.com/watch?v=c1xLMaTUWCY}
///
170 171
/// A container first surrounds the child with [padding] (inflated by any
/// borders present in the [decoration]) and then applies additional
172
/// [constraints] to the padded extent (incorporating the `width` and `height`
173 174 175 176 177 178 179
/// 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.
180 181 182 183 184 185
///
/// 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
186
///
187 188 189 190 191
/// ## 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
192
/// layout behavior, [Container]'s layout behavior is somewhat complicated.
193
///
194 195 196
/// 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.
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
///
/// 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].
///
231 232
/// ## Example
///
233
/// {@tool snippet}
234
/// This example shows a 48x48 amber square (placed inside a [Center] widget in
Ian Hickson's avatar
Ian Hickson committed
235 236 237 238
/// 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:
///
239 240
/// ![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
241
/// ```dart
242 243
/// Center(
///   child: Container(
Ian Hickson's avatar
Ian Hickson committed
244
///     margin: const EdgeInsets.all(10.0),
245
///     color: Colors.amber[600],
Ian Hickson's avatar
Ian Hickson committed
246 247 248 249 250
///     width: 48.0,
///     height: 48.0,
///   ),
/// )
/// ```
251
/// {@end-tool}
252
///
253
/// {@tool snippet}
Ian Hickson's avatar
Ian Hickson committed
254 255 256 257 258
///
/// 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
259 260
/// [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
261 262
/// entire contraption to complete the effect.
///
263 264 265
/// ![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
266
/// ```dart
267 268
/// Container(
///   constraints: BoxConstraints.expand(
269
///     height: Theme.of(context).textTheme.headline4.fontSize * 1.1 + 200.0,
Ian Hickson's avatar
Ian Hickson committed
270 271
///   ),
///   padding: const EdgeInsets.all(8.0),
272
///   color: Colors.blue[600],
273
///   alignment: Alignment.center,
274 275 276
///   child: Text('Hello World',
///     style: Theme.of(context)
///         .textTheme
277
///         .headline4
278
///         .copyWith(color: Colors.white)),
279
///   transform: Matrix4.rotationZ(0.1),
Ian Hickson's avatar
Ian Hickson committed
280 281
/// )
/// ```
282
/// {@end-tool}
Ian Hickson's avatar
Ian Hickson committed
283 284 285 286 287 288
///
/// 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
289 290
///  * [Ink], which paints a [Decoration] on a [Material], allowing
///    [InkResponse] and [InkWell] splashes to paint over them.
291
///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
292 293
class Container extends StatelessWidget {
  /// Creates a widget that combines common painting, positioning, and sizing widgets.
294 295
  ///
  /// The `height` and `width` values include the padding.
296
  ///
297 298 299 300
  /// The `color` and `decoration` arguments cannot both be supplied, since
  /// it would potentially result in the decoration drawing over the background
  /// color. To supply a decoration with a color, use `decoration:
  /// BoxDecoration(color: color)`.
301 302
  Container({
    Key key,
303
    this.alignment,
304
    this.padding,
305 306
    this.color,
    this.decoration,
307 308 309 310 311 312
    this.foregroundDecoration,
    double width,
    double height,
    BoxConstraints constraints,
    this.margin,
    this.transform,
313
    this.child,
314
    this.clipBehavior = Clip.none,
315 316 317 318
  }) : assert(margin == null || margin.isNonNegative),
       assert(padding == null || padding.isNonNegative),
       assert(decoration == null || decoration.debugAssertIsValid()),
       assert(constraints == null || constraints.debugAssertIsValid()),
319
       assert(clipBehavior != null),
320 321
       assert(color == null || decoration == null,
         'Cannot provide both a color and a decoration\n'
322
         'To provide both, use "decoration: BoxDecoration(color: color)".'
323
       ),
324
       constraints =
325 326
        (width != null || height != null)
          ? constraints?.tighten(width: width, height: height)
327
            ?? BoxConstraints.tightFor(width: width, height: height)
328
          : constraints,
329
       super(key: key);
330

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

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

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

364 365 366 367 368 369 370 371 372 373 374
  /// The color to paint behind the [child].
  ///
  /// This property should be preferred when the background is a simple color.
  /// For other cases, such as gradients or images, use the [decoration]
  /// property.
  ///
  /// If the [decoration] is used, this property must be null. A background
  /// color may still be painted by the [decoration] even if this property is
  /// null.
  final Color color;

375
  /// The decoration to paint behind the [child].
376
  ///
377
  /// Use the [color] property to specify a simple solid color.
378 379 380
  ///
  /// The [child] is not clipped to the decoration. To clip a child to the shape
  /// of a particular [ShapeDecoration], consider using a [ClipPath] widget.
381 382
  final Decoration decoration;

383
  /// The decoration to paint in front of the [child].
384 385 386
  final Decoration foregroundDecoration;

  /// Additional constraints to apply to the child.
387
  ///
388 389 390
  /// The constructor `width` and `height` arguments are combined with the
  /// `constraints` argument to set this property.
  ///
391
  /// The [padding] goes inside the constraints.
392 393
  final BoxConstraints constraints;

394
  /// Empty space to surround the [decoration] and [child].
395
  final EdgeInsetsGeometry margin;
396 397 398 399

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

400 401 402 403 404
  /// The clip behavior when [Container.decoration] has a clipPath.
  ///
  /// Defaults to [Clip.none].
  final Clip clipBehavior;

405
  EdgeInsetsGeometry get _paddingIncludingDecoration {
406 407
    if (decoration == null || decoration.padding == null)
      return padding;
408
    final EdgeInsetsGeometry decorationPadding = decoration.padding;
409 410
    if (padding == null)
      return decorationPadding;
411
    return padding.add(decorationPadding);
412 413 414 415 416 417
  }

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

418
    if (child == null && (constraints == null || !constraints.isTight)) {
419
      current = LimitedBox(
420 421
        maxWidth: 0.0,
        maxHeight: 0.0,
422
        child: ConstrainedBox(constraints: const BoxConstraints.expand()),
423 424
      );
    }
425

426
    if (alignment != null)
427
      current = Align(alignment: alignment, child: current);
428

429
    final EdgeInsetsGeometry effectivePadding = _paddingIncludingDecoration;
430
    if (effectivePadding != null)
431
      current = Padding(padding: effectivePadding, child: current);
432

433 434 435
    if (color != null)
      current = ColoredBox(color: color, child: current);

436
    if (decoration != null)
437
      current = DecoratedBox(decoration: decoration, child: current);
438 439

    if (foregroundDecoration != null) {
440
      current = DecoratedBox(
441 442
        decoration: foregroundDecoration,
        position: DecorationPosition.foreground,
443
        child: current,
444 445 446 447
      );
    }

    if (constraints != null)
448
      current = ConstrainedBox(constraints: constraints, child: current);
449 450

    if (margin != null)
451
      current = Padding(padding: margin, child: current);
452 453

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

456 457 458 459 460 461 462 463 464 465 466
    if (clipBehavior != Clip.none) {
      current = ClipPath(
        clipper: _DecorationClipper(
          textDirection: Directionality.of(context),
          decoration: decoration
        ),
        clipBehavior: clipBehavior,
        child: current,
      );
    }

467 468 469 470
    return current;
  }

  @override
471 472
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
473 474
    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, showName: false, defaultValue: null));
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
475
    properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: Clip.none));
Dan Field's avatar
Dan Field committed
476 477 478 479
    if (color != null)
      properties.add(DiagnosticsProperty<Color>('bg', color));
    else
      properties.add(DiagnosticsProperty<Decoration>('bg', decoration, defaultValue: null));
480 481 482 483
    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));
484 485
  }
}
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508

/// A clipper that uses [Decoration.getClipPath] to clip.
class _DecorationClipper extends CustomClipper<Path> {
  _DecorationClipper({
    TextDirection textDirection,
    @required this.decoration
  }) : assert (decoration != null),
       textDirection = textDirection ?? TextDirection.ltr;

  final TextDirection textDirection;
  final Decoration decoration;

  @override
  Path getClip(Size size) {
    return decoration.getClipPath(Offset.zero & size, textDirection);
  }

  @override
  bool shouldReclip(_DecorationClipper oldClipper) {
    return oldClipper.decoration != decoration
        || oldClipper.textDirection != textDirection;
  }
}