card.dart 12 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:flutter/widgets.dart';
6

7
import 'card_theme.dart';
8
import 'color_scheme.dart';
9
import 'colors.dart';
10
import 'material.dart';
Hans Muller's avatar
Hans Muller committed
11
import 'theme.dart';
12

13 14
enum _CardVariant { elevated, filled, outlined }

15
/// A Material Design card: a panel with slightly rounded corners and an
16
/// elevation shadow.
17 18 19
///
/// 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.
20
///
21 22 23 24 25 26
/// 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)
///
27
/// {@tool dartpad}
28 29
/// This sample shows creation of a [Card] widget that shows album information
/// and two actions.
30
///
31
/// ** See code in examples/api/lib/material/card/card.0.dart **
32
/// {@end-tool}
33
///
34 35 36
/// 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.
///
37
/// {@tool dartpad}
38 39 40 41
/// 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.
///
42
/// ** See code in examples/api/lib/material/card/card.1.dart **
43 44
/// {@end-tool}
///
45 46 47
/// Material Design 3 introduced new types of cards. The default [Card] is the
/// elevated card. To create a filled card, use [Card.filled]; to create a outlined
/// card, use [Card.outlined].
48 49 50 51 52 53 54
/// {@tool dartpad}
/// This sample shows creation of [Card] widgets for elevated, filled and
/// outlined types, as described in: https://m3.material.io/components/cards/overview
///
/// ** See code in examples/api/lib/material/card/card.2.dart **
/// {@end-tool}
///
55 56
/// See also:
///
57
///  * [ListTile], to display icons and text in a card.
58
///  * [showDialog], to display a modal card.
59
///  * <https://material.io/design/components/cards.html>
60
///  * <https://m3.material.io/components/cards>
61
class Card extends StatelessWidget {
62
  /// Creates a Material Design card.
63
  ///
64
  /// The [elevation] must be null or non-negative.
65
  const Card({
66
    super.key,
67
    this.color,
68
    this.shadowColor,
69
    this.surfaceTintColor,
70
    this.elevation,
Hans Muller's avatar
Hans Muller committed
71
    this.shape,
72
    this.borderOnForeground = true,
73 74
    this.margin,
    this.clipBehavior,
75
    this.child,
76
    this.semanticContainer = true,
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
  }) : assert(elevation == null || elevation >= 0.0),
       _variant = _CardVariant.elevated;

  /// Create a filled variant of Card.
  ///
  /// Filled cards provide subtle separation from the background. This has less
  /// emphasis than elevated(default) or outlined cards.
  const Card.filled({
    super.key,
    this.color,
    this.shadowColor,
    this.surfaceTintColor,
    this.elevation,
    this.shape,
    this.borderOnForeground = true,
    this.margin,
    this.clipBehavior,
    this.child,
    this.semanticContainer = true,
  }) : assert(elevation == null || elevation >= 0.0),
       _variant = _CardVariant.filled;

