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

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

12
// Examples can assume:
13
// late BuildContext context;
14

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

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

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

  @override
95 96
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
97
    final String label;
98 99 100 101 102 103 104
    switch (position) {
      case DecorationPosition.background:
        label = 'bg';
        break;
      case DecorationPosition.foreground:
        label = 'fg';
        break;
105
    }
106
    properties.add(EnumProperty<DecorationPosition>('position', position, level: DiagnosticLevel.hidden));
107
    properties.add(DiagnosticsProperty<Decoration>(label, decoration));
108
  }
109 110 111 112 113
}

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

285
  /// The [child] contained by the container.
286
  ///
287 288 289 290
  /// 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.
291
  ///
292
  /// {@macro flutter.widgets.ProxyWidget.child}
293
  final Widget? child;
294

295
  /// Align the [child] within the container.
296 297
  ///
  /// If non-null, the container will expand to fill its parent and position its
298 299 300 301
  /// 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.
302 303 304 305 306 307 308
  ///
  /// See also:
  ///
  ///  * [Alignment], a class with convenient constants typically used to
  ///    specify an [AlignmentGeometry].
  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
  ///    relative to text direction.
309
  final AlignmentGeometry? alignment;
310

311 312
  /// Empty space to inscribe inside the [decoration]. The [child], if any, is
  /// placed inside this padding.
Ian Hickson's avatar
Ian Hickson committed
313 314 315
  ///
  /// This padding is in addition to any padding inherent in the [decoration];
  /// see [Decoration.padding].
316
  final EdgeInsetsGeometry? padding;
317

318 319 320 321 322 323 324 325 326
  /// 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.
327
  final Color? color;
328

329
  /// The decoration to paint behind the [child].
330
  ///
331
  /// Use the [color] property to specify a simple solid color.
332 333 334
  ///
  /// The [child] is not clipped to the decoration. To clip a child to the shape
  /// of a particular [ShapeDecoration], consider using a [ClipPath] widget.
335
  final Decoration? decoration;
336

337
  /// The decoration to paint in front of the [child].
338
  final Decoration? foregroundDecoration;
339 340

  /// Additional constraints to apply to the child.
341
  ///
342 343 344
  /// The constructor `width` and `height` arguments are combined with the
  /// `constraints` argument to set this property.
  ///
345
  /// The [padding] goes inside the constraints.
346
  final BoxConstraints? constraints;
347

348
  /// Empty space to surround the [decoration] and [child].
349
  final EdgeInsetsGeometry? margin;
350 351

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

354 355 356 357 358 359 360 361
  /// The alignment of the origin, relative to the size of the container, if [transform] is specified.
  ///
  /// When [transform] is null, the value of this property is ignored.
  ///
  /// See also:
  ///
  ///  * [Transform.alignment], which is set by this property.
  final AlignmentGeometry? transformAlignment;
362

363
  /// The clip behavior when [Container.decoration] is not null.
364
  ///
365 366 367 368 369 370
  /// Defaults to [Clip.none]. Must be [Clip.none] if [decoration] is null.
  ///
  /// If a clip is to be applied, the [Decoration.getClipPath] method
  /// for the provided decoration must return a clip path. (This is not
  /// supported by all decorations; the default implementation of that
  /// method throws an [UnsupportedError].)
371 372
  final Clip clipBehavior;

373 374
  EdgeInsetsGeometry? get _paddingIncludingDecoration {
    if (decoration == null || decoration!.padding == null)
375
      return padding;
376
    final EdgeInsetsGeometry? decorationPadding = decoration!.padding;
377 378
    if (padding == null)
      return decorationPadding;
379
    return padding!.add(decorationPadding!);
380 381 382 383
  }

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

386
    if (child == null && (constraints == null || !constraints!.isTight)) {
387
      current = LimitedBox(
388 389
        maxWidth: 0.0,
        maxHeight: 0.0,
390
        child: ConstrainedBox(constraints: const BoxConstraints.expand()),
391 392
      );
    }
393

394
    if (alignment != null)
395
      current = Align(alignment: alignment!, child: current);
396

397
    final EdgeInsetsGeometry? effectivePadding = _paddingIncludingDecoration;
398
    if (effectivePadding != null)
399
      current = Padding(padding: effectivePadding, child: current);
400

401
    if (color != null)
402
      current = ColoredBox(color: color!, child: current);
403

404
    if (clipBehavior != Clip.none) {
405
      assert(decoration != null);
406 407
      current = ClipPath(
        clipper: _DecorationClipper(
408
          textDirection: Directionality.maybeOf(context),
409
          decoration: decoration!,
410 411 412 413 414 415
        ),
        clipBehavior: clipBehavior,
        child: current,
      );
    }

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

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

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

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

    if (transform != null)
434
      current = Transform(transform: transform!, alignment: transformAlignment, child: current);
435

436
    return current!;
437 438 439
  }

  @override
440 441
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
442 443
    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, showName: false, defaultValue: null));
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
444
    properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: Clip.none));
Dan Field's avatar
Dan Field committed
445 446 447 448
    if (color != null)
      properties.add(DiagnosticsProperty<Color>('bg', color));
    else
      properties.add(DiagnosticsProperty<Decoration>('bg', decoration, defaultValue: null));
449 450 451 452
    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));
453 454
  }
}
455 456 457 458

/// A clipper that uses [Decoration.getClipPath] to clip.
class _DecorationClipper extends CustomClipper<Path> {
  _DecorationClipper({
459
    TextDirection? textDirection,
460
    required this.decoration,
461
  }) : assert(decoration != null),
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477
       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;
  }
}