blob: c97193344328eaf0395d2f66ff66b95edb495848 [file] [log] [blame]
// 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/widgets.dart';
import 'card_theme.dart';
import 'colors.dart';
import 'material.dart';
import 'theme.dart';
/// A material design card. A card has slightly rounded corners and a shadow.
///
/// A card is a sheet of [Material] used to represent some related information,
/// for example an album, a geographical location, a meal, contact details, etc.
///
/// This is what it looks like when run:
///
/// ![A card with a slight shadow, consisting of two rows, one with an icon and
/// some text describing a musical, and the other with buttons for buying
/// tickets or listening to the show.](https://flutter.github.io/assets-for-api-docs/assets/material/card.png)
///
/// {@tool dartpad --template=stateless_widget_scaffold}
///
/// This sample shows creation of a [Card] widget that shows album information
/// and two actions.
///
/// ```dart
/// Widget build(BuildContext context) {
/// return Center(
/// child: Card(
/// child: Column(
/// mainAxisSize: MainAxisSize.min,
/// children: <Widget>[
/// const ListTile(
/// leading: Icon(Icons.album),
/// title: Text('The Enchanted Nightingale'),
/// subtitle: Text('Music by Julie Gable. Lyrics by Sidney Stein.'),
/// ),
/// ButtonBar(
/// children: <Widget>[
/// FlatButton(
/// child: const Text('BUY TICKETS'),
/// onPressed: () { /* ... */ },
/// ),
/// FlatButton(
/// child: const Text('LISTEN'),
/// onPressed: () { /* ... */ },
/// ),
/// ],
/// ),
/// ],
/// ),
/// ),
/// );
/// }
/// ```
/// {@end-tool}
///
/// Sometimes the primary action area of a card is the card itself. Cards can be
/// one large touch target that shows a detail screen when tapped.
///
/// {@tool dartpad --template=stateless_widget_scaffold}
///
/// This sample shows creation of a [Card] widget that can be tapped. When
/// tapped this [Card]'s [InkWell] displays an "ink splash" that fills the
/// entire card.
///
/// ```dart
/// Widget build(BuildContext context) {
/// return Center(
/// child: Card(
/// child: InkWell(
/// splashColor: Colors.blue.withAlpha(30),
/// onTap: () {
/// print('Card tapped.');
/// },
/// child: Container(
/// width: 300,
/// height: 100,
/// child: Text('A card that can be tapped'),
/// ),
/// ),
/// ),
/// );
/// }
/// ```
///
/// {@end-tool}
///
/// See also:
///
/// * [ListTile], to display icons and text in a card.
/// * [ButtonBar], to display buttons at the bottom of a card.
/// * [showDialog], to display a modal card.
/// * <https://material.io/design/components/cards.html>
class Card extends StatelessWidget {
/// Creates a material design card.
///
/// The [elevation] must be null or non-negative. The [borderOnForeground]
/// must not be null.
const Card({
Key key,
this.color,
this.shadowColor,
this.elevation,
this.shape,
this.borderOnForeground = true,
this.margin,
this.clipBehavior,
this.child,
this.semanticContainer = true,
}) : assert(elevation == null || elevation >= 0.0),
assert(borderOnForeground != null),
super(key: key);
/// The card's background color.
///
/// Defines the card's [Material.color].
///
/// If this property is null then [ThemeData.cardTheme.color] is used,
/// if that's null then [ThemeData.cardColor] is used.
final Color color;
/// The color to paint the shadow below the card.
///
/// If null then the ambient [CardTheme]'s shadowColor is used.
/// If that's null too, then the default is fully opaque black.
final Color shadowColor;
/// The z-coordinate at which to place this card. This controls the size of
/// the shadow below the card.
///
/// Defines the card's [Material.elevation].
///
/// If this property is null then [ThemeData.cardTheme.elevation] is used,
/// if that's null, the default value is 1.0.
final double elevation;
/// The shape of the card's [Material].
///
/// Defines the card's [Material.shape].
///
/// If this property is null then [ThemeData.cardTheme.shape] is used.
/// If that's null then the shape will be a [RoundedRectangleBorder] with a
/// circular corner radius of 4.0.
final ShapeBorder shape;
/// Whether to paint the [shape] border in front of the [child].
///
/// The default value is true.
/// If false, the border will be painted behind the [child].
final bool borderOnForeground;
/// {@macro flutter.widgets.Clip}
///
/// If this property is null then [ThemeData.cardTheme.clipBehavior] is used.
/// If that's null then the behavior will be [Clip.none].
final Clip clipBehavior;
/// The empty space that surrounds the card.
///
/// Defines the card's outer [Container.margin].
///
/// If this property is null then [ThemeData.cardTheme.margin] is used,
/// if that's null, the default margin is 4.0 logical pixels on all sides:
/// `EdgeInsets.all(4.0)`.
final EdgeInsetsGeometry margin;
/// Whether this widget represents a single semantic container, or if false
/// a collection of individual semantic nodes.
///
/// Defaults to true.
///
/// Setting this flag to true will attempt to merge all child semantics into
/// this node. Setting this flag to false will force all child semantic nodes
/// to be explicit.
///
/// This flag should be false if the card contains multiple different types
/// of content.
final bool semanticContainer;
/// The widget below this widget in the tree.
///
/// {@macro flutter.widgets.child}
final Widget child;
static const double _defaultElevation = 1.0;
@override
Widget build(BuildContext context) {
final CardTheme cardTheme = CardTheme.of(context);
return Semantics(
container: semanticContainer,
child: Container(
margin: margin ?? cardTheme.margin ?? const EdgeInsets.all(4.0),
child: Material(
type: MaterialType.card,
shadowColor: shadowColor ?? cardTheme.shadowColor ?? Colors.black,
color: color ?? cardTheme.color ?? Theme.of(context).cardColor,
elevation: elevation ?? cardTheme.elevation ?? _defaultElevation,
shape: shape ?? cardTheme.shape ?? const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
borderOnForeground: borderOnForeground,
clipBehavior: clipBehavior ?? cardTheme.clipBehavior ?? Clip.none,
child: Semantics(
explicitChildNodes: !semanticContainer,
child: child,
),
),
),
);
}
}