Skip to content

Commit d3dcd7d

Browse files
authored
Update CircleAvatar to support Material 3 (flutter#114812)
1 parent 92f10ed commit d3dcd7d

File tree

2 files changed

+73
-37
lines changed

2 files changed

+73
-37
lines changed

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ class CircleAvatar extends StatelessWidget {
8484
/// The color with which to fill the circle. Changing the background
8585
/// color will cause the avatar to animate to the new color.
8686
///
87-
/// If a [backgroundColor] is not specified, the theme's
87+
/// If a [backgroundColor] is not specified and [ThemeData.useMaterial3] is true,
88+
/// [ColorScheme.primaryContainer] will be used, otherwise the theme's
8889
/// [ThemeData.primaryColorLight] is used with dark foreground colors, and
8990
/// [ThemeData.primaryColorDark] with light foreground colors.
9091
final Color? backgroundColor;
@@ -94,7 +95,9 @@ class CircleAvatar extends StatelessWidget {
9495
/// Defaults to the primary text theme color if no [backgroundColor] is
9596
/// specified.
9697
///
97-
/// Defaults to [ThemeData.primaryColorLight] for dark background colors, and
98+
/// If a [foregroundColor] is not specified and [ThemeData.useMaterial3] is true,
99+
/// [ColorScheme.onPrimaryContainer] will be used, otherwise the theme's
100+
/// [ThemeData.primaryColorLight] for dark background colors, and
98101
/// [ThemeData.primaryColorDark] for light background colors.
99102
final Color? foregroundColor;
100103

@@ -192,8 +195,14 @@ class CircleAvatar extends StatelessWidget {
192195
Widget build(BuildContext context) {
193196
assert(debugCheckHasMediaQuery(context));
194197
final ThemeData theme = Theme.of(context);
195-
TextStyle textStyle = theme.primaryTextTheme.titleMedium!.copyWith(color: foregroundColor);
196-
Color? effectiveBackgroundColor = backgroundColor;
198+
final Color? effectiveForegroundColor = foregroundColor
199+
?? (theme.useMaterial3 ? theme.colorScheme.onPrimaryContainer : null);
200+
final TextStyle effectiveTextStyle = theme.useMaterial3
201+
? theme.textTheme.titleMedium!
202+
: theme.primaryTextTheme.titleMedium!;
203+
TextStyle textStyle = effectiveTextStyle.copyWith(color: effectiveForegroundColor);
204+
Color? effectiveBackgroundColor = backgroundColor
205+
?? (theme.useMaterial3 ? theme.colorScheme.primaryContainer : null);
197206
if (effectiveBackgroundColor == null) {
198207
switch (ThemeData.estimateBrightnessForColor(textStyle.color!)) {
199208
case Brightness.dark:
@@ -203,7 +212,7 @@ class CircleAvatar extends StatelessWidget {
203212
effectiveBackgroundColor = theme.primaryColorDark;
204213
break;
205214
}
206-
} else if (foregroundColor == null) {
215+
} else if (effectiveForegroundColor == null) {
207216
switch (ThemeData.estimateBrightnessForColor(backgroundColor!)) {
208217
case Brightness.dark:
209218
textStyle = textStyle.copyWith(color: theme.primaryColorLight);

packages/flutter/test/material/circle_avatar_test.dart

Lines changed: 59 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -144,36 +144,8 @@ void main() {
144144
expect(paragraph.text.style!.color, equals(foregroundColor));
145145
});
146146

147-
testWidgets('CircleAvatar with light theme', (WidgetTester tester) async {
148-
final ThemeData theme = ThemeData(
149-
primaryColor: Colors.grey.shade100,
150-
primaryColorBrightness: Brightness.light,
151-
);
152-
await tester.pumpWidget(
153-
wrap(
154-
child: Theme(
155-
data: theme,
156-
child: const CircleAvatar(
157-
child: Text('Z'),
158-
),
159-
),
160-
),
161-
);
162-
163-
final RenderConstrainedBox box = tester.renderObject(find.byType(CircleAvatar));
164-
final RenderDecoratedBox child = box.child! as RenderDecoratedBox;
165-
final BoxDecoration decoration = child.decoration as BoxDecoration;
166-
expect(decoration.color, equals(theme.primaryColorLight));
167-
168-
final RenderParagraph paragraph = tester.renderObject(find.text('Z'));
169-
expect(paragraph.text.style!.color, equals(theme.primaryTextTheme.titleLarge!.color));
170-
});
171-
172-
testWidgets('CircleAvatar with dark theme', (WidgetTester tester) async {
173-
final ThemeData theme = ThemeData(
174-
primaryColor: Colors.grey.shade800,
175-
primaryColorBrightness: Brightness.dark,
176-
);
147+
testWidgets('CircleAvatar default colors', (WidgetTester tester) async {
148+
final ThemeData theme = ThemeData(useMaterial3: true);
177149
await tester.pumpWidget(
178150
wrap(
179151
child: Theme(
@@ -188,10 +160,10 @@ void main() {
188160
final RenderConstrainedBox box = tester.renderObject(find.byType(CircleAvatar));
189161
final RenderDecoratedBox child = box.child! as RenderDecoratedBox;
190162
final BoxDecoration decoration = child.decoration as BoxDecoration;
191-
expect(decoration.color, equals(theme.primaryColorDark));
163+
expect(decoration.color, equals(theme.colorScheme.primaryContainer));
192164

193165
final RenderParagraph paragraph = tester.renderObject(find.text('Z'));
194-
expect(paragraph.text.style!.color, equals(theme.primaryTextTheme.titleLarge!.color));
166+
expect(paragraph.text.style!.color, equals(theme.colorScheme.onPrimaryContainer));
195167
});
196168

197169
testWidgets('CircleAvatar text does not expand with textScaleFactor', (WidgetTester tester) async {
@@ -306,6 +278,61 @@ void main() {
306278
final RenderParagraph paragraph = tester.renderObject(find.text('Z'));
307279
expect(paragraph.text.style!.color, equals(ThemeData.fallback().primaryColorLight));
308280
});
281+
282+
group('Material 2', () {
283+
// Tests that are only relevant for Material 2. Once ThemeData.useMaterial3
284+
// is turned on by default, these tests can be removed.
285+
286+
testWidgets('CircleAvatar default colors with light theme', (WidgetTester tester) async {
287+
final ThemeData theme = ThemeData(
288+
primaryColor: Colors.grey.shade100,
289+
primaryColorBrightness: Brightness.light,
290+
);
291+
await tester.pumpWidget(
292+
wrap(
293+
child: Theme(
294+
data: theme,
295+
child: const CircleAvatar(
296+
child: Text('Z'),
297+
),
298+
),
299+
),
300+
);
301+
302+
final RenderConstrainedBox box = tester.renderObject(find.byType(CircleAvatar));
303+
final RenderDecoratedBox child = box.child! as RenderDecoratedBox;
304+
final BoxDecoration decoration = child.decoration as BoxDecoration;
305+
expect(decoration.color, equals(theme.primaryColorLight));
306+
307+
final RenderParagraph paragraph = tester.renderObject(find.text('Z'));
308+
expect(paragraph.text.style!.color, equals(theme.primaryTextTheme.titleLarge!.color));
309+
});
310+
311+
testWidgets('CircleAvatar default colors with dark theme', (WidgetTester tester) async {
312+
final ThemeData theme = ThemeData(
313+
primaryColor: Colors.grey.shade800,
314+
primaryColorBrightness: Brightness.dark,
315+
);
316+
await tester.pumpWidget(
317+
wrap(
318+
child: Theme(
319+
data: theme,
320+
child: const CircleAvatar(
321+
child: Text('Z'),
322+
),
323+
),
324+
),
325+
);
326+
327+
final RenderConstrainedBox box = tester.renderObject(find.byType(CircleAvatar));
328+
final RenderDecoratedBox child = box.child! as RenderDecoratedBox;
329+
final BoxDecoration decoration = child.decoration as BoxDecoration;
330+
expect(decoration.color, equals(theme.primaryColorDark));
331+
332+
final RenderParagraph paragraph = tester.renderObject(find.text('Z'));
333+
expect(paragraph.text.style!.color, equals(theme.primaryTextTheme.titleLarge!.color));
334+
});
335+
});
309336
}
310337

311338
Widget wrap({ required Widget child }) {

0 commit comments

Comments
 (0)