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 123 124 125 126 127
  /// In Material 3, [surfaceTintColor] is drawn on top of this color
  /// when the card is elevated. This might make the appearance of
  /// the card slightly different than in Material 2. To disable this
  /// feature, set [surfaceTintColor] to [Colors.transparent].
  /// See [Material.surfaceTintColor] for more details.
  ///
128 129 130
  /// If this property is null then the ambient [CardTheme.color] is used. If that is null,
  /// and [ThemeData.useMaterial3] is true, then [ColorScheme.surface] of
  /// [ThemeData.colorScheme] is used. Otherwise, [ThemeData.cardColor] is used.
131
  final Color? color;
132

133 134 135
  /// The color to paint the shadow below the card.
  ///
  /// If null then the ambient [CardTheme]'s shadowColor is used.
136 137
  /// If that's null too, then the overall theme's [ThemeData.shadowColor]
  /// (default black) is used.
138
  final Color? shadowColor;
139

140 141
  /// The color used as an overlay on [color] to indicate elevation.
  ///
142 143
  /// If this is null, no overlay will be applied. Otherwise this color
  /// will be composited on top of [color] with an opacity related
144 145 146 147 148 149 150 151
  /// to [elevation] and used to paint the background of the card.
  ///
  /// The default is null.
  ///
  /// See [Material.surfaceTintColor] for more details on how this
  /// overlay is applied.
  final Color? surfaceTintColor;

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

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

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

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

183 184 185 186
  /// The empty space that surrounds the card.
  ///
  /// Defines the card's outer [Container.margin].
  ///
187 188 189
  /// 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)`.
190
  final EdgeInsetsGeometry? margin;
191

192 193 194
  /// Whether this widget represents a single semantic container, or if false
  /// a collection of individual semantic nodes.
  ///
195
  /// Defaults to true.
196 197 198 199 200 201 202 203 204
  ///
  /// 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
205 206
  /// The widget below this widget in the tree.
  ///
207
  /// {@macro flutter.widgets.ProxyWidget.child}
208
  final Widget? child;
Hans Muller's avatar
Hans Muller committed
209

210 211
  final _CardVariant _variant;

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

229
    return Semantics(
230
      container: semanticContainer,
231
      child: Container(
232
        margin: margin ?? cardTheme.margin ?? defaults.margin!,
233
        child: Material(
234
          type: MaterialType.card,
235 236 237 238 239
          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,
240
          borderOnForeground: borderOnForeground,
241
          clipBehavior: clipBehavior ?? cardTheme.clipBehavior ?? defaults.clipBehavior!,
242 243 244 245
          child: Semantics(
            explicitChildNodes: !semanticContainer,
            child: child,
          ),
Hans Muller's avatar
Hans Muller committed
246
        ),
247
      ),
248 249 250
    );
  }
}
251

252 253 254
// Hand coded defaults based on Material Design 2.
class _CardDefaultsM2 extends CardTheme {
  const _CardDefaultsM2(this.context)
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
    : 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;
}

273
// BEGIN GENERATED TOKEN PROPERTIES - Card
274

275 276 277 278 279 280
// 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 {
281
  _CardDefaultsM3(this.context)
282 283 284 285 286 287 288
    : super(
        clipBehavior: Clip.none,
        elevation: 1.0,
        margin: const EdgeInsets.all(4.0),
      );

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

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

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

  @override
298
  Color? get surfaceTintColor => _colors.surfaceTint;
299 300 301

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

304
// END GENERATED TOKEN PROPERTIES - Card
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 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 360 361 362 363 364 365 366 367 368 369 370 371 372 373

// 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
  Color? get color => _colors.surfaceVariant;

  @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
  Color? get surfaceTintColor => _colors.surfaceTint;

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

// END GENERATED TOKEN PROPERTIES - OutlinedCard