container.dart 18.2 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
  ///
Ian Hickson's avatar
Ian Hickson committed
297 298 299 300 301
  /// 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`.
302 303
  Container({
    Key key,
304
    this.alignment,
305
    this.padding,
306 307
    Color color,
    Decoration decoration,
308 309 310 311 312 313
    this.foregroundDecoration,
    double width,
    double height,
    BoxConstraints constraints,
    this.margin,
    this.transform,
314
    this.child,
315
    this.clipBehavior = Clip.none,
316 317 318 319
  }) : assert(margin == null || margin.isNonNegative),
       assert(padding == null || padding.isNonNegative),
       assert(decoration == null || decoration.debugAssertIsValid()),
       assert(constraints == null || constraints.debugAssertIsValid()),
320
       assert(clipBehavior != null),
321 322 323 324
       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)".'
       ),
325
       decoration = decoration ?? (color != null ? BoxDecoration(color: color) : null),
326
       constraints =
327 328
        (width != null || height != null)
          ? constraints?.tighten(width: width, height: height)
329
            ?? BoxConstraints.tightFor(width: width, height: height)
330
          : constraints,
331
       super(key: key);
332

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

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

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

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

376
  /// The decoration to paint in front of the [child].
377 378 379
  final Decoration foregroundDecoration;

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

387
  /// Empty space to surround the [decoration] and [child].
388
  final EdgeInsetsGeometry margin;
389 390 391 392

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

393 394 395 396 397
  /// The clip behavior when [Container.decoration] has a clipPath.
  ///
  /// Defaults to [Clip.none].
  final Clip clipBehavior;

398
  EdgeInsetsGeometry get _paddingIncludingDecoration {
399 400
    if (decoration == null || decoration.padding == null)
      return padding;
401
    final EdgeInsetsGeometry decorationPadding = decoration.padding;
402 403
    if (padding == null)
      return decorationPadding;
404
    return padding.add(decorationPadding);
405 406 407 408 409 410
  }

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

411
    if (child == null && (constraints == null || !constraints.isTight)) {
412
      current = LimitedBox(
413 414
        maxWidth: 0.0,
        maxHeight: 0.0,
415
        child: ConstrainedBox(constraints: const BoxConstraints.expand()),
416 417
      );
    }
418

419
    if (alignment != null)
420
      current = Align(alignment: alignment, child: current);
421

422
    final EdgeInsetsGeometry effectivePadding = _paddingIncludingDecoration;
423
    if (effectivePadding != null)
424
      current = Padding(padding: effectivePadding, child: current);
425 426

    if (decoration != null)
427
      current = DecoratedBox(decoration: decoration, child: current);
428 429

    if (foregroundDecoration != null) {
430
      current = DecoratedBox(
431 432
        decoration: foregroundDecoration,
        position: DecorationPosition.foreground,
433
        child: current,
434 435 436 437
      );
    }

    if (constraints != null)
438
      current = ConstrainedBox(constraints: constraints, child: current);
439 440

    if (margin != null)
441
      current = Padding(padding: margin, child: current);
442 443

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

446 447 448 449 450 451 452 453 454 455 456
    if (clipBehavior != Clip.none) {
      current = ClipPath(
        clipper: _DecorationClipper(
          textDirection: Directionality.of(context),
          decoration: decoration
        ),
        clipBehavior: clipBehavior,
        child: current,
      );
    }

457 458 459 460
    return current;
  }

  @override
461 462
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
463 464
    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, showName: false, defaultValue: null));
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
465
    properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: Clip.none));
466 467 468 469 470
    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));
471 472
  }
}
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495

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