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 | import 'package:flutter/foundation.dart'; |
6 | import 'package:flutter/rendering.dart'; |
7 | |
8 | import 'basic.dart'; |
9 | import 'framework.dart'; |
10 | import '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. |
56 | class 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/). |
240 | class 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. |
459 | class _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 | |