container.dart 17.3 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
///  * [DecoratedSliver], which applies a [Decoration] to a sliver.
56 57 58 59 60
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.
61
  const DecoratedBox({
62
    super.key,
63
    required this.decoration,
64
    this.position = DecorationPosition.background,
65
    super.child,
66
  });
67 68 69 70 71 72 73 74 75 76 77

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

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

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

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

280
  /// The [child] contained by the container.
281
  ///
282 283 284 285
  /// 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.
286
  ///
287
  /// {@macro flutter.widgets.ProxyWidget.child}
288
  final Widget? child;
289

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

306 307
  /// Empty space to inscribe inside the [decoration]. The [child], if any, is
  /// placed inside this padding.
Ian Hickson's avatar
Ian Hickson committed
308 309 310
  ///
  /// This padding is in addition to any padding inherent in the [decoration];
  /// see [Decoration.padding].
311
  final EdgeInsetsGeometry? padding;
312

313 314 315 316 317 318 319 320 321
  /// 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.
322
  final Color? color;
323

324
  /// The decoration to paint behind the [child].
325
  ///
326
  /// Use the [color] property to specify a simple solid color.
327 328 329
  ///
  /// The [child] is not clipped to the decoration. To clip a child to the shape
  /// of a particular [ShapeDecoration], consider using a [ClipPath] widget.
330
  final Decoration? decoration;
331

332
  /// The decoration to paint in front of the [child].
333
  final Decoration? foregroundDecoration;
334 335

  /// Additional constraints to apply to the child.
336
  ///
337 338 339
  /// The constructor `width` and `height` arguments are combined with the
  /// `constraints` argument to set this property.
  ///
340
  /// The [padding] goes inside the constraints.
341
  final BoxConstraints? constraints;
342

343
  /// Empty space to surround the [decoration] and [child].
344
  final EdgeInsetsGeometry? margin;
345 346

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

349 350 351 352 353 354 355 356
  /// 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;
357

358
  /// The clip behavior when [Container.decoration] is not null.
359
  ///
360 361 362 363 364 365
  /// 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].)
366 367
  final Clip clipBehavior;

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

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

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

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

398
    if (color != null) {
399
      current = ColoredBox(color: color!, child: current);
400
    }
401

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

414
    if (decoration != null) {
415
      current = DecoratedBox(decoration: decoration!, child: current);
416
    }
417 418

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

426
    if (constraints != null) {
427
      current = ConstrainedBox(constraints: constraints!, child: current);
428
    }
429

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

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

438
    return current!;
439 440 441
  }

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

/// A clipper that uses [Decoration.getClipPath] to clip.
class _DecorationClipper extends CustomClipper<Path> {
  _DecorationClipper({
462
    TextDirection? textDirection,
463
    required this.decoration,
464
  }) : textDirection = textDirection ?? TextDirection.ltr;
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479

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