  /// Create an outlined variant of Card.
  ///
  /// Outlined cards have a visual boundary around the container. This can
  /// provide greater emphasis than the other types.
  const Card.outlined({
    super.key,
    this.color,
    this.shadowColor,
    this.surfaceTintColor,
    this.elevation,
    this.shape,
    this.borderOnForeground = true,
    this.margin,
    this.clipBehavior,
    this.child,
    this.semanticContainer = true,
  }) : assert(elevation == null || elevation >= 0.0),
       _variant = _CardVariant.outlined;
117

Hans Muller's avatar
Hans Muller committed
118
  /// The card's background color.
119
  ///
Hans Muller's avatar
Hans Muller committed
120 121
  /// Defines the card's [Material.color].
  ///
122
  /// If this property is null then the ambient [CardTheme.color] is used. If that is null,
123
  /// and [ThemeData.useMaterial3] is true, then [ColorScheme.surfaceContainerLow] of
124
  /// [ThemeData.colorScheme] is used. Otherwise, [ThemeData.cardColor] is used.
125
  final Color? color;
126

127 128 129
  /// The color to paint the shadow below the card.
  ///
  /// If null then the ambient [CardTheme]'s shadowColor is used.
130 131
  /// If that's null too, then the overall theme's [ThemeData.shadowColor]
  /// (default black) is used.
132
  final Color? shadowColor;
133

134 135
  /// The color used as an overlay on [color] to indicate elevation.
  ///
136 137 138 139 140
  /// This is not recommended for use. [Material 3 spec](https://m3.material.io/styles/color/the-color-system/color-roles)
  /// introduced a set of tone-based surfaces and surface containers in its [ColorScheme],
  /// which provide more flexibility. The intention is to eventually remove surface tint color from
  /// the framework.
  ///
141 142
  /// If this is null, no overlay will be applied. Otherwise this color
  /// will be composited on top of [color] with an opacity related
143 144
  /// to [elevation] and used to paint the background of the card.
  ///
145
  /// The default is [Colors.transparent].
146 147 148 149 150
  ///
  /// See [Material.surfaceTintColor] for more details on how this
  /// overlay is applied.
  final Color? surfaceTintColor;

151 152
  /// The z-coordinate at which to place this card. This controls the size of
  /// the shadow below the card.
153
  ///
Hans Muller's avatar
Hans Muller committed
154 155
  /// Defines the card's [Material.elevation].
  ///
156 157
  /// If this property is null then [CardTheme.elevation] of
  /// [ThemeData.cardTheme] is used. If that's null, the default value is 1.0.
158
  final double? elevation;
159

Hans Muller's avatar
Hans Muller committed
160 161 162 163
  /// The shape of the card's [Material].
  ///
  /// Defines the card's [Material.shape].
  ///
164 165
  /// If this property is null then [CardTheme.shape] of [ThemeData.cardTheme]
  /// is used. If that's null then the shape will be a [RoundedRectangleBorder]
166 167
  /// with a circular corner radius of 12.0 and if [ThemeData.useMaterial3] is
  /// false, then the circular corner radius will be 4.0.
168
  final ShapeBorder? shape;
Hans Muller's avatar
Hans Muller committed
169

170 171 172 173 174 175
  /// 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;

176
  /// {@macro flutter.material.Material.clipBehavior}
177
  ///
178 179
  /// If this property is null then [CardTheme.clipBehavior] of
  /// [ThemeData.cardTheme] is used. If that's null then the behavior will be [Clip.none].
180
  final Clip? clipBehavior;
181

182 183 184 185
  /// The empty space that surrounds the card.
  ///
  /// Defines the card's outer [Container.margin].
  ///
186 187 188
  /// If this property is null then [CardTheme.margin] of
  /// [ThemeData.cardTheme] is used. If that's null, the default margin is 4.0
  /// logical pixels on all sides: `EdgeInsets.all(4.0)`.
189
  final EdgeInsetsGeometry? margin;
190

191 192 193
  /// Whether this widget represents a single semantic container, or if false
  /// a collection of individual semantic nodes.
  ///
194
  /// Defaults to true.
195 196 197 198 199 200 201 202 203
  ///
  /// 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;

Hans Muller's avatar
Hans Muller committed
204 205
  /// The widget below this widget in the tree.
  ///
206
  /// {@macro flutter.widgets.ProxyWidget.child}
207
  final Widget? child;
Hans Muller's avatar
Hans Muller committed
208

209 210
  final _CardVariant _variant;

211
  @override
212
  Widget build(BuildContext context) {
213
    final CardTheme cardTheme = CardTheme.of(context);
214 215
    final CardTheme defaults;
    if (Theme.of(context).useMaterial3) {
216 217 218 219 220
      defaults = switch (_variant) {
        _CardVariant.elevated => _CardDefaultsM3(context),
        _CardVariant.filled   => _FilledCardDefaultsM3(context),
        _CardVariant.outlined => _OutlinedCardDefaultsM3(context),
      };
221 222 223
    } else {
      defaults = _CardDefaultsM2(context);
    }
224

225
    return Semantics(
226
      container: semanticContainer,
227
      child: Container(
228
        margin: margin ?? cardTheme.margin ?? defaults.margin!,
229
        child: Material(
230
          type: MaterialType.card,
231 232 233 234 235
          color: color ?? cardTheme.color ?? defaults.color,
          shadowColor: shadowColor ?? cardTheme.shadowColor ?? defaults.shadowColor,
          surfaceTintColor: surfaceTintColor ?? cardTheme.surfaceTintColor ?? defaults.surfaceTintColor,
          elevation: elevation ?? cardTheme.elevation ?? defaults.elevation!,
          shape: shape ?? cardTheme.shape ?? defaults.shape,
236
          borderOnForeground: borderOnForeground,
237
          clipBehavior: clipBehavior ?? cardTheme.clipBehavior ?? defaults.clipBehavior!,
238 239 240 241
          child: Semantics(
            explicitChildNodes: !semanticContainer,
            child: child,
          ),
Hans Muller's avatar
Hans Muller committed
242
        ),
243
      ),
244 245 246
    );
  }
}
247

