Skip to content

Commit d9bf879

Browse files
authored
Add shadowColor to AppBar and AppBarTheme (flutter#58708)
1 parent 6202476 commit d9bf879

File tree

3 files changed

+70
-5
lines changed

3 files changed

+70
-5
lines changed

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
175175
/// and [automaticallyImplyLeading] must not be null. Additionally, if
176176
/// [elevation] is specified, it must be non-negative.
177177
///
178-
/// If [backgroundColor], [elevation], [brightness], [iconTheme],
178+
/// If [backgroundColor], [elevation], [shadowColor], [brightness], [iconTheme],
179179
/// [actionsIconTheme], [textTheme] or [centerTitle] are null, then their
180180
/// [AppBarTheme] values will be used. If the corresponding [AppBarTheme] property is null,
181181
/// then the default specified in the property's documentation will be used.
@@ -190,6 +190,7 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
190190
this.flexibleSpace,
191191
this.bottom,
192192
this.elevation,
193+
this.shadowColor,
193194
this.shape,
194195
this.backgroundColor,
195196
this.brightness,
@@ -338,6 +339,13 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
338339
/// for app bars.
339340
final double elevation;
340341

342+
/// The color to paint the shadow below the app bar.
343+
///
344+
/// If this property is null, then [ThemeData.appBarTheme.shadowColor] is used,
345+
/// if that is also null, the default value is fully opaque black, the appropriate
346+
/// color for shadows.
347+
final Color shadowColor;
348+
341349
/// The material's shape as well its shadow.
342350
///
343351
/// A shadow is only displayed if the [elevation] is greater than
@@ -456,6 +464,7 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
456464

457465
class _AppBarState extends State<AppBar> {
458466
static const double _defaultElevation = 4.0;
467+
static const Color _defaultShadowColor = Color(0xFF000000);
459468

460469
void _handleDrawerButton() {
461470
Scaffold.of(context).openDrawer();
@@ -665,6 +674,9 @@ class _AppBarState extends State<AppBar> {
665674
elevation: widget.elevation
666675
?? appBarTheme.elevation
667676
?? _defaultElevation,
677+
shadowColor: widget.shadowColor
678+
?? appBarTheme.shadowColor
679+
?? _defaultShadowColor,
668680
shape: widget.shape,
669681
child: Semantics(
670682
explicitChildNodes: true,
@@ -737,6 +749,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
737749
@required this.flexibleSpace,
738750
@required this.bottom,
739751
@required this.elevation,
752+
@required this.shadowColor,
740753
@required this.forceElevated,
741754
@required this.backgroundColor,
742755
@required this.brightness,
@@ -765,6 +778,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
765778
final Widget flexibleSpace;
766779
final PreferredSizeWidget bottom;
767780
final double elevation;
781+
final Color shadowColor;
768782
final bool forceElevated;
769783
final Color backgroundColor;
770784
final Brightness brightness;
@@ -822,6 +836,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
822836
: flexibleSpace,
823837
bottom: bottom,
824838
elevation: forceElevated || overlapsContent || (pinned && shrinkOffset > maxExtent - minExtent) ? elevation ?? 4.0 : 0.0,
839+
shadowColor: shadowColor,
825840
backgroundColor: backgroundColor,
826841
brightness: brightness,
827842
iconTheme: iconTheme,
@@ -849,6 +864,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
849864
|| bottom != oldDelegate.bottom
850865
|| _bottomHeight != oldDelegate._bottomHeight
851866
|| elevation != oldDelegate.elevation
867+
|| shadowColor != oldDelegate.shadowColor
852868
|| backgroundColor != oldDelegate.backgroundColor
853869
|| brightness != oldDelegate.brightness
854870
|| iconTheme != oldDelegate.iconTheme
@@ -964,6 +980,7 @@ class SliverAppBar extends StatefulWidget {
964980
this.flexibleSpace,
965981
this.bottom,
966982
this.elevation,
983+
this.shadowColor,
967984
this.forceElevated = false,
968985
this.backgroundColor,
969986
this.brightness,
@@ -1081,6 +1098,14 @@ class SliverAppBar extends StatefulWidget {
10811098
/// shadow is drawn, regardless of the value of [elevation].
10821099
final double elevation;
10831100

1101+
/// The color to paint the shadow below the app bar. Typically this should be set
1102+
/// along with [elevation].
1103+
///
1104+
/// If this property is null, then [ThemeData.appBarTheme.shadowColor] is used,
1105+
/// if that is also null, the default value is fully opaque black, the appropriate
1106+
/// color for shadows.
1107+
final Color shadowColor;
1108+
10841109
/// Whether to show the shadow appropriate for the [elevation] even if the
10851110
/// content is not scrolled under the [AppBar].
10861111
///
@@ -1339,6 +1364,7 @@ class _SliverAppBarState extends State<SliverAppBar> with TickerProviderStateMix
13391364
flexibleSpace: widget.flexibleSpace,
13401365
bottom: widget.bottom,
13411366
elevation: widget.elevation,
1367+
shadowColor: widget.shadowColor,
13421368
forceElevated: widget.forceElevated,
13431369
backgroundColor: widget.backgroundColor,
13441370
brightness: widget.brightness,

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class AppBarTheme with Diagnosticable {
3636
this.brightness,
3737
this.color,
3838
this.elevation,
39+
this.shadowColor,
3940
this.iconTheme,
4041
this.actionsIconTheme,
4142
this.textTheme,
@@ -57,6 +58,11 @@ class AppBarTheme with Diagnosticable {
5758
/// If null, [AppBar] uses a default value of 4.0.
5859
final double elevation;
5960

61+
/// Default value for [AppBar.shadowColor].
62+
///
63+
/// If null, [AppBar] uses a default value of fully opaque black.
64+
final Color shadowColor;
65+
6066
/// Default value for [AppBar.iconTheme].
6167
///
6268
/// If null, [AppBar] uses [ThemeData.primaryIconTheme].
@@ -84,6 +90,7 @@ class AppBarTheme with Diagnosticable {
8490
Brightness brightness,
8591
Color color,
8692
double elevation,
93+
Color shadowColor,
8794
IconThemeData iconTheme,
8895
TextTheme textTheme,
8996
bool centerTitle,
@@ -92,6 +99,7 @@ class AppBarTheme with Diagnosticable {
9299
brightness: brightness ?? this.brightness,
93100
color: color ?? this.color,
94101
elevation: elevation ?? this.elevation,
102+
shadowColor: shadowColor ?? this.shadowColor,
95103
iconTheme: iconTheme ?? this.iconTheme,
96104
actionsIconTheme: actionsIconTheme ?? this.actionsIconTheme,
97105
textTheme: textTheme ?? this.textTheme,
@@ -115,6 +123,7 @@ class AppBarTheme with Diagnosticable {
115123
brightness: t < 0.5 ? a?.brightness : b?.brightness,
116124
color: Color.lerp(a?.color, b?.color, t),
117125
elevation: lerpDouble(a?.elevation, b?.elevation, t),
126+
shadowColor: Color.lerp(a?.shadowColor, b?.shadowColor, t),
118127
iconTheme: IconThemeData.lerp(a?.iconTheme, b?.iconTheme, t),
119128
actionsIconTheme: IconThemeData.lerp(a?.actionsIconTheme, b?.actionsIconTheme, t),
120129
textTheme: TextTheme.lerp(a?.textTheme, b?.textTheme, t),
@@ -128,6 +137,7 @@ class AppBarTheme with Diagnosticable {
128137
brightness,
129138
color,
130139
elevation,
140+
shadowColor,
131141
iconTheme,
132142
actionsIconTheme,
133143
textTheme,
@@ -145,6 +155,7 @@ class AppBarTheme with Diagnosticable {
145155
&& other.brightness == brightness
146156
&& other.color == color
147157
&& other.elevation == elevation
158+
&& other.shadowColor == shadowColor
148159
&& other.iconTheme == iconTheme
149160
&& other.actionsIconTheme == actionsIconTheme
150161
&& other.textTheme == textTheme
@@ -157,6 +168,7 @@ class AppBarTheme with Diagnosticable {
157168
properties.add(DiagnosticsProperty<Brightness>('brightness', brightness, defaultValue: null));
158169
properties.add(ColorProperty('color', color, defaultValue: null));
159170
properties.add(DiagnosticsProperty<double>('elevation', elevation, defaultValue: null));
171+
properties.add(ColorProperty('shadowColor', shadowColor, defaultValue: null));
160172
properties.add(DiagnosticsProperty<IconThemeData>('iconTheme', iconTheme, defaultValue: null));
161173
properties.add(DiagnosticsProperty<IconThemeData>('actionsIconTheme', actionsIconTheme, defaultValue: null));
162174
properties.add(DiagnosticsProperty<TextTheme>('textTheme', textTheme, defaultValue: null));

packages/flutter/test/material/app_bar_theme_test.dart

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ void main() {
3333
expect(SystemChrome.latestStyle.statusBarBrightness, Brightness.dark);
3434
expect(widget.color, Colors.blue);
3535
expect(widget.elevation, 4.0);
36+
expect(widget.shadowColor, Colors.black);
3637
expect(iconTheme.data, const IconThemeData(color: Colors.white));
3738
expect(actionsIconTheme.data, const IconThemeData(color: Colors.white));
3839
expect(actionIconText.text.style.color, Colors.white);
@@ -61,6 +62,7 @@ void main() {
6162
expect(SystemChrome.latestStyle.statusBarBrightness, appBarTheme.brightness);
6263
expect(widget.color, appBarTheme.color);
6364
expect(widget.elevation, appBarTheme.elevation);
65+
expect(widget.shadowColor, appBarTheme.shadowColor);
6466
expect(iconTheme.data, appBarTheme.iconTheme);
6567
expect(actionsIconTheme.data, appBarTheme.actionsIconTheme);
6668
expect(actionIconText.text.style.color, appBarTheme.actionsIconTheme.color);
@@ -71,6 +73,7 @@ void main() {
7173
const Brightness brightness = Brightness.dark;
7274
const Color color = Colors.orange;
7375
const double elevation = 3.0;
76+
const Color shadowColor = Colors.red;
7477
const IconThemeData iconThemeData = IconThemeData(color: Colors.green);
7578
const IconThemeData actionsIconThemeData = IconThemeData(color: Colors.lightBlue);
7679
const TextTheme textTheme = TextTheme(headline6: TextStyle(color: Colors.orange), bodyText2: TextStyle(color: Colors.pink));
@@ -83,6 +86,7 @@ void main() {
8386
backgroundColor: color,
8487
brightness: brightness,
8588
elevation: elevation,
89+
shadowColor: shadowColor,
8690
iconTheme: iconThemeData,
8791
actionsIconTheme: actionsIconThemeData,
8892
textTheme: textTheme,
@@ -101,6 +105,7 @@ void main() {
101105
expect(SystemChrome.latestStyle.statusBarBrightness, brightness);
102106
expect(widget.color, color);
103107
expect(widget.elevation, elevation);
108+
expect(widget.shadowColor, shadowColor);
104109
expect(iconTheme.data, iconThemeData);
105110
expect(actionsIconTheme.data, actionsIconThemeData);
106111
expect(actionIconText.text.style.color, actionsIconThemeData.color);
@@ -151,6 +156,7 @@ void main() {
151156
expect(SystemChrome.latestStyle.statusBarBrightness, appBarTheme.brightness);
152157
expect(widget.color, appBarTheme.color);
153158
expect(widget.elevation, appBarTheme.elevation);
159+
expect(widget.shadowColor, appBarTheme.shadowColor);
154160
expect(iconTheme.data, appBarTheme.iconTheme);
155161
expect(actionsIconTheme.data, appBarTheme.actionsIconTheme);
156162
expect(actionIconText.text.style.color, appBarTheme.actionsIconTheme.color);
@@ -178,6 +184,7 @@ void main() {
178184
expect(SystemChrome.latestStyle.statusBarBrightness, themeData.brightness);
179185
expect(widget.color, themeData.primaryColor);
180186
expect(widget.elevation, 4.0);
187+
expect(widget.shadowColor, Colors.black);
181188
expect(iconTheme.data, themeData.primaryIconTheme);
182189
expect(actionsIconTheme.data, themeData.primaryIconTheme);
183190
expect(actionIconText.text.style.color, themeData.primaryIconTheme.color);
@@ -199,10 +206,11 @@ void main() {
199206
await tester.pumpWidget(MaterialApp(
200207
theme: ThemeData(appBarTheme: const AppBarTheme(centerTitle: true)),
201208
home: Scaffold(
202-
appBar: AppBar(
203-
title: const Text('Title'),
204-
centerTitle: false,
205-
)),
209+
appBar: AppBar(
210+
title: const Text('Title'),
211+
centerTitle: false,
212+
),
213+
),
206214
));
207215

208216
final NavigationToolbar navToolBar = tester.widget(find.byType(NavigationToolbar));
@@ -221,12 +229,29 @@ void main() {
221229
// the value of NavigationToolBar.centerMiddle should be true.
222230
expect(navToolBar.centerMiddle, true);
223231
});
232+
233+
testWidgets('AppBar.shadowColor takes priority over AppBarTheme.shadowColor', (WidgetTester tester) async {
234+
await tester.pumpWidget(MaterialApp(
235+
theme: ThemeData(appBarTheme: const AppBarTheme(shadowColor: Colors.red)),
236+
home: Scaffold(
237+
appBar: AppBar(
238+
title: const Text('Title'),
239+
shadowColor: Colors.yellow,
240+
),
241+
),
242+
));
243+
244+
final AppBar appBar = tester.widget(find.byType(AppBar));
245+
// The AppBar.shadowColor should be used instead of AppBarTheme.shadowColor.
246+
expect(appBar.shadowColor, Colors.yellow);
247+
});
224248
}
225249

226250
AppBarTheme _appBarTheme() {
227251
const Brightness brightness = Brightness.light;
228252
const Color color = Colors.lightBlue;
229253
const double elevation = 6.0;
254+
const Color shadowColor = Colors.red;
230255
const IconThemeData iconThemeData = IconThemeData(color: Colors.black);
231256
const IconThemeData actionsIconThemeData = IconThemeData(color: Colors.pink);
232257
const TextTheme textTheme = TextTheme(bodyText2: TextStyle(color: Colors.yellow));
@@ -235,6 +260,7 @@ AppBarTheme _appBarTheme() {
235260
brightness: brightness,
236261
color: color,
237262
elevation: elevation,
263+
shadowColor: shadowColor,
238264
iconTheme: iconThemeData,
239265
textTheme: textTheme,
240266
);
@@ -284,6 +310,7 @@ RichText _getAppBarIconRichText(WidgetTester tester) {
284310
).first,
285311
);
286312
}
313+
287314
DefaultTextStyle _getAppBarText(WidgetTester tester) {
288315
return tester.widget<DefaultTextStyle>(
289316
find.descendant(

0 commit comments

Comments
 (0)