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(EnumProperty<DecorationPosition>('position', position, level: DiagnosticLevel.hidden));
107 properties.add(DiagnosticsProperty<Decoration>(label, decoration));
108 }
109}
110
111/// A convenience widget that combines common painting, positioning, and sizing
112/// widgets.
113///
114/// {@youtube 560 315 https://www.youtube.com/watch?v=c1xLMaTUWCY}
115///
116/// A container first surrounds the child with [padding] (inflated by any
117/// borders present in the [decoration]) and then applies additional
118/// [constraints] to the padded extent (incorporating the `width` and `height`
119/// as constraints, if either is non-null). The container is then surrounded by
120/// additional empty space described from the [margin].
121///
122/// During painting, the container first applies the given [transform], then
123/// paints the [decoration] to fill the padded extent, then it paints the child,
124/// and finally paints the [foregroundDecoration], also filling the padded
125/// extent.
126///
127/// Containers with no children try to be as big as possible unless the incoming
128/// constraints are unbounded, in which case they try to be as small as
129/// possible. Containers with children size themselves to their children. The
130/// `width`, `height`, and [constraints] arguments to the constructor override
131/// this.
132///
133/// By default, containers return false for all hit tests. If the [color]
134/// property is specified, the hit testing is handled by [ColoredBox], which
135/// always returns true. If the [decoration] or [foregroundDecoration] properties
136/// are specified, hit testing is handled by [Decoration.hitTest].
137///
138/// ## Layout behavior
139///
140/// _See [BoxConstraints] for an introduction to box layout models._
141///
142/// Since [Container] combines a number of other widgets each with their own
143/// layout behavior, [Container]'s layout behavior is somewhat complicated.
144///
145/// Summary: [Container] tries, in order: to honor [alignment], to size itself
146/// to the [child], to honor the `width`, `height`, and [constraints], to expand
147/// to fit the parent, to be as small as possible.
148///
149/// More specifically:
150///
151/// If the widget has no child, no `height`, no `width`, no [constraints],
152/// and the parent provides unbounded constraints, then [Container] tries to
153/// size as small as possible.
154///
155/// If the widget has no child and no [alignment], but a `height`, `width`, or
156/// [constraints] are provided, then the [Container] tries to be as small as
157/// possible given the combination of those constraints and the parent's
158/// constraints.
159///
160/// If the widget has no child, no `height`, no `width`, no [constraints], and
161/// no [alignment], but the parent provides bounded constraints, then
162/// [Container] expands to fit the constraints provided by the parent.
163///
164/// If the widget has an [alignment], and the parent provides unbounded
165/// constraints, then the [Container] tries to size itself around the child.
166///
167/// If the widget has an [alignment], and the parent provides bounded
168/// constraints, then the [Container] tries to expand to fit the parent, and
169/// then positions the child within itself as per the [alignment].
170///
171/// Otherwise, the widget has a [child] but no `height`, no `width`, no
172/// [constraints], and no [alignment], and the [Container] passes the
173/// constraints from the parent to the child and sizes itself to match the
174/// child.
175///
176/// The [margin] and [padding] properties also affect the layout, as described
177/// in the documentation for those properties. (Their effects merely augment the
178/// rules described above.) The [decoration] can implicitly increase the
179/// [padding] (e.g. borders in a [BoxDecoration] contribute to the [padding]);
180/// see [Decoration.padding].
181///
182/// ## Example
183///
184/// {@tool snippet}
185/// This example shows a 48x48 amber square (placed inside a [Center] widget in
186/// case the parent widget has its own opinions regarding the size that the
187/// [Container] should take), with a margin so that it stays away from
188/// neighboring widgets:
189///
190/// ![An amber colored container with the dimensions of 48 square pixels.](https://flutter.github.io/assets-for-api-docs/assets/widgets/container_a.png)
191///
192/// ```dart
193/// Center(
194/// child: Container(
195/// margin: const EdgeInsets.all(10.0),
196/// color: Colors.amber[600],
197/// width: 48.0,
198/// height: 48.0,
199/// ),
200/// )
201/// ```
202/// {@end-tool}
203///
204/// {@tool snippet}
205///
206/// This example shows how to use many of the features of [Container] at once.
207/// The [constraints] are set to fit the font size plus ample headroom
208/// vertically, while expanding horizontally to fit the parent. The [padding] is
209/// used to make sure there is space between the contents and the text. The
210/// [color] makes the box blue. The [alignment] causes the [child] to be
211/// centered in the box. Finally, the [transform] applies a slight rotation to the
212/// entire contraption to complete the effect.
213///
214/// ![A blue rectangular container with 'Hello World' in the center, rotated
215/// slightly in the z axis.](https://flutter.github.io/assets-for-api-docs/assets/widgets/container_b.png)
216///
217/// ```dart
218/// Container(
219/// constraints: BoxConstraints.expand(
220/// height: Theme.of(context).textTheme.headlineMedium!.fontSize! * 1.1 + 200.0,
221/// ),
222/// padding: const EdgeInsets.all(8.0),
223/// color: Colors.blue[600],
224/// alignment: Alignment.center,
225/// transform: Matrix4.rotationZ(0.1),
226/// child: Text('Hello World',
227/// style: Theme.of(context)
228/// .textTheme
229/// .headlineMedium!
230/// .copyWith(color: Colors.white)),
231/// )
232/// ```
233/// {@end-tool}
234///
235/// See also:
236///
237/// * [AnimatedContainer], a variant that smoothly animates the properties when
238/// they change.
239/// * [Border], which has a sample which uses [Container] heavily.
240/// * [Ink], which paints a [Decoration] on a [Material], allowing
241/// [InkResponse] and [InkWell] splashes to paint over them.
242/// * Cookbook: [Animate the properties of a container](https://docs.flutter.dev/cookbook/animation/animated-container)
243/// * The [catalog of layout widgets](https://docs.flutter.dev/ui/widgets/layout).
244class Container extends StatelessWidget {
245 /// Creates a widget that combines common painting, positioning, and sizing widgets.
246 ///
247 /// The `height` and `width` values include the padding.
248 ///
249 /// The `color` and `decoration` arguments cannot both be supplied, since
250 /// it would potentially result in the decoration drawing over the background
251 /// color. To supply a decoration with a color, use `decoration:
252 /// BoxDecoration(color: color)`.
253 Container({
254 super.key,
255 this.alignment,
256 this.padding,
257 this.color,
258 this.decoration,
259 this.foregroundDecoration,
260 double? width,
261 double? height,
262 BoxConstraints? constraints,
263 this.margin,
264 this.transform,
265 this.transformAlignment,
266 this.child,
267 this.clipBehavior = Clip.none,
268 }) : assert(margin == null || margin.isNonNegative),
269 assert(padding == null || padding.isNonNegative),
270 assert(decoration == null || decoration.debugAssertIsValid()),
271 assert(constraints == null || constraints.debugAssertIsValid()),
272 assert(decoration != null || clipBehavior == Clip.none),
273 assert(color == null || decoration == null,
274 'Cannot provide both a color and a decoration\n'
275 'To provide both, use "decoration: BoxDecoration(color: color)".',
276 ),
277 constraints =
278 (width != null || height != null)
279 ? constraints?.tighten(width: width, height: height)
280 ?? BoxConstraints.tightFor(width: width, height: height)
281 : constraints;
282
283 /// The [child] contained by the container.
284 ///
285 /// If null, and if the [constraints] are unbounded or also null, the
286 /// container will expand to fill all available space in its parent, unless
287 /// the parent provides unbounded constraints, in which case the container
288 /// will attempt to be as small as possible.
289 ///
290 /// {@macro flutter.widgets.ProxyWidget.child}
291 final Widget? child;
292
293 /// Align the [child] within the container.
294 ///
295 /// If non-null, the container will expand to fill its parent and position its
296 /// child within itself according to the given value. If the incoming
297 /// constraints are unbounded, then the child will be shrink-wrapped instead.
298 ///
299 /// Ignored if [child] is null.
300 ///
301 /// See also:
302 ///
303 /// * [Alignment], a class with convenient constants typically used to
304 /// specify an [AlignmentGeometry].
305 /// * [AlignmentDirectional], like [Alignment] for specifying alignments
306 /// relative to text direction.
307 final AlignmentGeometry? alignment;
308
309 /// Empty space to inscribe inside the [decoration]. The [child], if any, is
310 /// placed inside this padding.
311 ///
312 /// This padding is in addition to any padding inherent in the [decoration];
313 /// see [Decoration.padding].
314 final EdgeInsetsGeometry? padding;
315
316 /// The color to paint behind the [child].
317 ///
318 /// This property should be preferred when the background is a simple color.
319 /// For other cases, such as gradients or images, use the [decoration]
320 /// property.
321 ///
322 /// If the [decoration] is used, this property must be null. A background
323 /// color may still be painted by the [decoration] even if this property is
324 /// null.
325 final Color? color;
326
327 /// The decoration to paint behind the [child].
328 ///
329 /// Use the [color] property to specify a simple solid color.
330 ///
331 /// The [child] is not clipped to the decoration. To clip a child to the shape
332 /// of a particular [ShapeDecoration], consider using a [ClipPath] widget.
333 final Decoration? decoration;
334
335 /// The decoration to paint in front of the [child].
336 final Decoration? foregroundDecoration;
337
338 /// Additional constraints to apply to the child.
339 ///
340 /// The constructor `width` and `height` arguments are combined with the
341 /// `constraints` argument to set this property.
342 ///
343 /// The [padding] goes inside the constraints.
344 final BoxConstraints? constraints;
345
346 /// Empty space to surround the [decoration] and [child].
347 final EdgeInsetsGeometry? margin;
348
349 /// The transformation matrix to apply before painting the container.
350 final Matrix4? transform;
351
352 /// The alignment of the origin, relative to the size of the container, if [transform] is specified.
353 ///
354 /// When [transform] is null, the value of this property is ignored.
355 ///
356 /// See also:
357 ///
358 /// * [Transform.alignment], which is set by this property.
359 final AlignmentGeometry? transformAlignment;
360
361 /// The clip behavior when [Container.decoration] is not null.
362 ///
363 /// Defaults to [Clip.none]. Must be [Clip.none] if [decoration] is null.
364 ///
365 /// If a clip is to be applied, the [Decoration.getClipPath] method
366 /// for the provided decoration must return a clip path. (This is not
367 /// supported by all decorations; the default implementation of that
368 /// method throws an [UnsupportedError].)
369 final Clip clipBehavior;
370
371 EdgeInsetsGeometry? get _paddingIncludingDecoration {
372 return switch ((padding, decoration?.padding)) {
373 (null, final EdgeInsetsGeometry? padding) => padding,
374 (final EdgeInsetsGeometry? padding, null) => padding,
375 (_) => padding!.add(decoration!.padding),
376 };
377 }
378
379 @override
380 Widget build(BuildContext context) {
381 Widget? current = child;
382
383 if (child == null && (constraints == null || !constraints!.isTight)) {
384 current = LimitedBox(
385 maxWidth: 0.0,
386 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 current = ClipPath(
405 clipper: _DecorationClipper(
406 textDirection: Directionality.maybeOf(context),
407 decoration: decoration!,
408 ),
409 clipBehavior: clipBehavior,
410 child: current,
411 );
412 }
413
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 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
443 super.debugFillProperties(properties);
444 properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, showName: false, defaultValue: null));
445 properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
446 properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: Clip.none));
447 if (color != null) {
448 properties.add(DiagnosticsProperty<Color>('bg', color));
449 } else {
450 properties.add(DiagnosticsProperty<Decoration>('bg', decoration, defaultValue: null));
451 }
452 properties.add(DiagnosticsProperty<Decoration>('fg', foregroundDecoration, defaultValue: null));
453 properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null));
454 properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('margin', margin, defaultValue: null));
455 properties.add(ObjectFlagProperty<Matrix4>.has('transform', transform));
456 }
457}
458
459/// A clipper that uses [Decoration.getClipPath] to clip.
460class _DecorationClipper extends CustomClipper<Path> {
461 _DecorationClipper({
462 TextDirection? textDirection,
463 required this.decoration,
464 }) : textDirection = textDirection ?? TextDirection.ltr;
465
466 final TextDirection textDirection;
467 final Decoration decoration;
468
469 @override
470 Path getClip(Size size) {
471 return decoration.getClipPath(Offset.zero & size, textDirection);
472 }
473
474 @override
475 bool shouldReclip(_DecorationClipper oldClipper) {
476 return oldClipper.decoration != decoration
477 || oldClipper.textDirection != textDirection;
478 }
479}
480