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'; |
10 | library; |
11 | |
12 | import 'package:flutter/foundation.dart'; |
13 | import 'package:flutter/rendering.dart'; |
14 | |
15 | import 'basic.dart'; |
16 | import 'framework.dart'; |
17 | import '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. |
63 | class 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). |
244 | class 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. |
460 | class _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 | |