Permalink
391 lines (361 sloc) 14.5 KB
// Copyright 2016 The Chromium 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/painting.dart';
import 'package:flutter/rendering.dart';
import 'basic.dart';
import 'framework.dart';
import 'image.dart';
/// 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].
///
/// ## Sample code
///
/// This sample shows a radial gradient that draws a moon on a night sky:
///
/// ```dart
/// new DecoratedBox(
/// decoration: new BoxDecoration(
/// gradient: new RadialGradient(
/// center: const Alignment(-0.5, -0.6),
/// radius: 0.15,
/// colors: <Color>[
/// const Color(0xFFEEEEEE),
/// const Color(0xFF111133),
/// ],
/// stops: <double>[0.9, 1.0],
/// ),
/// ),
/// )
/// ```
///
/// 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({
Key key,
@required this.decoration,
this.position = DecorationPosition.background,
Widget child
}) : assert(decoration != null),
assert(position != null),
super(key: key, child: child);
/// 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 new 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);
String label;
if (position != null) {
switch (position) {
case DecorationPosition.background:
label = 'bg';
break;
case DecorationPosition.foreground:
label = 'fg';
break;
}
} else {
label = 'decoration';
}
properties.add(new EnumProperty<DecorationPosition>('position', position, level: position != null ? DiagnosticLevel.hidden : DiagnosticLevel.info));
properties.add(new DiagnosticsProperty<Decoration>(
label,
decoration,
ifNull: 'no decoration',
showName: decoration != null,
));
}
}
/// A convenience widget that combines common painting, positioning, and sizing
/// widgets.
///
/// 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.
///
/// ## 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.
///
/// tl;dr: [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].
///
/// ## Sample code
///
/// This example shows a 48x48 green 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:
///
/// ```dart
/// new Center(
/// child: new Container(
/// margin: const EdgeInsets.all(10.0),
/// color: const Color(0xFF00FF00),
/// width: 48.0,
/// height: 48.0,
/// ),
/// )
/// ```
///
/// 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 teal. The [alignment] causes the [child] to be
/// centered in the box. The [foregroundDecoration] overlays a nine-patch image
/// onto the text. Finally, the [transform] applies a slight rotation to the
/// entire contraption to complete the effect.
///
/// ```dart
/// new Container(
/// constraints: new BoxConstraints.expand(
/// height: Theme.of(context).textTheme.display1.fontSize * 1.1 + 200.0,
/// ),
/// padding: const EdgeInsets.all(8.0),
/// color: Colors.teal.shade700,
/// alignment: Alignment.center,
/// child: new Text('Hello World', style: Theme.of(context).textTheme.display1.copyWith(color: Colors.white)),
/// foregroundDecoration: new BoxDecoration(
/// image: new DecorationImage(
/// image: new NetworkImage('https://www.example.com/images/frame.png'),
/// centerSlice: new Rect.fromLTRB(270.0, 180.0, 1360.0, 730.0),
/// ),
/// ),
/// transform: new Matrix4.rotationZ(0.1),
/// )
/// ```
///
/// 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.
/// * The [catalog of layout widgets](https://flutter.io/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` argument is a shorthand for `decoration: new
/// BoxDecoration(color: color)`, which means you cannot supply both a `color`
/// and a `decoration` argument. If you want to have both a `color` and a
/// `decoration`, you can pass the color as the `color` argument to the
/// `BoxDecoration`.
Container({
Key key,
this.alignment,
this.padding,
Color color,
Decoration decoration,
this.foregroundDecoration,
double width,
double height,
BoxConstraints constraints,
this.margin,
this.transform,
this.child,
}) : assert(margin == null || margin.isNonNegative),
assert(padding == null || padding.isNonNegative),
assert(decoration == null || decoration.debugAssertIsValid()),
assert(constraints == null || constraints.debugAssertIsValid()),
assert(color == null || decoration == null,
'Cannot provide both a color and a decoration\n'
'The color argument is just a shorthand for "decoration: new BoxDecoration(color: color)".'
),
decoration = decoration ?? (color != null ? new BoxDecoration(color: color) : null),
constraints =
(width != null || height != null)
? constraints?.tighten(width: width, height: height)
?? new BoxConstraints.tightFor(width: width, height: height)
: constraints,
super(key: key);
/// 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.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 decoration to paint behind the [child].
///
/// A shorthand for specifying just a solid color is available in the
/// constructor: set the `color` argument instead of the `decoration`
/// argument.
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;
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 = new LimitedBox(
maxWidth: 0.0,
maxHeight: 0.0,
child: new ConstrainedBox(constraints: const BoxConstraints.expand())
);
}
if (alignment != null)
current = new Align(alignment: alignment, child: current);
final EdgeInsetsGeometry effectivePadding = _paddingIncludingDecoration;
if (effectivePadding != null)
current = new Padding(padding: effectivePadding, child: current);
if (decoration != null)
current = new DecoratedBox(decoration: decoration, child: current);
if (foregroundDecoration != null) {
current = new DecoratedBox(
decoration: foregroundDecoration,
position: DecorationPosition.foreground,
child: current
);
}
if (constraints != null)
current = new ConstrainedBox(constraints: constraints, child: current);
if (margin != null)
current = new Padding(padding: margin, child: current);
if (transform != null)
current = new Transform(transform: transform, child: current);
return current;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(new DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, showName: false, defaultValue: null));
properties.add(new DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
properties.add(new DiagnosticsProperty<Decoration>('bg', decoration, defaultValue: null));
properties.add(new DiagnosticsProperty<Decoration>('fg', foregroundDecoration, defaultValue: null));
properties.add(new DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null));
properties.add(new DiagnosticsProperty<EdgeInsetsGeometry>('margin', margin, defaultValue: null));
properties.add(new ObjectFlagProperty<Matrix4>.has('transform', transform));
}
}