Skip to content

Commit 2e7fa83

Browse files
authored
Normalize Card theme (#151914)
This PR is to make preparations to make `CardTheme` conform to Flutter's conventions for component themes: * Added a `CardThemeData` class which defines overrides for the defaults for `Card` properties. * Added 2 `CardTheme` constructor parameters: `CardThemeData? data` and `Widget? child`. This is now the preferred way to configure a `CardTheme`: ```dart CardTheme( data: CardThemeData(color: xxx, elevation: xxx, ...), child: Card(...) ) ``` These two properties are made nullable to not break existing apps which has customized `ThemeData.cardTheme`. * Changed the type of theme defaults from `CardTheme` to `CardThemeData`. TODO: * Fix internal failures that may have breakages. * Change the type of `ThemeData.cardTheme` from `CardTheme` to `CardThemeData`. This may cause breaking changes, a migration guide will be created. Addresses the "theme normalization" sub project within flutter/flutter#91772
1 parent 804cca6 commit 2e7fa83

File tree

4 files changed

+271
-51
lines changed

4 files changed

+271
-51
lines changed

dev/tools/gen_defaults/lib/card_template.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class CardTemplate extends TokenTemplate {
2626

2727
@override
2828
String generate() => '''
29-
class _${blockName}DefaultsM3 extends CardTheme {
29+
class _${blockName}DefaultsM3 extends CardThemeData {
3030
_${blockName}DefaultsM3(this.context)
3131
: super(
3232
clipBehavior: Clip.none,

packages/flutter/lib/src/material/card.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ class Card extends StatelessWidget {
216216
@override
217217
Widget build(BuildContext context) {
218218
final CardTheme cardTheme = CardTheme.of(context);
219-
final CardTheme defaults;
219+
final CardThemeData defaults;
220220
if (Theme.of(context).useMaterial3) {
221221
defaults = switch (_variant) {
222222
_CardVariant.elevated => _CardDefaultsM3(context),
@@ -251,7 +251,7 @@ class Card extends StatelessWidget {
251251
}
252252

253253
// Hand coded defaults based on Material Design 2.
254-
class _CardDefaultsM2 extends CardTheme {
254+
class _CardDefaultsM2 extends CardThemeData {
255255
const _CardDefaultsM2(this.context)
256256
: super(
257257
clipBehavior: Clip.none,
@@ -278,7 +278,7 @@ class _CardDefaultsM2 extends CardTheme {
278278
// Design token database by the script:
279279
// dev/tools/gen_defaults/bin/gen_defaults.dart.
280280

281-
class _CardDefaultsM3 extends CardTheme {
281+
class _CardDefaultsM3 extends CardThemeData {
282282
_CardDefaultsM3(this.context)
283283
: super(
284284
clipBehavior: Clip.none,
@@ -311,7 +311,7 @@ class _CardDefaultsM3 extends CardTheme {
311311
// Design token database by the script:
312312
// dev/tools/gen_defaults/bin/gen_defaults.dart.
313313

314-
class _FilledCardDefaultsM3 extends CardTheme {
314+
class _FilledCardDefaultsM3 extends CardThemeData {
315315
_FilledCardDefaultsM3(this.context)
316316
: super(
317317
clipBehavior: Clip.none,
@@ -344,7 +344,7 @@ class _FilledCardDefaultsM3 extends CardTheme {
344344
// Design token database by the script:
345345
// dev/tools/gen_defaults/bin/gen_defaults.dart.
346346

347-
class _OutlinedCardDefaultsM3 extends CardTheme {
347+
class _OutlinedCardDefaultsM3 extends CardThemeData {
348348
_OutlinedCardDefaultsM3(this.context)
349349
: super(
350350
clipBehavior: Clip.none,

packages/flutter/lib/src/material/card_theme.dart

Lines changed: 198 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,197 @@ import 'theme.dart';
3030
///
3131
/// * [ThemeData], which describes the overall theme information for the
3232
/// application.
33-
@immutable
34-
class CardTheme with Diagnosticable {
33+
class CardTheme extends InheritedWidget with Diagnosticable {
3534

3635
/// Creates a theme that can be used for [ThemeData.cardTheme].
3736
///
3837
/// The [elevation] must be null or non-negative.
3938
const CardTheme({
39+
super.key,
40+
Clip? clipBehavior,
41+
Color? color,
42+
Color? surfaceTintColor,
43+
Color? shadowColor,
44+
double? elevation,
45+
EdgeInsetsGeometry? margin,
46+
ShapeBorder? shape,
47+
CardThemeData? data,
48+
Widget? child,
49+
}) : assert(
50+
data == null ||
51+
(clipBehavior ??
52+
color ??
53+
surfaceTintColor ??
54+
shadowColor ??
55+
elevation ??
56+
margin ??
57+
shape) == null),
58+
assert(elevation == null || elevation >= 0.0),
59+
_data = data,
60+
_clipBehavior = clipBehavior,
61+
_color = color,
62+
_surfaceTintColor = surfaceTintColor,
63+
_shadowColor = shadowColor,
64+
_elevation = elevation,
65+
_margin = margin,
66+
_shape = shape,
67+
super(child: child ?? const SizedBox());
68+
69+
final CardThemeData? _data;
70+
final Clip? _clipBehavior;
71+
final Color? _color;
72+
final Color? _surfaceTintColor;
73+
final Color? _shadowColor;
74+
final double? _elevation;
75+
final EdgeInsetsGeometry? _margin;
76+
final ShapeBorder? _shape;
77+
78+
/// Overrides the default value for [Card.clipBehavior].
79+
///
80+
/// This property is obsolete and will be deprecated in a future release:
81+
/// please use the [CardThemeData.clipBehavior] property in [data] instead.
82+
Clip? get clipBehavior => _data != null ? _data.clipBehavior : _clipBehavior;
83+
84+
/// Overrides the default value for [Card.color].
85+
///
86+
/// This property is obsolete and will be deprecated in a future release:
87+
/// please use the [CardThemeData.color] property in [data] instead.
88+
Color? get color => _data != null ? _data.color : _color;
89+
90+
/// Overrides the default value for [Card.surfaceTintColor].
91+
///
92+
/// This property is obsolete and will be deprecated in a future release:
93+
/// please use the [CardThemeData.surfaceTintColor] property in [data] instead.
94+
Color? get surfaceTintColor => _data != null ? _data.surfaceTintColor : _surfaceTintColor;
95+
96+
/// Overrides the default value for [Card.shadowColor].
97+
///
98+
/// This property is obsolete and will be deprecated in a future release:
99+
/// please use the [CardThemeData.shadowColor] property in [data] instead.
100+
Color? get shadowColor => _data != null ? _data.shadowColor : _shadowColor;
101+
102+
/// Overrides the default value for [Card.elevation].
103+
///
104+
/// This property is obsolete and will be deprecated in a future release:
105+
/// please use the [CardThemeData.elevation] property in [data] instead.
106+
double? get elevation => _data != null ? _data.elevation : _elevation;
107+
108+
/// Overrides the default value for [Card.margin].
109+
///
110+
/// This property is obsolete and will be deprecated in a future release:
111+
/// please use the [CardThemeData.margin] property in [data] instead.
112+
EdgeInsetsGeometry? get margin => _data != null ? _data.margin : _margin;
113+
114+
/// Overrides the default value for [Card.shape].
115+
///
116+
/// This property is obsolete and will be deprecated in a future release:
117+
/// please use the [CardThemeData.shape] property in [data] instead.
118+
ShapeBorder? get shape => _data != null ? _data.shape : _shape;
119+
120+
/// The properties used for all descendant [Card] widgets.
121+
CardThemeData get data {
122+
return _data ?? CardThemeData(
123+
clipBehavior: _clipBehavior,
124+
color: _color,
125+
surfaceTintColor: _surfaceTintColor,
126+
shadowColor: _shadowColor,
127+
elevation: _elevation,
128+
margin: _margin,
129+
shape: _shape,
130+
);
131+
}
132+
133+
/// Creates a copy of this object with the given fields replaced with the
134+
/// new values.
135+
///
136+
/// This method is obsolete and will be deprecated in a future release:
137+
/// please use the [CardThemeData.copyWith] instead.
138+
CardTheme copyWith({
139+
Clip? clipBehavior,
140+
Color? color,
141+
Color? shadowColor,
142+
Color? surfaceTintColor,
143+
double? elevation,
144+
EdgeInsetsGeometry? margin,
145+
ShapeBorder? shape,
146+
}) {
147+
return CardTheme(
148+
clipBehavior: clipBehavior ?? this.clipBehavior,
149+
color: color ?? this.color,
150+
shadowColor: shadowColor ?? this.shadowColor,
151+
surfaceTintColor: surfaceTintColor ?? this.surfaceTintColor,
152+
elevation: elevation ?? this.elevation,
153+
margin: margin ?? this.margin,
154+
shape: shape ?? this.shape,
155+
);
156+
}
157+
158+
/// The [ThemeData.cardTheme] property of the ambient [Theme].
159+
static CardTheme of(BuildContext context) {
160+
final CardTheme? cardTheme = context.dependOnInheritedWidgetOfExactType<CardTheme>();
161+
return cardTheme ?? Theme.of(context).cardTheme;
162+
}
163+
164+
@override
165+
bool updateShouldNotify(CardTheme oldWidget) => data != oldWidget.data;
166+
167+
/// Linearly interpolate between two Card themes.
168+
///
169+
/// {@macro dart.ui.shadow.lerp}
170+
///
171+
/// This method is obsolete and will be deprecated in a future release:
172+
/// please use the [CardThemeData.lerp] instead.
173+
static CardTheme lerp(CardTheme? a, CardTheme? b, double t) {
174+
if (identical(a, b) && a != null) {
175+
return a;
176+
}
177+
return CardTheme(
178+
clipBehavior: t < 0.5 ? a?.clipBehavior : b?.clipBehavior,
179+
color: Color.lerp(a?.color, b?.color, t),
180+
shadowColor: Color.lerp(a?.shadowColor, b?.shadowColor, t),
181+
surfaceTintColor: Color.lerp(a?.surfaceTintColor, b?.surfaceTintColor, t),
182+
elevation: lerpDouble(a?.elevation, b?.elevation, t),
183+
margin: EdgeInsetsGeometry.lerp(a?.margin, b?.margin, t),
184+
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
185+
);
186+
}
187+
188+
@override
189+
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
190+
super.debugFillProperties(properties);
191+
properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: null));
192+
properties.add(ColorProperty('color', color, defaultValue: null));
193+
properties.add(ColorProperty('shadowColor', shadowColor, defaultValue: null));
194+
properties.add(ColorProperty('surfaceTintColor', surfaceTintColor, defaultValue: null));
195+
properties.add(DiagnosticsProperty<double>('elevation', elevation, defaultValue: null));
196+
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('margin', margin, defaultValue: null));
197+
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
198+
}
199+
}
200+
201+
/// Defines default property values for descendant [Card] widgets.
202+
///
203+
/// Descendant widgets obtain the current [CardThemeData] object using
204+
/// `CardTheme.of(context)`. Instances of [CardThemeData] can be
205+
/// customized with [CardThemeData.copyWith].
206+
///
207+
/// Typically a [CardThemeData] is specified as part of the overall [Theme]
208+
/// with [ThemeData.cardTheme].
209+
///
210+
/// All [CardThemeData] properties are `null` by default. When null, the [Card]
211+
/// will use the values from [ThemeData] if they exist, otherwise it will
212+
/// provide its own defaults. See the individual [Card] properties for details.
213+
///
214+
/// See also:
215+
///
216+
/// * [ThemeData], which describes the overall theme information for the
217+
/// application.
218+
@immutable
219+
class CardThemeData with Diagnosticable {
220+
/// Creates a theme that can be used for [ThemeData.cardTheme].
221+
///
222+
/// The [elevation] must be null or non-negative.
223+
const CardThemeData({
40224
this.clipBehavior,
41225
this.color,
42226
this.shadowColor,
@@ -47,48 +231,29 @@ class CardTheme with Diagnosticable {
47231
}) : assert(elevation == null || elevation >= 0.0);
48232

49233
/// Overrides the default value for [Card.clipBehavior].
50-
///
51-
/// If null, [Card] uses [Clip.none].
52234
final Clip? clipBehavior;
53235

54236
/// Overrides the default value for [Card.color].
55-
///
56-
/// If null, [Card] uses [ThemeData.cardColor].
57237
final Color? color;
58238

59239
/// Overrides the default value for [Card.shadowColor].
60-
///
61-
/// If null, [Card] defaults to fully opaque black.
62240
final Color? shadowColor;
63241

64242
/// Overrides the default value for [Card.surfaceTintColor].
65-
///
66-
/// If null, [Card] will not display an overlay color.
67-
///
68-
/// See [Material.surfaceTintColor] for more details.
69243
final Color? surfaceTintColor;
70244

71245
/// Overrides the default value for [Card.elevation].
72-
///
73-
/// If null, [Card] uses a default of 1.0.
74246
final double? elevation;
75247

76248
/// Overrides the default value for [Card.margin].
77-
///
78-
/// If null, [Card] uses a default margin of 4.0 logical pixels on all sides:
79-
/// `EdgeInsets.all(4.0)`.
80249
final EdgeInsetsGeometry? margin;
81250

82251
/// Overrides the default value for [Card.shape].
83-
///
84-
/// If null, [Card] then uses a [RoundedRectangleBorder] with a circular
85-
/// corner radius of 12.0 and if [ThemeData.useMaterial3] is false,
86-
/// then the circular corner radius will be 4.0.
87252
final ShapeBorder? shape;
88253

89254
/// Creates a copy of this object with the given fields replaced with the
90255
/// new values.
91-
CardTheme copyWith({
256+
CardThemeData copyWith({
92257
Clip? clipBehavior,
93258
Color? color,
94259
Color? shadowColor,
@@ -97,7 +262,7 @@ class CardTheme with Diagnosticable {
97262
EdgeInsetsGeometry? margin,
98263
ShapeBorder? shape,
99264
}) {
100-
return CardTheme(
265+
return CardThemeData(
101266
clipBehavior: clipBehavior ?? this.clipBehavior,
102267
color: color ?? this.color,
103268
shadowColor: shadowColor ?? this.shadowColor,
@@ -108,19 +273,14 @@ class CardTheme with Diagnosticable {
108273
);
109274
}
110275

111-
/// The [ThemeData.cardTheme] property of the ambient [Theme].
112-
static CardTheme of(BuildContext context) {
113-
return Theme.of(context).cardTheme;
114-
}
115-
116276
/// Linearly interpolate between two Card themes.
117277
///
118278
/// {@macro dart.ui.shadow.lerp}
119-
static CardTheme lerp(CardTheme? a, CardTheme? b, double t) {
279+
static CardThemeData lerp(CardThemeData? a, CardThemeData? b, double t) {
120280
if (identical(a, b) && a != null) {
121281
return a;
122282
}
123-
return CardTheme(
283+
return CardThemeData(
124284
clipBehavior: t < 0.5 ? a?.clipBehavior : b?.clipBehavior,
125285
color: Color.lerp(a?.color, b?.color, t),
126286
shadowColor: Color.lerp(a?.shadowColor, b?.shadowColor, t),
@@ -150,14 +310,14 @@ class CardTheme with Diagnosticable {
150310
if (other.runtimeType != runtimeType) {
151311
return false;
152312
}
153-
return other is CardTheme
154-
&& other.clipBehavior == clipBehavior
155-
&& other.color == color
156-
&& other.shadowColor == shadowColor
157-
&& other.surfaceTintColor == surfaceTintColor
158-
&& other.elevation == elevation
159-
&& other.margin == margin
160-
&& other.shape == shape;
313+
return other is CardThemeData
314+
&& other.clipBehavior == clipBehavior
315+
&& other.color == color
316+
&& other.shadowColor == shadowColor
317+
&& other.surfaceTintColor == surfaceTintColor
318+
&& other.elevation == elevation
319+
&& other.margin == margin
320+
&& other.shape == shape;
161321
}
162322

163323
@override

0 commit comments

Comments
 (0)