// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'basic.dart'; import 'framework.dart'; import 'image.dart'; // Examples can assume: // late BuildContext context; /// A widget that paints a [Decoration] either before or after its child paints. /// /// [Container] insets its child by the widths of the borders; this widget does /// not. /// /// Commonly used with [BoxDecoration]. /// /// The [child] is not clipped. To clip a child to the shape of a particular /// [ShapeDecoration], consider using a [ClipPath] widget. /// /// {@tool snippet} /// /// This sample shows a radial gradient that draws a moon on a night sky: /// /// ```dart /// const DecoratedBox( /// decoration: BoxDecoration( /// gradient: RadialGradient( /// center: Alignment(-0.5, -0.6), /// radius: 0.15, /// colors: <Color>[ /// Color(0xFFEEEEEE), /// Color(0xFF111133), /// ], /// stops: <double>[0.9, 1.0], /// ), /// ), /// ) /// ``` /// {@end-tool} /// /// See also: /// /// * [Ink], which paints a [Decoration] on a [Material], allowing /// [InkResponse] and [InkWell] splashes to paint over them. /// * [DecoratedBoxTransition], the version of this class that animates on the /// [decoration] property. /// * [Decoration], which you can extend to provide other effects with /// [DecoratedBox]. /// * [CustomPaint], another way to draw custom effects from the widget layer. class DecoratedBox extends SingleChildRenderObjectWidget { /// Creates a widget that paints a [Decoration]. /// /// The [decoration] and [position] arguments must not be null. By default the /// decoration paints behind the child. const DecoratedBox({ super.key, required this.decoration, this.position = DecorationPosition.background, super.child, }) : assert(decoration != null), assert(position != null); /// What decoration to paint. /// /// Commonly a [BoxDecoration]. final Decoration decoration; /// Whether to paint the box decoration behind or in front of the child. final DecorationPosition position; @override RenderDecoratedBox createRenderObject(BuildContext context) { return RenderDecoratedBox( decoration: decoration, position: position, configuration: createLocalImageConfiguration(context), ); } @override void updateRenderObject(BuildContext context, RenderDecoratedBox renderObject) { renderObject ..decoration = decoration ..configuration = createLocalImageConfiguration(context) ..position = position; } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); final String label; switch (position) { case DecorationPosition.background: label = 'bg'; break; case DecorationPosition.foreground: label = 'fg'; break; } properties.add(EnumProperty<DecorationPosition>('position', position, level: DiagnosticLevel.hidden)); properties.add(DiagnosticsProperty<Decoration>(label, decoration)); } } /// A convenience widget that combines common painting, positioning, and sizing /// widgets. /// /// {@youtube 560 315 https://www.youtube.com/watch?v=c1xLMaTUWCY} /// /// A container first surrounds the child with [padding] (inflated by any /// borders present in the [decoration]) and then applies additional /// [constraints] to the padded extent (incorporating the `width` and `height` /// as constraints, if either is non-null). The container is then surrounded by /// additional empty space described from the [margin]. /// /// During painting, the container first applies the given [transform], then /// paints the [decoration] to fill the padded extent, then it paints the child, /// and finally paints the [foregroundDecoration], also filling the padded /// extent. /// /// Containers with no children try to be as big as possible unless the incoming /// constraints are unbounded, in which case they try to be as small as /// possible. Containers with children size themselves to their children. The /// `width`, `height`, and [constraints] arguments to the constructor override /// this. /// /// By default, containers return false for all hit tests. If the [color] /// property is specified, the hit testing is handled by [ColoredBox], which /// always returns true. If the [decoration] or [foregroundDecoration] properties /// are specified, hit testing is handled by [Decoration.hitTest]. /// /// ## Layout behavior /// /// _See [BoxConstraints] for an introduction to box layout models._ /// /// Since [Container] combines a number of other widgets each with their own /// layout behavior, [Container]'s layout behavior is somewhat complicated. /// /// Summary: [Container] tries, in order: to honor [alignment], to size itself /// to the [child], to honor the `width`, `height`, and [constraints], to expand /// to fit the parent, to be as small as possible. /// /// More specifically: /// /// If the widget has no child, no `height`, no `width`, no [constraints], /// and the parent provides unbounded constraints, then [Container] tries to /// size as small as possible. /// /// If the widget has no child and no [alignment], but a `height`, `width`, or /// [constraints] are provided, then the [Container] tries to be as small as /// possible given the combination of those constraints and the parent's /// constraints. /// /// If the widget has no child, no `height`, no `width`, no [constraints], and /// no [alignment], but the parent provides bounded constraints, then /// [Container] expands to fit the constraints provided by the parent. /// /// If the widget has an [alignment], and the parent provides unbounded /// constraints, then the [Container] tries to size itself around the child. /// /// If the widget has an [alignment], and the parent provides bounded /// constraints, then the [Container] tries to expand to fit the parent, and /// then positions the child within itself as per the [alignment]. /// /// Otherwise, the widget has a [child] but no `height`, no `width`, no /// [constraints], and no [alignment], and the [Container] passes the /// constraints from the parent to the child and sizes itself to match the /// child. /// /// The [margin] and [padding] properties also affect the layout, as described /// in the documentation for those properties. (Their effects merely augment the /// rules described above.) The [decoration] can implicitly increase the /// [padding] (e.g. borders in a [BoxDecoration] contribute to the [padding]); /// see [Decoration.padding]. /// /// ## Example /// /// {@tool snippet} /// This example shows a 48x48 amber square (placed inside a [Center] widget in /// case the parent widget has its own opinions regarding the size that the /// [Container] should take), with a margin so that it stays away from /// neighboring widgets: /// /// ![An amber colored container with the dimensions of 48 square pixels.](https://flutter.github.io/assets-for-api-docs/assets/widgets/container_a.png) /// /// ```dart /// Center( /// child: Container( /// margin: const EdgeInsets.all(10.0), /// color: Colors.amber[600], /// width: 48.0, /// height: 48.0, /// ), /// ) /// ``` /// {@end-tool} /// /// {@tool snippet} /// /// This example shows how to use many of the features of [Container] at once. /// The [constraints] are set to fit the font size plus ample headroom /// vertically, while expanding horizontally to fit the parent. The [padding] is /// used to make sure there is space between the contents and the text. The /// [color] makes the box blue. The [alignment] causes the [child] to be /// centered in the box. Finally, the [transform] applies a slight rotation to the /// entire contraption to complete the effect. /// /// ![A blue rectangular container with 'Hello World' in the center, rotated /// slightly in the z axis.](https://flutter.github.io/assets-for-api-docs/assets/widgets/container_b.png) /// /// ```dart /// Container( /// constraints: BoxConstraints.expand( /// height: Theme.of(context).textTheme.headlineMedium!.fontSize! * 1.1 + 200.0, /// ), /// padding: const EdgeInsets.all(8.0), /// color: Colors.blue[600], /// alignment: Alignment.center, /// transform: Matrix4.rotationZ(0.1), /// child: Text('Hello World', /// style: Theme.of(context) /// .textTheme /// .headlineMedium! /// .copyWith(color: Colors.white)), /// ) /// ``` /// {@end-tool} /// /// See also: /// /// * [AnimatedContainer], a variant that smoothly animates the properties when /// they change. /// * [Border], which has a sample which uses [Container] heavily. /// * [Ink], which paints a [Decoration] on a [Material], allowing /// [InkResponse] and [InkWell] splashes to paint over them. /// * Cookbook: [Animate the properties of a container](https://flutter.dev/docs/cookbook/animation/animated-container) /// * The [catalog of layout widgets](https://flutter.dev/widgets/layout/). class Container extends StatelessWidget { /// Creates a widget that combines common painting, positioning, and sizing widgets. /// /// The `height` and `width` values include the padding. /// /// The `color` and `decoration` arguments cannot both be supplied, since /// it would potentially result in the decoration drawing over the background /// color. To supply a decoration with a color, use `decoration: /// BoxDecoration(color: color)`. Container({ super.key, this.alignment, this.padding, this.color, this.decoration, this.foregroundDecoration, double? width, double? height, BoxConstraints? constraints, this.margin, this.transform, this.transformAlignment, this.child, this.clipBehavior = Clip.none, }) : assert(margin == null || margin.isNonNegative), assert(padding == null || padding.isNonNegative), assert(decoration == null || decoration.debugAssertIsValid()), assert(constraints == null || constraints.debugAssertIsValid()), assert(clipBehavior != null), assert(decoration != null || clipBehavior == Clip.none), assert(color == null || decoration == null, 'Cannot provide both a color and a decoration\n' 'To provide both, use "decoration: BoxDecoration(color: color)".', ), constraints = (width != null || height != null) ? constraints?.tighten(width: width, height: height) ?? BoxConstraints.tightFor(width: width, height: height) : constraints; /// The [child] contained by the container. /// /// If null, and if the [constraints] are unbounded or also null, the /// container will expand to fill all available space in its parent, unless /// the parent provides unbounded constraints, in which case the container /// will attempt to be as small as possible. /// /// {@macro flutter.widgets.ProxyWidget.child} final Widget? child; /// Align the [child] within the container. /// /// If non-null, the container will expand to fill its parent and position its /// child within itself according to the given value. If the incoming /// constraints are unbounded, then the child will be shrink-wrapped instead. /// /// Ignored if [child] is null. /// /// See also: /// /// * [Alignment], a class with convenient constants typically used to /// specify an [AlignmentGeometry]. /// * [AlignmentDirectional], like [Alignment] for specifying alignments /// relative to text direction. final AlignmentGeometry? alignment; /// Empty space to inscribe inside the [decoration]. The [child], if any, is /// placed inside this padding. /// /// This padding is in addition to any padding inherent in the [decoration]; /// see [Decoration.padding]. final EdgeInsetsGeometry? padding; /// The color to paint behind the [child]. /// /// This property should be preferred when the background is a simple color. /// For other cases, such as gradients or images, use the [decoration] /// property. /// /// If the [decoration] is used, this property must be null. A background /// color may still be painted by the [decoration] even if this property is /// null. final Color? color; /// The decoration to paint behind the [child]. /// /// Use the [color] property to specify a simple solid color. /// /// The [child] is not clipped to the decoration. To clip a child to the shape /// of a particular [ShapeDecoration], consider using a [ClipPath] widget. final Decoration? decoration; /// The decoration to paint in front of the [child]. final Decoration? foregroundDecoration; /// Additional constraints to apply to the child. /// /// The constructor `width` and `height` arguments are combined with the /// `constraints` argument to set this property. /// /// The [padding] goes inside the constraints. final BoxConstraints? constraints; /// Empty space to surround the [decoration] and [child]. final EdgeInsetsGeometry? margin; /// The transformation matrix to apply before painting the container. final Matrix4? transform; /// The alignment of the origin, relative to the size of the container, if [transform] is specified. /// /// When [transform] is null, the value of this property is ignored. /// /// See also: /// /// * [Transform.alignment], which is set by this property. final AlignmentGeometry? transformAlignment; /// The clip behavior when [Container.decoration] is not null. /// /// Defaults to [Clip.none]. Must be [Clip.none] if [decoration] is null. /// /// If a clip is to be applied, the [Decoration.getClipPath] method /// for the provided decoration must return a clip path. (This is not /// supported by all decorations; the default implementation of that /// method throws an [UnsupportedError].) final Clip clipBehavior; EdgeInsetsGeometry? get _paddingIncludingDecoration { if (decoration == null || decoration!.padding == null) { return padding; } final EdgeInsetsGeometry? decorationPadding = decoration!.padding; if (padding == null) { return decorationPadding; } return padding!.add(decorationPadding!); } @override Widget build(BuildContext context) { Widget? current = child; if (child == null && (constraints == null || !constraints!.isTight)) { current = LimitedBox( maxWidth: 0.0, maxHeight: 0.0, child: ConstrainedBox(constraints: const BoxConstraints.expand()), ); } else if (alignment != null) { current = Align(alignment: alignment!, child: current); } final EdgeInsetsGeometry? effectivePadding = _paddingIncludingDecoration; if (effectivePadding != null) { current = Padding(padding: effectivePadding, child: current); } if (color != null) { current = ColoredBox(color: color!, child: current); } if (clipBehavior != Clip.none) { assert(decoration != null); current = ClipPath( clipper: _DecorationClipper( textDirection: Directionality.maybeOf(context), decoration: decoration!, ), clipBehavior: clipBehavior, child: current, ); } if (decoration != null) { current = DecoratedBox(decoration: decoration!, child: current); } if (foregroundDecoration != null) { current = DecoratedBox( decoration: foregroundDecoration!, position: DecorationPosition.foreground, child: current, ); } if (constraints != null) { current = ConstrainedBox(constraints: constraints!, child: current); } if (margin != null) { current = Padding(padding: margin!, child: current); } if (transform != null) { current = Transform(transform: transform!, alignment: transformAlignment, child: current); } return current!; } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, showName: false, defaultValue: null)); properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null)); properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: Clip.none)); if (color != null) { properties.add(DiagnosticsProperty<Color>('bg', color)); } else { properties.add(DiagnosticsProperty<Decoration>('bg', decoration, defaultValue: null)); } properties.add(DiagnosticsProperty<Decoration>('fg', foregroundDecoration, defaultValue: null)); properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null)); properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('margin', margin, defaultValue: null)); properties.add(ObjectFlagProperty<Matrix4>.has('transform', transform)); } } /// A clipper that uses [Decoration.getClipPath] to clip. class _DecorationClipper extends CustomClipper<Path> { _DecorationClipper({ TextDirection? textDirection, required this.decoration, }) : assert(decoration != null), textDirection = textDirection ?? TextDirection.ltr; final TextDirection textDirection; final Decoration decoration; @override Path getClip(Size size) { return decoration.getClipPath(Offset.zero & size, textDirection); } @override bool shouldReclip(_DecorationClipper oldClipper) { return oldClipper.decoration != decoration || oldClipper.textDirection != textDirection; } }