1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// @docImport 'package:flutter/material.dart';
6///
7/// @docImport 'decorated_sliver.dart';
8/// @docImport 'implicit_animations.dart';
9/// @docImport 'transitions.dart';
10library;
11
12import 'package:flutter/foundation.dart';
13import 'package:flutter/rendering.dart';
14
15import 'basic.dart';
16import 'framework.dart';
17import 'image.dart';
18
19// Examples can assume:
20// late BuildContext context;
21
22/// A widget that paints a [Decoration] either before or after its child paints.
23///
24/// [Container] insets its child by the widths of the borders; this widget does
25/// not.
26///
27/// Commonly used with [BoxDecoration].
28///
29/// The [child] is not clipped. To clip a child to the shape of a particular
30/// [ShapeDecoration], consider using a [ClipPath] widget.
31///
32/// {@tool snippet}
33///
34/// This sample shows a radial gradient that draws a moon on a night sky:
35///
36/// ```dart
37/// const DecoratedBox(
38/// decoration: BoxDecoration(
39/// gradient: RadialGradient(
40/// center: Alignment(-0.5, -0.6),
41/// radius: 0.15,
42/// colors: <Color>[
43/// Color(0xFFEEEEEE),
44/// Color(0xFF111133),
45/// ],
46/// stops: <double>[0.9, 1.0],
47/// ),
48/// ),
49/// )
50/// ```
51/// {@end-tool}
52///
53/// See also:
54///
55/// * [Ink], which paints a [Decoration] on a [Material], allowing
56/// [InkResponse] and [InkWell] splashes to paint over them.
57/// * [DecoratedBoxTransition], the version of this class that animates on the
58/// [decoration] property.
59/// * [Decoration], which you can extend to provide other effects with
60/// [DecoratedBox].
61/// * [CustomPaint], another way to draw custom effects from the widget layer.
62/// * [DecoratedSliver], which applies a [Decoration] to a sliver.
63class DecoratedBox extends SingleChildRenderObjectWidget {
64 /// Creates a widget that paints a [Decoration].
65 ///
66 /// By default the decoration paints behind the child.
67 const DecoratedBox({
68 super.key,
69 required this.decoration,
70 this.position = DecorationPosition.background,
71 super.child,
72 });
73
74 /// What decoration to paint.
75 ///
76 /// Commonly a [BoxDecoration].
77 final Decoration decoration;
78
79 /// Whether to paint the box decoration behind or in front of the child.
80 final DecorationPosition position;
81
82 @override
83 RenderDecoratedBox createRenderObject(BuildContext context) {
84 return RenderDecoratedBox(
85 decoration: decoration,
86 position: position,
87 configuration: createLocalImageConfiguration(context),
88 );
89 }
90
91 @override
92 void updateRenderObject(BuildContext context, RenderDecoratedBox renderObject) {
93 renderObject
94 ..decoration = decoration
95 ..configuration = createLocalImageConfiguration(context)
96 ..position = position;
97 }
98
99 @override
100 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
101 super.debugFillProperties(properties);
102 final String label = switch (position) {
103 DecorationPosition.background => 'bg',
104 DecorationPosition.foreground => 'fg',
105 };
106 properties.add(
107 EnumProperty<DecorationPosition>('position', position, level: DiagnosticLevel.hidden),
108 );
109 properties.add(DiagnosticsProperty<Decoration>(label, decoration));
110 }
111}
112
113/// A convenience widget that combines common painting, positioning, and sizing
114/// widgets.
115///
116/// {@youtube 560 315 https://www.youtube.com/watch?v=c1xLMaTUWCY}
117///
118/// A container first surrounds the child with [padding] (inflated by any
119/// borders present in the [decoration]) and then applies additional
120/// [constraints] to the padded extent (incorporating the `width` and `height`
121/// as constraints, if either is non-null). The container is then surrounded by
122/// additional empty space described from the [margin].
123///
124/// During painting, the container first applies the given [transform], then
125/// paints the [decoration] to fill the padded extent, then it paints the child,
126/// and finally paints the [foregroundDecoration], also filling the padded
127/// extent.
128///
129/// Containers with no children try to be as big as possible unless the incoming
130/// constraints are unbounded, in which case they try to be as small as
131/// possible. Containers with children size themselves to their children. The
132/// `width`, `height`, and [constraints] arguments to the constructor override
133/// this.
134///
135/// By default, containers return false for all hit tests. If the [color]
136/// property is specified, the hit testing is handled by [ColoredBox], which
137/// always returns true. If the [decoration] or [foregroundDecoration] properties
138/// are specified, hit testing is handled by [Decoration.hitTest].
139///
140/// ## Layout behavior
141///
142/// _See [BoxConstraints] for an introduction to box layout models._
143///
144/// Since [Container] combines a number of other widgets each with their own
145/// layout behavior, [Container]'s layout behavior is somewhat complicated.
146///
147/// Summary: [Container] tries, in order: to honor [alignment], to size itself
148/// to the [child], to honor the `width`, `height`, and [constraints], to expand
149/// to fit the parent, to be as small as possible.
150///
151/// More specifically:
152///
153/// If the widget has no child, no `height`, no `width`, no [constraints],
154/// and the parent provides unbounded constraints, then [Container] tries to
155/// size as small as possible.
156///
157/// If the widget has no child and no [alignment], but a `height`, `width`, or
158/// [constraints] are provided, then the [Container] tries to be as small as
159/// possible given the combination of those constraints and the parent's
160/// constraints.
161///
162/// If the widget has no child, no `height`, no `width`, no [constraints], and
163/// no [alignment], but the parent provides bounded constraints, then
164/// [Container] expands to fit the constraints provided by the parent.
165///
166/// If the widget has an [alignment], and the parent provides unbounded
167/// constraints, then the [Container] tries to size itself around the child.
168///
169/// If the widget has an [alignment], and the parent provides bounded
170/// constraints, then the [Container] tries to expand to fit the parent, and
171/// then positions the child within itself as per the [alignment].
172///
173/// Otherwise, the widget has a [child] but no `height`, no `width`, no
174/// [constraints], and no [alignment], and the [Container] passes the
175/// constraints from the parent to the child and sizes itself to match the
176/// child.
177///
178/// The [margin] and [padding] properties also affect the layout, as described
179/// in the documentation for those properties. (Their effects merely augment the
180/// rules described above.) The [decoration] can implicitly increase the
181/// [padding] (e.g. borders in a [BoxDecoration] contribute to the [padding]);
182/// see [Decoration.padding].
183///
184/// ## Example
185///
186/// {@tool snippet}
187/// This example shows a 48x48 amber square (placed inside a [Center] widget in
188/// case the parent widget has its own opinions regarding the size that the
189/// [Container] should take), with a margin so that it stays away from
190/// 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)
193///
194/// ```dart
195/// Center(
196/// child: Container(
197/// margin: const EdgeInsets.all(10.0),
198/// color: Colors.amber[600],
199/// width: 48.0,
200/// height: 48.0,
201/// ),
202/// )
203/// ```
204/// {@end-tool}
205///
206/// {@tool snippet}
207///
208/// This example shows how to use many of the features of [Container] at once.
209/// The [constraints] are set to fit the font size plus ample headroom
210/// vertically, while expanding horizontally to fit the parent. The [padding] is
211/// used to make sure there is space between the contents and the text. The
212/// [color] makes the box blue. The [alignment] causes the [child] to be
213/// centered in the box. Finally, the [transform] applies a slight rotation to the
214/// entire contraption to complete the effect.
215///
216/// ![A blue rectangular container with 'Hello World' in the center, rotated
217/// slightly in the z axis.](https://flutter.github.io/assets-for-api-docs/assets/widgets/container_b.png)
218///
219/// ```dart
220/// Container(
221/// constraints: BoxConstraints.expand(
222/// height: Theme.of(context).textTheme.headlineMedium!.fontSize! * 1.1 + 200.0,
223/// ),
224/// padding: const EdgeInsets.all(8.0),
225/// color: Colors.blue[600],
226/// alignment: Alignment.center,
227/// transform: Matrix4.rotationZ(0.1),
228/// child: Text('Hello World',
229/// style: Theme.of(context)
230/// .textTheme
231/// .headlineMedium!
232/// .copyWith(color: Colors.white)),
233/// )
234/// ```
235/// {@end-tool}
236///
237/// See also:
238///
239/// * [AnimatedContainer], a variant that smoothly animates the properties when
240/// they change.
241/// * [Border], which has a sample which uses [Container] heavily.
242/// * [Ink], which paints a [Decoration] on a [Material], allowing
243/// [InkResponse] and [InkWell] splashes to paint over them.
244/// * Cookbook: [Animate the properties of a container](https://docs.flutter.dev/cookbook/animation/animated-container)
245/// * The [catalog of layout widgets](https://docs.flutter.dev/ui/widgets/layout).
246class Container extends StatelessWidget {
247 /// Creates a widget that combines common painting, positioning, and sizing widgets.
248 ///
249 /// The `height` and `width` values include the padding.
250 ///
251 /// The `color` and `decoration` arguments cannot both be supplied, since
252 /// it would potentially result in the decoration drawing over the background
253 /// color. To supply a decoration with a color, use `decoration:
254 /// BoxDecoration(color: color)`.
255 Container({
256 super.key,
257 this.alignment,
258 this.padding,
259 this.color,
260 this.decoration,
261 this.foregroundDecoration,
262 double? width,
263 double? height,
264 BoxConstraints? constraints,
265 this.margin,
266 this.transform,
267 this.transformAlignment,
268 this.child,
269 this.clipBehavior = Clip.none,
270 }) : assert(margin == null || margin.isNonNegative),
271 assert(padding == null || padding.isNonNegative),
272 assert(decoration == null || decoration.debugAssertIsValid()),
273 assert(constraints == null || constraints.debugAssertIsValid()),
274 assert(decoration != null || clipBehavior == Clip.none),
275 assert(
276 color == null || decoration == null,
277 'Cannot provide both a color and a decoration\n'
278 'To provide both, use "decoration: BoxDecoration(color: color)".',
279 ),
280 constraints = (width != null || height != null)
281 ? constraints?.tighten(width: width, height: height) ??
282 BoxConstraints.tightFor(width: width, height: height)
283 : constraints;
284
285 /// The [child] contained by the container.
286 ///
287 /// If null, and if the [constraints] are unbounded or also null, the
288 /// container will expand to fill all available space in its parent, unless
289 /// the parent provides unbounded constraints, in which case the container
290 /// 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 /// child within itself according to the given value. If the incoming
299 /// constraints are unbounded, then the child will be shrink-wrapped instead.
300 ///
301 /// Ignored if [child] is null.
302 ///
303 /// See also:
304 ///
305 /// * [Alignment], a class with convenient constants typically used to
306 /// specify an [AlignmentGeometry].
307 /// * [AlignmentDirectional], like [Alignment] for specifying alignments
308 /// relative to text direction.
309 final AlignmentGeometry? alignment;
310
311 /// Empty space to inscribe inside the [decoration]. The [child], if any, is
312 /// placed inside this padding.
313 ///
314 /// This padding is in addition to any padding inherent in the [decoration];
315 /// see [Decoration.padding].
316 final EdgeInsetsGeometry? padding;
317
318 /// The color to paint behind the [child].
319 ///
320 /// This property should be preferred when the background is a simple color.
321 /// For other cases, such as gradients or images, use the [decoration]
322 /// property.
323 ///
324 /// If the [decoration] is used, this property must be null. A background
325 /// color may still be painted by the [decoration] even if this property is
326 /// 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 /// The [child] is not clipped to the decoration. To clip a child to the shape
334 /// 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 /// The constructor `width` and `height` arguments are combined with the
343 /// `constraints` argument to set this property.
344 ///
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 /// The alignment of the origin, relative to the size of the container, if [transform] is specified.
355 ///
356 /// When [transform] is null, the value of this property is ignored.
357 ///
358 /// See also:
359 ///
360 /// * [Transform.alignment], which is set by this property.
361 final AlignmentGeometry? transformAlignment;
362
363 /// The clip behavior when [Container.decoration] is not null.
364 ///
365 /// Defaults to [Clip.none]. Must be [Clip.none] if [decoration] is null.
366 ///
367 /// If a clip is to be applied, the [Decoration.getClipPath] method
368 /// for the provided decoration must return a clip path. (This is not
369 /// supported by all decorations; the default implementation of that
370 /// method throws an [UnsupportedError].)
371 final Clip clipBehavior;
372
373 EdgeInsetsGeometry? get _paddingIncludingDecoration {
374 return switch ((padding, decoration?.padding)) {
375 (null, final EdgeInsetsGeometry? padding) => padding,
376 (final EdgeInsetsGeometry? padding, null) => padding,
377 (_) => padding!.add(decoration!.padding),
378 };
379 }
380
381 @override
382 Widget build(BuildContext context) {
383 Widget? current = child;
384
385 if (child == null && (constraints == null || !constraints!.isTight)) {
386 current = LimitedBox(
387 maxWidth: 0.0,
388 maxHeight: 0.0,
389 child: ConstrainedBox(constraints: const BoxConstraints.expand()),
390 );
391 } else if (alignment != null) {
392 current = Align(alignment: alignment!, child: current);
393 }
394
395 final EdgeInsetsGeometry? effectivePadding = _paddingIncludingDecoration;
396 if (effectivePadding != null) {
397 current = Padding(padding: effectivePadding, child: current);
398 }
399
400 if (color != null) {
401 current = ColoredBox(color: color!, child: current);
402 }
403
404 if (clipBehavior != Clip.none) {
405 assert(decoration != null);
406 current = ClipPath(
407 clipper: _DecorationClipper(
408 textDirection: Directionality.maybeOf(context),
409 decoration: decoration!,
410 ),
411 clipBehavior: clipBehavior,
412 child: current,
413 );
414 }
415
416 if (decoration != null) {
417 current = DecoratedBox(decoration: decoration!, child: current);
418 }
419
420 if (foregroundDecoration != null) {
421 current = DecoratedBox(
422 decoration: foregroundDecoration!,
423 position: DecorationPosition.foreground,
424 child: current,
425 );
426 }
427
428 if (constraints != null) {
429 current = ConstrainedBox(constraints: constraints!, child: current);
430 }
431
432 if (margin != null) {
433 current = Padding(padding: margin!, child: current);
434 }
435
436 if (transform != null) {
437 current = Transform(transform: transform!, alignment: transformAlignment, child: current);
438 }
439
440 return current!;
441 }
442
443 @override
444 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
445 super.debugFillProperties(properties);
446 properties.add(
447 DiagnosticsProperty<AlignmentGeometry>(
448 'alignment',
449 alignment,
450 showName: false,
451 defaultValue: null,
452 ),
453 );
454 properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
455 properties.add(
456 DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: Clip.none),
457 );
458 if (color != null) {
459 properties.add(DiagnosticsProperty<Color>('bg', color));
460 } else {
461 properties.add(DiagnosticsProperty<Decoration>('bg', decoration, defaultValue: null));
462 }
463 properties.add(DiagnosticsProperty<Decoration>('fg', foregroundDecoration, defaultValue: null));
464 properties.add(
465 DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null),
466 );
467 properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('margin', margin, defaultValue: null));
468 properties.add(ObjectFlagProperty<Matrix4>.has('transform', transform));
469 }
470}
471
472/// A clipper that uses [Decoration.getClipPath] to clip.
473class _DecorationClipper extends CustomClipper<Path> {
474 _DecorationClipper({TextDirection? textDirection, required this.decoration})
475 : textDirection = textDirection ?? TextDirection.ltr;
476
477 final TextDirection textDirection;
478 final Decoration decoration;
479
480 @override
481 Path getClip(Size size) {
482 return decoration.getClipPath(Offset.zero & size, textDirection);
483 }
484
485 @override
486 bool shouldReclip(_DecorationClipper oldClipper) {
487 return oldClipper.decoration != decoration || oldClipper.textDirection != textDirection;
488 }
489}
490