container.dart 17.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
/// See also:
///
Ian Hickson's avatar
Ian Hickson committed
49 50
///  * [Ink], which paints a [Decoration] on a [Material], allowing
///    [InkResponse] and [InkWell] splashes to paint over them.
51 52 53 54 55
///  * [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.
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 63
    Key? key,
    required this.decoration,
64
    this.position = DecorationPosition.background,
65
    Widget? child,
66 67 68
  }) : assert(decoration != null),
       assert(position != null),
       super(key: key, child: child);
69 70 71 72 73 74 75 76 77 78 79

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

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

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

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

291
  /// The [child] contained by the container.
292
  ///
293 294 295 296
  /// 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.
297 298
  ///
  /// {@macro flutter.widgets.child}
299
  final Widget? child;
300

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

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

324 325 326 327 328 329 330 331 332
  /// 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.
333
  final Color? color;
334

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

343
  /// The decoration to paint in front of the [child].
344
  final Decoration? foregroundDecoration;
345 346

  /// Additional constraints to apply to the child.
347
  ///
348 349 350
  /// The constructor `width` and `height` arguments are combined with the
  /// `constraints` argument to set this property.
  ///
351
  /// The [padding] goes inside the constraints.
352
  final BoxConstraints? constraints;
353

354
  /// Empty space to surround the [decoration] and [child].
355
  final EdgeInsetsGeometry? margin;
356 357

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

360 361 362 363 364 365 366 367 368
 /// 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 Alignment? transformAlignment;

369
  /// The clip behavior when [Container.decoration] is not null.
370
  ///
371 372 373 374 375 376
  /// 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].)
377 378
  final Clip clipBehavior;

379 380
  EdgeInsetsGeometry? get _paddingIncludingDecoration {
    if (decoration == null || decoration!.padding == null)
381
      return padding;
382
    final EdgeInsetsGeometry? decorationPadding = decoration!.padding;
383 384
    if (padding == null)
      return decorationPadding;
385
    return padding!.add(decorationPadding!);
386 387 388 389
  }

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

392
    if (child == null && (constraints == null || !constraints!.isTight)) {
393
      current = LimitedBox(
394 395
        maxWidth: 0.0,
        maxHeight: 0.0,
396
        child: ConstrainedBox(constraints: const BoxConstraints.expand()),
397 398
      );
    }
399

400
    if (alignment != null)
401
      current = Align(alignment: alignment!, child: current);
402

403
    final EdgeInsetsGeometry? effectivePadding = _paddingIncludingDecoration;
404
    if (effectivePadding != null)
405
      current = Padding(padding: effectivePadding, child: current);
406

407
    if (color != null)
408
      current = ColoredBox(color: color!, child: current);
409

410
    if (clipBehavior != Clip.none) {
411
      assert(decoration != null);
412 413 414
      current = ClipPath(
        clipper: _DecorationClipper(
          textDirection: Directionality.of(context),
415
          decoration: decoration!,
416 417 418 419 420 421
        ),
        clipBehavior: clipBehavior,
        child: current,
      );
    }

422
    if (decoration != null)
423
      current = DecoratedBox(decoration: decoration!, child: current);
424 425

    if (foregroundDecoration != null) {
426
      current = DecoratedBox(
427
        decoration: foregroundDecoration!,
428
        position: DecorationPosition.foreground,
429
        child: current,
430 431 432 433
      );
    }

    if (constraints != null)
434
      current = ConstrainedBox(constraints: constraints!, child: current);
435 436

    if (margin != null)
437
      current = Padding(padding: margin!, child: current);
438 439

    if (transform != null)
440
      current = Transform(transform: transform!, child: current, alignment: transformAlignment);
441

442
    return current!;
443 444 445
  }

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

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