container.dart 16.8 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 6
// @dart = 2.8

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

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

15 16 17
// Examples can assume:
// BuildContext context;

18 19 20 21 22 23
/// 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].
24
///
25 26 27
/// The [child] is not clipped. To clip a child to the shape of a particular
/// [ShapeDecoration], consider using a [ClipPath] widget.
///
28
/// {@tool snippet}
29 30 31 32
///
/// This sample shows a radial gradient that draws a moon on a night sky:
///
/// ```dart
33 34 35
/// DecoratedBox(
///   decoration: BoxDecoration(
///     gradient: RadialGradient(
36
///       center: const Alignment(-0.5, -0.6),
37 38 39 40 41 42 43 44
///       radius: 0.15,
///       colors: <Color>[
///         const Color(0xFFEEEEEE),
///         const Color(0xFF111133),
///       ],
///       stops: <double>[0.9, 1.0],
///     ),
///   ),
45
/// )
46
/// ```
47
/// {@end-tool}
48
///
49 50
/// See also:
///
Ian Hickson's avatar
Ian Hickson committed
51 52
///  * [Ink], which paints a [Decoration] on a [Material], allowing
///    [InkResponse] and [InkWell] splashes to paint over them.
53 54 55 56 57
///  * [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.
58 59 60 61 62
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.
63
  const DecoratedBox({
64 65
    Key key,
    @required this.decoration,
66
    this.position = DecorationPosition.background,
67
    Widget child,
68 69 70
  }) : assert(decoration != null),
       assert(position != null),
       super(key: key, child: child);
71 72 73 74 75 76 77 78 79 80 81

  /// 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) {
82
    return RenderDecoratedBox(
83 84
      decoration: decoration,
      position: position,
Ian Hickson's avatar
Ian Hickson committed
85
      configuration: createLocalImageConfiguration(context),
86 87 88 89 90 91 92 93 94 95
    );
  }

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

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

/// A convenience widget that combines common painting, positioning, and sizing
/// widgets.
///
126 127
/// {@youtube 560 315 https://www.youtube.com/watch?v=c1xLMaTUWCY}
///
128 129
/// A container first surrounds the child with [padding] (inflated by any
/// borders present in the [decoration]) and then applies additional
130
/// [constraints] to the padded extent (incorporating the `width` and `height`
131 132 133 134 135 136 137
/// 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.
138 139 140 141 142 143
///
/// 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
144
///
145 146 147 148 149
/// By default, containers return false for all hit tests. If the [color]
/// property is specified, the hit testing is handled by [ColoredBox], which
/// always returns true. If the [decoration] or [foregroundDecoration] properties
/// are specified, hit testing is handled by [Decoration.hitTest].
///
150 151 152 153 154
/// ## 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
155
/// layout behavior, [Container]'s layout behavior is somewhat complicated.
156
///
157 158 159
/// 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.
160 161 162 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
///
/// 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].
///
194 195
/// ## Example
///
196
/// {@tool snippet}
197
/// This example shows a 48x48 amber square (placed inside a [Center] widget in
Ian Hickson's avatar
Ian Hickson committed
198 199 200 201
/// 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:
///
202 203
/// ![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
204
/// ```dart
205 206
/// Center(
///   child: Container(
Ian Hickson's avatar
Ian Hickson committed
207
///     margin: const EdgeInsets.all(10.0),
208
///     color: Colors.amber[600],
Ian Hickson's avatar
Ian Hickson committed
209 210 211 212 213
///     width: 48.0,
///     height: 48.0,
///   ),
/// )
/// ```
214
/// {@end-tool}
215
///
216
/// {@tool snippet}
Ian Hickson's avatar
Ian Hickson committed
217 218 219 220 221
///
/// 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
222 223
/// [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
224 225
/// entire contraption to complete the effect.
///
226 227 228
/// ![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
229
/// ```dart
230 231
/// Container(
///   constraints: BoxConstraints.expand(
232
///     height: Theme.of(context).textTheme.headline4.fontSize * 1.1 + 200.0,
Ian Hickson's avatar
Ian Hickson committed
233 234
///   ),
///   padding: const EdgeInsets.all(8.0),
235
///   color: Colors.blue[600],
236
///   alignment: Alignment.center,
237 238 239
///   child: Text('Hello World',
///     style: Theme.of(context)
///         .textTheme
240
///         .headline4
241
///         .copyWith(color: Colors.white)),
242
///   transform: Matrix4.rotationZ(0.1),
Ian Hickson's avatar
Ian Hickson committed
243 244
/// )
/// ```
245
/// {@end-tool}
Ian Hickson's avatar
Ian Hickson committed
246 247 248 249 250 251
///
/// 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
252 253
///  * [Ink], which paints a [Decoration] on a [Material], allowing
///    [InkResponse] and [InkWell] splashes to paint over them.
254
///  * Cookbook: [Animate the properties of a container](https://flutter.dev/docs/cookbook/animation/animated-container)
255
///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
256 257
class Container extends StatelessWidget {
  /// Creates a widget that combines common painting, positioning, and sizing widgets.
258 259
  ///
  /// The `height` and `width` values include the padding.
260
  ///
261 262 263 264
  /// 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)`.
265 266
  Container({
    Key key,
267
    this.alignment,
268
    this.padding,
269 270
    this.color,
    this.decoration,
271 272 273 274 275 276
    this.foregroundDecoration,
    double width,
    double height,
    BoxConstraints constraints,
    this.margin,
    this.transform,
277
    this.child,
278
    this.clipBehavior = Clip.none,
279 280 281 282
  }) : assert(margin == null || margin.isNonNegative),
       assert(padding == null || padding.isNonNegative),
       assert(decoration == null || decoration.debugAssertIsValid()),
       assert(constraints == null || constraints.debugAssertIsValid()),
283
       assert(clipBehavior != null),
284 285
       assert(color == null || decoration == null,
         'Cannot provide both a color and a decoration\n'
286
         'To provide both, use "decoration: BoxDecoration(color: color)".'
287
       ),
288
       constraints =
289 290
        (width != null || height != null)
          ? constraints?.tighten(width: width, height: height)
291
            ?? BoxConstraints.tightFor(width: width, height: height)
292
          : constraints,
293
       super(key: key);
294

295
  /// The [child] contained by the container.
296
  ///
297 298 299 300
  /// 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.
301 302
  ///
  /// {@macro flutter.widgets.child}
303 304
  final Widget child;

305
  /// Align the [child] within the container.
306 307
  ///
  /// If non-null, the container will expand to fill its parent and position its
308 309 310 311
  /// 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.
312 313 314 315 316 317 318
  ///
  /// See also:
  ///
  ///  * [Alignment], a class with convenient constants typically used to
  ///    specify an [AlignmentGeometry].
  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
  ///    relative to text direction.
319
  final AlignmentGeometry alignment;
320

321 322
  /// Empty space to inscribe inside the [decoration]. The [child], if any, is
  /// placed inside this padding.
Ian Hickson's avatar
Ian Hickson committed
323 324 325
  ///
  /// This padding is in addition to any padding inherent in the [decoration];
  /// see [Decoration.padding].
326
  final EdgeInsetsGeometry padding;
327

328 329 330 331 332 333 334 335 336 337 338
  /// 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;

339
  /// The decoration to paint behind the [child].
340
  ///
341
  /// Use the [color] property to specify a simple solid color.
342 343 344
  ///
  /// The [child] is not clipped to the decoration. To clip a child to the shape
  /// of a particular [ShapeDecoration], consider using a [ClipPath] widget.
345 346
  final Decoration decoration;

347
  /// The decoration to paint in front of the [child].
348 349 350
  final Decoration foregroundDecoration;

