container.dart 14.8 KB
Newer Older
1 2 3 4
// Copyright 2016 The Chromium Authors. All rights reserved.
// 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 sample}
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 99 100 101 102 103 104 105 106 107 108 109 110
    String label;
    if (position != null) {
      switch (position) {
        case DecorationPosition.background:
          label = 'bg';
          break;
        case DecorationPosition.foreground:
          label = 'fg';
          break;
      }
    } else {
      label = 'decoration';
    }
111 112
    properties.add(EnumProperty<DecorationPosition>('position', position, level: position != null ? DiagnosticLevel.hidden : DiagnosticLevel.info));
    properties.add(DiagnosticsProperty<Decoration>(
113 114 115 116 117
      label,
      decoration,
      ifNull: 'no decoration',
      showName: decoration != null,
    ));
118
  }
119 120 121 122 123 124 125
}

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

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

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

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

316
  /// The decoration to paint behind the [child].
317 318 319 320
  ///
  /// A shorthand for specifying just a solid color is available in the
  /// constructor: set the `color` argument instead of the `decoration`
  /// argument.
321 322 323
  ///
  /// The [child] is not clipped to the decoration. To clip a child to the shape
  /// of a particular [ShapeDecoration], consider using a [ClipPath] widget.
324 325
  final Decoration decoration;

326
  /// The decoration to paint in front of the [child].
327 328 329
  final Decoration foregroundDecoration;

  /// Additional constraints to apply to the child.
330
  ///
331 332 333
  /// The constructor `width` and `height` arguments are combined with the
  /// `constraints` argument to set this property.
  ///
334
  /// The [padding] goes inside the constraints.
335 336
  final BoxConstraints constraints;

337
  /// Empty space to surround the [decoration] and [child].
338
  final EdgeInsetsGeometry margin;
339 340 341 342

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

343
  EdgeInsetsGeometry get _paddingIncludingDecoration {
344 345
    if (decoration == null || decoration.padding == null)
      return padding;
346
    final EdgeInsetsGeometry decorationPadding = decoration.padding;
347 348
    if (padding == null)
      return decorationPadding;
349
    return padding.add(decorationPadding);
350 351 352 353 354 355
  }

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

356
    if (child == null && (constraints == null || !constraints.isTight)) {
357
      current = LimitedBox(
358 359
        maxWidth: 0.0,
        maxHeight: 0.0,
360
        child: ConstrainedBox(constraints: const BoxConstraints.expand()),
361 362
      );
    }
363

364
    if (alignment != null)
365
      current = Align(alignment: alignment, child: current);
366

367
    final EdgeInsetsGeometry effectivePadding = _paddingIncludingDecoration;
368
    if (effectivePadding != null)
369
      current = Padding(padding: effectivePadding, child: current);
370 371

    if (decoration != null)
372
      current = DecoratedBox(decoration: decoration, child: current);
373 374

    if (foregroundDecoration != null) {
375
      current = DecoratedBox(
376 377
        decoration: foregroundDecoration,
        position: DecorationPosition.foreground,
378
        child: current,
379 380 381 382
      );
    }

    if (constraints != null)
383
      current = ConstrainedBox(constraints: constraints, child: current);
384 385

    if (margin != null)
386
      current = Padding(padding: margin, child: current);
387 388

    if (transform != null)
389
      current = Transform(transform: transform, child: current);
390 391 392 393 394

    return current;
  }

  @override
395 396
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
397 398 399 400 401 402 403
    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, showName: false, defaultValue: null));
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
    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));
404 405
  }
}