Normalize ThemeData.cardTheme (#153254)

Following https://github.com/flutter/flutter/pull/151914, this PR is to normalize `ThemeData.cardTheme`; change the `CardTheme cardTheme` property to `CardThemeData cardTheme` in `ThemeData`. In `ThemeData()` and `ThemeData.copyWith()`, the `cardTheme` parameter type is changed to `Object?` to accept both `CardTheme` and `CardThemeData` so that we won't cause immediate breaking change and make sure rolling is smooth. Once all component themes are normalized, these `Object?` types should be changed to `xxxThemeData`.

There's no way to create a dart fix because we can't add a "@deprecated" label for `CardTheme` because `CardTheme` is a new InheritedWidget subclass now.

Addresses the "theme normalization" sub project within https://github.com/flutter/flutter/issues/91772
This commit is contained in:
Qun Cheng 2024-10-04 11:59:05 -07:00 committed by GitHub
parent 85abc1a3a4
commit 534adfbe02
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 154 additions and 22 deletions

View File

@ -215,7 +215,7 @@ class Card extends StatelessWidget {
@override
Widget build(BuildContext context) {
final CardTheme cardTheme = CardTheme.of(context);
final CardThemeData cardTheme = CardTheme.of(context);
final CardThemeData defaults;
if (Theme.of(context).useMaterial3) {
defaults = switch (_variant) {

View File

@ -156,9 +156,9 @@ class CardTheme extends InheritedWidget with Diagnosticable {
}
/// The [ThemeData.cardTheme] property of the ambient [Theme].
static CardTheme of(BuildContext context) {
static CardThemeData of(BuildContext context) {
final CardTheme? cardTheme = context.dependOnInheritedWidgetOfExactType<CardTheme>();
return cardTheme ?? Theme.of(context).cardTheme;
return cardTheme?.data ?? Theme.of(context).cardTheme;
}
@override

View File

@ -315,7 +315,8 @@ class ThemeData with Diagnosticable {
BottomNavigationBarThemeData? bottomNavigationBarTheme,
BottomSheetThemeData? bottomSheetTheme,
ButtonThemeData? buttonTheme,
CardTheme? cardTheme,
// TODO(QuncCccccc): Change the parameter type to CardThemeData
Object? cardTheme,
CheckboxThemeData? checkboxTheme,
ChipThemeData? chipTheme,
DataTableThemeData? dataTableTheme,
@ -493,7 +494,15 @@ class ThemeData with Diagnosticable {
bottomAppBarTheme ??= const BottomAppBarTheme();
bottomNavigationBarTheme ??= const BottomNavigationBarThemeData();
bottomSheetTheme ??= const BottomSheetThemeData();
cardTheme ??= const CardTheme();
// TODO(QuncCccccc): Clean it up once the type of `cardTheme` is changed to `CardThemeData`
if (cardTheme != null) {
if (cardTheme is CardTheme) {
cardTheme = cardTheme.data;
} else if (cardTheme is! CardThemeData) {
throw ArgumentError('cardTheme must be either a CardThemeData or a CardTheme');
}
}
cardTheme ??= const CardThemeData();
checkboxTheme ??= const CheckboxThemeData();
chipTheme ??= const ChipThemeData();
dataTableTheme ??= const DataTableThemeData();
@ -594,7 +603,7 @@ class ThemeData with Diagnosticable {
bottomNavigationBarTheme: bottomNavigationBarTheme,
bottomSheetTheme: bottomSheetTheme,
buttonTheme: buttonTheme,
cardTheme: cardTheme,
cardTheme: cardTheme as CardThemeData,
checkboxTheme: checkboxTheme,
chipTheme: chipTheme,
dataTableTheme: dataTableTheme,
@ -1266,7 +1275,7 @@ class ThemeData with Diagnosticable {
/// The colors and styles used to render [Card].
///
/// This is the value returned from [CardTheme.of].
final CardTheme cardTheme;
final CardThemeData cardTheme;
/// A theme for customizing the appearance and layout of [Checkbox] widgets.
final CheckboxThemeData checkboxTheme;
@ -1466,7 +1475,7 @@ class ThemeData with Diagnosticable {
BottomNavigationBarThemeData? bottomNavigationBarTheme,
BottomSheetThemeData? bottomSheetTheme,
ButtonThemeData? buttonTheme,
CardTheme? cardTheme,
Object? cardTheme,
CheckboxThemeData? checkboxTheme,
ChipThemeData? chipTheme,
DataTableThemeData? dataTableTheme,
@ -1521,6 +1530,15 @@ class ThemeData with Diagnosticable {
}) {
cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
// TODO(QuncCccccc): Clean it up once the type of `cardTheme` is changed to `CardThemeData`
if (cardTheme != null) {
if (cardTheme is CardTheme) {
cardTheme = cardTheme.data;
} else if (cardTheme is! CardThemeData) {
throw ArgumentError('cardTheme must be either a CardThemeData or a CardTheme');
}
}
// TODO(QuncCccccc): Clean this up once the type of `dialogTheme` is changed to `DialogThemeData`
if (dialogTheme != null) {
if (dialogTheme is DialogTheme) {
@ -1585,7 +1603,7 @@ class ThemeData with Diagnosticable {
bottomNavigationBarTheme: bottomNavigationBarTheme ?? this.bottomNavigationBarTheme,
bottomSheetTheme: bottomSheetTheme ?? this.bottomSheetTheme,
buttonTheme: buttonTheme ?? this.buttonTheme,
cardTheme: cardTheme ?? this.cardTheme,
cardTheme: cardTheme as CardThemeData? ?? this.cardTheme,
checkboxTheme: checkboxTheme ?? this.checkboxTheme,
chipTheme: chipTheme ?? this.chipTheme,
dataTableTheme: dataTableTheme ?? this.dataTableTheme,
@ -1778,7 +1796,7 @@ class ThemeData with Diagnosticable {
bottomNavigationBarTheme: BottomNavigationBarThemeData.lerp(a.bottomNavigationBarTheme, b.bottomNavigationBarTheme, t),
bottomSheetTheme: BottomSheetThemeData.lerp(a.bottomSheetTheme, b.bottomSheetTheme, t)!,
buttonTheme: t < 0.5 ? a.buttonTheme : b.buttonTheme,
cardTheme: CardTheme.lerp(a.cardTheme, b.cardTheme, t),
cardTheme: CardThemeData.lerp(a.cardTheme, b.cardTheme, t),
checkboxTheme: CheckboxThemeData.lerp(a.checkboxTheme, b.checkboxTheme, t),
chipTheme: ChipThemeData.lerp(a.chipTheme, b.chipTheme, t)!,
dataTableTheme: DataTableThemeData.lerp(a.dataTableTheme, b.dataTableTheme, t),
@ -2076,7 +2094,7 @@ class ThemeData with Diagnosticable {
properties.add(DiagnosticsProperty<BottomNavigationBarThemeData>('bottomNavigationBarTheme', bottomNavigationBarTheme, defaultValue: defaultData.bottomNavigationBarTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<BottomSheetThemeData>('bottomSheetTheme', bottomSheetTheme, defaultValue: defaultData.bottomSheetTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<ButtonThemeData>('buttonTheme', buttonTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<CardTheme>('cardTheme', cardTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<CardThemeData>('cardTheme', cardTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<CheckboxThemeData>('checkboxTheme', checkboxTheme, defaultValue: defaultData.checkboxTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<ChipThemeData>('chipTheme', chipTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<DataTableThemeData>('dataTableTheme', dataTableTheme, defaultValue: defaultData.dataTableTheme, level: DiagnosticLevel.debug));

View File

@ -106,7 +106,7 @@ void main() {
});
testWidgets('Card uses values from CardTheme', (WidgetTester tester) async {
final CardTheme cardTheme = _cardTheme();
final CardThemeData cardTheme = _cardTheme();
await tester.pumpWidget(MaterialApp(
theme: ThemeData(cardTheme: cardTheme),
@ -163,7 +163,7 @@ void main() {
});
testWidgets('CardTheme properties take priority over ThemeData properties', (WidgetTester tester) async {
final CardTheme cardTheme = _cardTheme();
final CardThemeData cardTheme = _cardTheme();
final ThemeData themeData = _themeData().copyWith(cardTheme: cardTheme);
await tester.pumpWidget(MaterialApp(
@ -192,7 +192,7 @@ void main() {
});
testWidgets('Material3 - CardTheme customizes shape', (WidgetTester tester) async {
const CardTheme cardTheme = CardTheme(
const CardThemeData cardTheme = CardThemeData(
color: Colors.white,
shape: BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(7))),
elevation: 1.0,
@ -220,6 +220,120 @@ void main() {
);
});
testWidgets('Card properties are taken over the theme values', (WidgetTester tester) async {
const Clip themeClipBehavior = Clip.antiAlias;
const Color themeColor = Colors.red;
const Color themeShadowColor = Colors.orange;
const double themeElevation = 10.0;
const EdgeInsets themeMargin = EdgeInsets.all(12.0);
const ShapeBorder themeShape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(15.0)));
const Clip clipBehavior = Clip.hardEdge;
const Color color = Colors.yellow;
const Color shadowColor = Colors.green;
const double elevation = 20.0;
const EdgeInsets margin = EdgeInsets.all(18.0);
const ShapeBorder shape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(25.0)));
final ThemeData themeData = ThemeData(
cardTheme: const CardThemeData(
clipBehavior: themeClipBehavior,
color: themeColor,
shadowColor: themeShadowColor,
elevation: themeElevation,
margin: themeMargin,
shape: themeShape,
),
);
await tester.pumpWidget(MaterialApp(
theme: themeData,
home: const Scaffold(
body: Card(
clipBehavior: clipBehavior,
color: color,
shadowColor: shadowColor,
elevation: elevation,
margin: margin,
shape: shape,
child: SizedBox(
width: 200,
height: 200,
),
),
),
)
);
final Padding cardMargin = _getCardPadding(tester);
final Material material = _getCardMaterial(tester);
expect(material.clipBehavior, clipBehavior);
expect(material.color, color);
expect(material.shadowColor, shadowColor);
expect(material.elevation, elevation);
expect(material.shape, shape);
expect(cardMargin.padding, margin);
});
testWidgets('Local CardTheme can override global CardTheme', (WidgetTester tester) async {
const Clip globalClipBehavior = Clip.antiAlias;
const Color globalColor = Colors.red;
const Color globalShadowColor = Colors.orange;
const double globalElevation = 10.0;
const EdgeInsets globalMargin = EdgeInsets.all(12.0);
const ShapeBorder globalShape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(15.0)));
const Clip localClipBehavior = Clip.hardEdge;
const Color localColor = Colors.yellow;
const Color localShadowColor = Colors.green;
const double localElevation = 20.0;
const EdgeInsets localMargin = EdgeInsets.all(18.0);
const ShapeBorder localShape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(25.0)));
final ThemeData themeData = ThemeData(
cardTheme: const CardThemeData(
clipBehavior: globalClipBehavior,
color: globalColor,
shadowColor: globalShadowColor,
elevation: globalElevation,
margin: globalMargin,
shape: globalShape,
),
);
await tester.pumpWidget(MaterialApp(
theme: themeData,
home: const Scaffold(
body: CardTheme(
data: CardThemeData(
clipBehavior: localClipBehavior,
color: localColor,
shadowColor: localShadowColor,
elevation: localElevation,
margin: localMargin,
shape: localShape,
),
child: Card(
child: SizedBox(
width: 200,
height: 200,
),
),
),
),
));
final Padding cardMargin = _getCardPadding(tester);
final Material material = _getCardMaterial(tester);
expect(material.clipBehavior, localClipBehavior);
expect(material.color, localColor);
expect(material.shadowColor, localShadowColor);
expect(material.elevation, localElevation);
expect(material.shape, localShape);
expect(cardMargin.padding, localMargin);
});
group('Material 2', () {
// These tests are only relevant for Material 2. Once Material 2
// support is deprecated and the APIs are removed, these tests
@ -262,7 +376,7 @@ void main() {
});
testWidgets('Material2 - CardTheme customizes shape', (WidgetTester tester) async {
const CardTheme cardTheme = CardTheme(
const CardThemeData cardTheme = CardThemeData(
color: Colors.white,
shape: BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(7))),
elevation: 1.0,
@ -292,8 +406,8 @@ void main() {
});
}
CardTheme _cardTheme() {
return const CardTheme(
CardThemeData _cardTheme() {
return const CardThemeData(
clipBehavior: Clip.antiAlias,
color: Colors.green,
shadowColor: Colors.red,

View File

@ -2707,7 +2707,7 @@ void main() {
backgroundColor: const Color(0xffffff00)
),
),
cardTheme: const CardTheme(color: Color(0xff00ffff)),
cardTheme: const CardThemeData(color: Color(0xff00ffff)),
);
Widget buildSearchAnchor() {
return MaterialApp(

View File

@ -888,7 +888,7 @@ void main() {
bottomSheetTheme: const BottomSheetThemeData(backgroundColor: Colors.black),
buttonBarTheme: const ButtonBarThemeData(alignment: MainAxisAlignment.start),
buttonTheme: const ButtonThemeData(colorScheme: ColorScheme.dark()),
cardTheme: const CardTheme(color: Colors.black),
cardTheme: const CardThemeData(color: Colors.black),
checkboxTheme: const CheckboxThemeData(),
chipTheme: chipTheme,
dataTableTheme: const DataTableThemeData(),
@ -1002,7 +1002,7 @@ void main() {
bottomSheetTheme: const BottomSheetThemeData(backgroundColor: Colors.white),
buttonBarTheme: const ButtonBarThemeData(alignment: MainAxisAlignment.end),
buttonTheme: const ButtonThemeData(colorScheme: ColorScheme.light()),
cardTheme: const CardTheme(color: Colors.white),
cardTheme: const CardThemeData(color: Colors.white),
checkboxTheme: const CheckboxThemeData(),
chipTheme: otherChipTheme,
dataTableTheme: const DataTableThemeData(),

View File

@ -60,7 +60,7 @@ class ThemedCard extends SingleChildRenderObjectWidget {
@override
RenderPhysicalShape createRenderObject(BuildContext context) {
final CardThemeData cardTheme = CardTheme.of(context).data;
final CardThemeData cardTheme = CardTheme.of(context);
return RenderPhysicalShape(
clipper: ShapeBorderClipper(shape: cardTheme.shape ?? const RoundedRectangleBorder()),
@ -73,7 +73,7 @@ class ThemedCard extends SingleChildRenderObjectWidget {
@override
void updateRenderObject(BuildContext context, RenderPhysicalShape renderObject) {
final CardThemeData cardTheme = CardTheme.of(context).data;
final CardThemeData cardTheme = CardTheme.of(context);
renderObject
..clipper = ShapeBorderClipper(shape: cardTheme.shape ?? const RoundedRectangleBorder())