  /// Additional constraints to apply to the child.
351
  ///
352 353 354
  /// The constructor `width` and `height` arguments are combined with the
  /// `constraints` argument to set this property.
  ///
355
  /// The [padding] goes inside the constraints.
356 357
  final BoxConstraints constraints;

358
  /// Empty space to surround the [decoration] and [child].
359
  final EdgeInsetsGeometry margin;
360 361 362 363

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

364 365 366 367 368
  /// The clip behavior when [Container.decoration] has a clipPath.
  ///
  /// Defaults to [Clip.none].
  final Clip clipBehavior;

369
  EdgeInsetsGeometry get _paddingIncludingDecoration {
370 371
    if (decoration == null || decoration.padding == null)
      return padding;
372
    final EdgeInsetsGeometry decorationPadding = decoration.padding;
373 374
    if (padding == null)
      return decorationPadding;
375
    return padding.add(decorationPadding);
376 377 378 379 380 381
  }

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

382
    if (child == null && (constraints == null || !constraints.isTight)) {
383
      current = LimitedBox(
384 385
        maxWidth: 0.0,
        maxHeight: 0.0,
386
        child: ConstrainedBox(constraints: const BoxConstraints.expand()),
387 388
      );
    }
389

390
    if (alignment != null)
391
      current = Align(alignment: alignment, child: current);
392

393
    final EdgeInsetsGeometry effectivePadding = _paddingIncludingDecoration;
394
    if (effectivePadding != null)
395
      current = Padding(padding: effectivePadding, child: current);
396

397 398 399
    if (color != null)
      current = ColoredBox(color: color, child: current);

400
    if (decoration != null)
401
      current = DecoratedBox(decoration: decoration, child: current);
402 403

    if (foregroundDecoration != null) {
404
      current = DecoratedBox(
405 406
        decoration: foregroundDecoration,
        position: DecorationPosition.foreground,
407
        child: current,
408 409 410 411
      );
    }

    if (constraints != null)
412
      current = ConstrainedBox(constraints: constraints, child: current);
413 414

    if (margin != null)
415
      current = Padding(padding: margin, child: current);
416 417

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

420 421 422 423 424 425 426 427 428 429 430
    if (clipBehavior != Clip.none) {
      current = ClipPath(
        clipper: _DecorationClipper(
          textDirection: Directionality.of(context),
          decoration: decoration
        ),
        clipBehavior: clipBehavior,
        child: current,
      );
    }

431 432 433 434
    return current;
  }

  @override
435 436
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
437 438
    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, showName: false, defaultValue: null));
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
439
    properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: Clip.none));
Dan Field's avatar
Dan Field committed
440 441 442 443
    if (color != null)
      properties.add(DiagnosticsProperty<Color>('bg', color));
    else
      properties.add(DiagnosticsProperty<Decoration>('bg', decoration, defaultValue: null));
444 445 446 447
    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));
448 449
  }
}
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472

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