From cd4fe85bfaeb75b55003a7db28eea2a3707f3df5 Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Wed, 14 Apr 2021 17:01:08 -0700 Subject: [PATCH] Added support for AppBarTheme.toolbarHeight (#80467) --- .../flutter/lib/src/material/app_bar.dart | 24 ++++++++- .../lib/src/material/app_bar_theme.dart | 16 ++++++ .../flutter/lib/src/material/scaffold.dart | 2 +- .../flutter/test/material/app_bar_test.dart | 53 +++++++++++++++++++ .../test/material/app_bar_theme_test.dart | 49 ++++++++++------- 5 files changed, 123 insertions(+), 21 deletions(-) diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index ee37391d73e..dbc226de9b2 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart @@ -54,6 +54,14 @@ class _ToolbarContainerLayout extends SingleChildLayoutDelegate { toolbarHeight != oldDelegate.toolbarHeight; } +class _PreferredAppBarSize extends Size { + _PreferredAppBarSize(this.toolbarHeight, this.bottomHeight) + : super.fromHeight((toolbarHeight ?? kToolbarHeight) + (bottomHeight ?? 0)); + + final double? toolbarHeight; + final double? bottomHeight; +} + /// A material design app bar. /// /// An app bar consists of a toolbar and potentially other widgets, such as a @@ -202,9 +210,21 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget { assert(primary != null), assert(toolbarOpacity != null), assert(bottomOpacity != null), - preferredSize = Size.fromHeight(toolbarHeight ?? kToolbarHeight + (bottom?.preferredSize.height ?? 0.0)), + preferredSize = _PreferredAppBarSize(toolbarHeight, bottom?.preferredSize.height), super(key: key); + /// Used by [Scaffold] to compute its [AppBar]'s overall height. The returned value is + /// the same `preferredSize.height` unless [AppBar.toolbarHeight] was null and + /// `AppBarTheme.of(context).toolbarHeight` is non-null. In that case the + /// return value is the sum of the theme's toolbar height and the height of + /// the app bar's [AppBar.bottom] widget. + static double preferredHeightFor(BuildContext context, Size preferredSize) { + if (preferredSize is _PreferredAppBarSize && preferredSize.toolbarHeight == null) { + return (AppBarTheme.of(context).toolbarHeight ?? kToolbarHeight) + (preferredSize.bottomHeight ?? 0); + } + return preferredSize.height; + } + /// {@template flutter.material.appbar.leading} /// A widget to display before the toolbar's [title]. /// @@ -781,7 +801,7 @@ class _AppBarState extends State { final bool canPop = parentRoute?.canPop ?? false; final bool useCloseButton = parentRoute is PageRoute && parentRoute.fullscreenDialog; - final double toolbarHeight = widget.toolbarHeight ?? kToolbarHeight; + final double toolbarHeight = widget.toolbarHeight ?? appBarTheme.toolbarHeight ?? kToolbarHeight; final bool backwardsCompatibility = widget.backwardsCompatibility ?? appBarTheme.backwardsCompatibility ?? true; final Color backgroundColor = backwardsCompatibility diff --git a/packages/flutter/lib/src/material/app_bar_theme.dart b/packages/flutter/lib/src/material/app_bar_theme.dart index cb153cffc68..263af6416ed 100644 --- a/packages/flutter/lib/src/material/app_bar_theme.dart +++ b/packages/flutter/lib/src/material/app_bar_theme.dart @@ -39,6 +39,7 @@ class AppBarTheme with Diagnosticable { this.textTheme, this.centerTitle, this.titleSpacing, + this.toolbarHeight, this.toolbarTextStyle, this.titleTextStyle, this.systemOverlayStyle, @@ -144,6 +145,15 @@ class AppBarTheme with Diagnosticable { /// If null, [AppBar] uses default value of [NavigationToolbar.kMiddleSpacing]. final double? titleSpacing; + /// Overrides the default value for the [AppBar.toolbarHeight] + /// property in all descendant [AppBar] widgets. + /// + /// See also: + /// + /// * [AppBar.preferredHeightFor], which computes the overall + /// height of an AppBar widget, taking this value into account. + final double? toolbarHeight; + /// Overrides the default value for the obsolete [AppBar.toolbarTextStyle] /// property in all descendant [AppBar] widgets. /// @@ -184,6 +194,7 @@ class AppBarTheme with Diagnosticable { TextTheme? textTheme, bool? centerTitle, double? titleSpacing, + double? toolbarHeight, TextStyle? toolbarTextStyle, TextStyle? titleTextStyle, SystemUiOverlayStyle? systemOverlayStyle, @@ -204,6 +215,7 @@ class AppBarTheme with Diagnosticable { textTheme: textTheme ?? this.textTheme, centerTitle: centerTitle ?? this.centerTitle, titleSpacing: titleSpacing ?? this.titleSpacing, + toolbarHeight: toolbarHeight ?? this.toolbarHeight, toolbarTextStyle: toolbarTextStyle ?? this.toolbarTextStyle, titleTextStyle: titleTextStyle ?? this.titleTextStyle, systemOverlayStyle: systemOverlayStyle ?? this.systemOverlayStyle, @@ -234,6 +246,7 @@ class AppBarTheme with Diagnosticable { textTheme: TextTheme.lerp(a?.textTheme, b?.textTheme, t), centerTitle: t < 0.5 ? a?.centerTitle : b?.centerTitle, titleSpacing: lerpDouble(a?.titleSpacing, b?.titleSpacing, t), + toolbarHeight: lerpDouble(a?.toolbarHeight, b?.toolbarHeight, t), toolbarTextStyle: TextStyle.lerp(a?.toolbarTextStyle, b?.toolbarTextStyle, t), titleTextStyle: TextStyle.lerp(a?.titleTextStyle, b?.titleTextStyle, t), systemOverlayStyle: t < 0.5 ? a?.systemOverlayStyle : b?.systemOverlayStyle, @@ -254,6 +267,7 @@ class AppBarTheme with Diagnosticable { textTheme, centerTitle, titleSpacing, + toolbarHeight, toolbarTextStyle, titleTextStyle, systemOverlayStyle, @@ -278,6 +292,7 @@ class AppBarTheme with Diagnosticable { && other.textTheme == textTheme && other.centerTitle == centerTitle && other.titleSpacing == titleSpacing + && other.toolbarHeight == toolbarHeight && other.toolbarTextStyle == toolbarTextStyle && other.titleTextStyle == titleTextStyle && other.systemOverlayStyle == systemOverlayStyle @@ -297,6 +312,7 @@ class AppBarTheme with Diagnosticable { properties.add(DiagnosticsProperty('textTheme', textTheme, defaultValue: null)); properties.add(DiagnosticsProperty('centerTitle', centerTitle, defaultValue: null)); properties.add(DiagnosticsProperty('titleSpacing', titleSpacing, defaultValue: null)); + properties.add(DiagnosticsProperty('toolbarHeight', toolbarHeight, defaultValue: null)); properties.add(DiagnosticsProperty('toolbarTextStyle', toolbarTextStyle, defaultValue: null)); properties.add(DiagnosticsProperty('titleTextStyle', titleTextStyle, defaultValue: null)); properties.add(DiagnosticsProperty('backwardsCompatibility', backwardsCompatibility, defaultValue: null)); diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index c142a9217e7..09157634ddc 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -3035,7 +3035,7 @@ class ScaffoldState extends State with TickerProviderStateMixin, Resto if (widget.appBar != null) { final double topPadding = widget.primary ? mediaQuery.padding.top : 0.0; - _appBarMaxHeight = widget.appBar!.preferredSize.height + topPadding; + _appBarMaxHeight = AppBar.preferredHeightFor(context, widget.appBar!.preferredSize) + topPadding; assert(_appBarMaxHeight! >= 0.0 && _appBarMaxHeight!.isFinite); _addIfNonNull( children, diff --git a/packages/flutter/test/material/app_bar_test.dart b/packages/flutter/test/material/app_bar_test.dart index 02bee22c75f..c43073dd0a1 100644 --- a/packages/flutter/test/material/app_bar_test.dart +++ b/packages/flutter/test/material/app_bar_test.dart @@ -2822,4 +2822,57 @@ void main() { expect(tester.takeException(), isNull); }); + + testWidgets('AppBar.preferredHeightFor', (WidgetTester tester) async { + late double preferredHeight; + late Size preferredSize; + + Widget buildFrame({ double? themeToolbarHeight, double? appBarToolbarHeight }) { + final AppBar appBar = AppBar( + toolbarHeight: appBarToolbarHeight, + ); + return MaterialApp( + theme: ThemeData.light().copyWith( + appBarTheme: AppBarTheme( + toolbarHeight: themeToolbarHeight, + ), + ), + home: Builder( + builder: (BuildContext context) { + preferredHeight = AppBar.preferredHeightFor(context, appBar.preferredSize); + preferredSize = appBar.preferredSize; + return Scaffold( + appBar: appBar, + body: const Placeholder(), + ); + }, + ), + ); + } + + await tester.pumpWidget(buildFrame()); + expect(tester.getSize(find.byType(AppBar)).height, kToolbarHeight); + expect(preferredHeight, kToolbarHeight); + expect(preferredSize.height, kToolbarHeight); + + await tester.pumpWidget(buildFrame(themeToolbarHeight: 96)); + await tester.pumpAndSettle(); // Animate MaterialApp theme change. + expect(tester.getSize(find.byType(AppBar)).height, 96); + expect(preferredHeight, 96); + // Special case: AppBarTheme.toolbarHeight specified, + // AppBar.theme.toolbarHeight is null. + expect(preferredSize.height, kToolbarHeight); + + await tester.pumpWidget(buildFrame(appBarToolbarHeight: 64)); + await tester.pumpAndSettle(); // Animate MaterialApp theme change. + expect(tester.getSize(find.byType(AppBar)).height, 64); + expect(preferredHeight, 64); + expect(preferredSize.height, 64); + + await tester.pumpWidget(buildFrame(appBarToolbarHeight: 64, themeToolbarHeight: 96)); + await tester.pumpAndSettle(); // Animate MaterialApp theme change. + expect(tester.getSize(find.byType(AppBar)).height, 64); + expect(preferredHeight, 64); + expect(preferredSize.height, 64); + }); } diff --git a/packages/flutter/test/material/app_bar_theme_test.dart b/packages/flutter/test/material/app_bar_theme_test.dart index d371bd428f3..b2bb735f8e7 100644 --- a/packages/flutter/test/material/app_bar_theme_test.dart +++ b/packages/flutter/test/material/app_bar_theme_test.dart @@ -15,14 +15,18 @@ void main() { }); testWidgets('Passing no AppBarTheme returns defaults', (WidgetTester tester) async { - await tester.pumpWidget(MaterialApp( - home: Scaffold(appBar: AppBar( - backwardsCompatibility: false, - actions: [ - IconButton(icon: const Icon(Icons.share), onPressed: () { }), - ], - )), - )); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + appBar: AppBar( + backwardsCompatibility: false, + actions: [ + IconButton(icon: const Icon(Icons.share), onPressed: () { }), + ], + ), + ), + ), + ); final Material widget = _getAppBarMaterial(tester); final IconTheme iconTheme = _getAppBarIconTheme(tester); @@ -38,21 +42,27 @@ void main() { expect(actionsIconTheme.data, const IconThemeData(color: Colors.white)); expect(actionIconText.text.style!.color, Colors.white); expect(text.style, Typography.material2014().englishLike.bodyText2!.merge(Typography.material2014().white.bodyText2)); + expect(tester.getSize(find.byType(AppBar)).height, kToolbarHeight); + expect(tester.getSize(find.byType(AppBar)).width, 800); }); testWidgets('AppBar uses values from AppBarTheme', (WidgetTester tester) async { final AppBarTheme appBarTheme = _appBarTheme(); - await tester.pumpWidget(MaterialApp( - theme: ThemeData(appBarTheme: appBarTheme), - home: Scaffold(appBar: AppBar( - backwardsCompatibility: false, - title: const Text('App Bar Title'), - actions: [ - IconButton(icon: const Icon(Icons.share), onPressed: () { }), - ], - )), - )); + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(appBarTheme: appBarTheme), + home: Scaffold( + appBar: AppBar( + backwardsCompatibility: false, + title: const Text('App Bar Title'), + actions: [ + IconButton(icon: const Icon(Icons.share), onPressed: () { }), + ], + ), + ), + ), + ); final Material widget = _getAppBarMaterial(tester); final IconTheme iconTheme = _getAppBarIconTheme(tester); @@ -68,6 +78,8 @@ void main() { expect(actionsIconTheme.data, appBarTheme.actionsIconTheme); expect(actionIconText.text.style!.color, appBarTheme.actionsIconTheme!.color); expect(text.style, appBarTheme.toolbarTextStyle); + expect(tester.getSize(find.byType(AppBar)).height, appBarTheme.toolbarHeight); + expect(tester.getSize(find.byType(AppBar)).width, 800); }); testWidgets('SliverAppBar allows AppBar to determine backwardsCompatibility', (WidgetTester tester) async { @@ -531,6 +543,7 @@ AppBarTheme _appBarTheme() { elevation: elevation, shadowColor: shadowColor, iconTheme: iconThemeData, + toolbarHeight: 96, toolbarTextStyle: TextStyle(color: Colors.yellow), titleTextStyle: TextStyle(color: Colors.pink), );