248 249 250
// Hand coded defaults based on Material Design 2.
class _CardDefaultsM2 extends CardTheme {
  const _CardDefaultsM2(this.context)
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
    : super(
        clipBehavior: Clip.none,
        elevation: 1.0,
        margin: const EdgeInsets.all(4.0),
        shape: const RoundedRectangleBorder(
          borderRadius: BorderRadius.all(Radius.circular(4.0)),
        )
    );

  final BuildContext context;

  @override
  Color? get color => Theme.of(context).cardColor;

  @override
  Color? get shadowColor => Theme.of(context).shadowColor;
}

269
// BEGIN GENERATED TOKEN PROPERTIES - Card
270

271 272 273 274 275 276
// Do not edit by hand. The code between the "BEGIN GENERATED" and
// "END GENERATED" comments are generated from data in the Material
// Design token database by the script:
//   dev/tools/gen_defaults/bin/gen_defaults.dart.

class _CardDefaultsM3 extends CardTheme {
277
  _CardDefaultsM3(this.context)
278 279 280 281 282 283 284
    : super(
        clipBehavior: Clip.none,
        elevation: 1.0,
        margin: const EdgeInsets.all(4.0),
      );

  final BuildContext context;
285
  late final ColorScheme _colors = Theme.of(context).colorScheme;
286 287

  @override
288
  Color? get color => _colors.surfaceContainerLow;
289 290

  @override
291
  Color? get shadowColor => _colors.shadow;
292 293

  @override
294
  Color? get surfaceTintColor => Colors.transparent;
295 296 297

  @override
  ShapeBorder? get shape =>const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12.0)));
298 299
}

300
// END GENERATED TOKEN PROPERTIES - Card
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320

// BEGIN GENERATED TOKEN PROPERTIES - FilledCard

// Do not edit by hand. The code between the "BEGIN GENERATED" and
// "END GENERATED" comments are generated from data in the Material
// Design token database by the script:
//   dev/tools/gen_defaults/bin/gen_defaults.dart.

class _FilledCardDefaultsM3 extends CardTheme {
  _FilledCardDefaultsM3(this.context)
    : super(
        clipBehavior: Clip.none,
        elevation: 0.0,
        margin: const EdgeInsets.all(4.0),
      );

  final BuildContext context;
  late final ColorScheme _colors = Theme.of(context).colorScheme;

  @override
321
  Color? get color => _colors.surfaceContainerHighest;
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359

  @override
  Color? get shadowColor => _colors.shadow;

  @override
  Color? get surfaceTintColor => Colors.transparent;

  @override
  ShapeBorder? get shape =>const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12.0)));
}

// END GENERATED TOKEN PROPERTIES - FilledCard

// BEGIN GENERATED TOKEN PROPERTIES - OutlinedCard

// Do not edit by hand. The code between the "BEGIN GENERATED" and
// "END GENERATED" comments are generated from data in the Material
// Design token database by the script:
//   dev/tools/gen_defaults/bin/gen_defaults.dart.

class _OutlinedCardDefaultsM3 extends CardTheme {
  _OutlinedCardDefaultsM3(this.context)
    : super(
        clipBehavior: Clip.none,
        elevation: 0.0,
        margin: const EdgeInsets.all(4.0),
      );

  final BuildContext context;
  late final ColorScheme _colors = Theme.of(context).colorScheme;

  @override
  Color? get color => _colors.surface;

  @override
  Color? get shadowColor => _colors.shadow;

  @override
360
  Color? get surfaceTintColor => Colors.transparent;
361 362 363 364 365 366 367 368 369

  @override
  ShapeBorder? get shape =>
    const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12.0))).copyWith(
      side: BorderSide(color: _colors.outlineVariant)
    );
}

// END GENERATED TOKEN PROPERTIES - OutlinedCard