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