diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index 5c43195c94c..8a1999f5d79 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart @@ -26,6 +26,7 @@ import 'text_theme.dart'; import 'theme.dart'; const double _kLeadingWidth = kToolbarHeight; // So the leading button is square. +const double _kMaxTitleTextScaleFactor = 1.34; // TODO(perc): Add link to Material spec when available, https://github.com/flutter/flutter/issues/58769. // Bottom justify the kToolbarHeight child which may overflow the top. class _ToolbarContainerLayout extends SingleChildLayoutDelegate { @@ -565,6 +566,25 @@ class _AppBarState extends State { overflow: TextOverflow.ellipsis, child: title, ); + + // Set maximum text scale factor to [_kMaxTitleTextScaleFactor] for the + // title to keep the visual hierarchy the same even with larger font + // sizes. To opt out, wrap the [title] widget in a [MediaQuery] widget + // with [MediaQueryData.textScaleFactor] set to + // `MediaQuery.textScaleFactorOf(context)`. + // ignore: deprecated_member_use_from_same_package + if (appBarTheme?.shouldCapTextScaleForTitle == true) { + final MediaQueryData mediaQueryData = MediaQuery.of(context); + title = MediaQuery( + data: mediaQueryData.copyWith( + textScaleFactor: math.min( + mediaQueryData.textScaleFactor, + _kMaxTitleTextScaleFactor, + ), + ), + child: title, + ); + } } Widget actions; diff --git a/packages/flutter/lib/src/material/app_bar_theme.dart b/packages/flutter/lib/src/material/app_bar_theme.dart index 356bf47ab53..149e9f668be 100644 --- a/packages/flutter/lib/src/material/app_bar_theme.dart +++ b/packages/flutter/lib/src/material/app_bar_theme.dart @@ -41,6 +41,12 @@ class AppBarTheme with Diagnosticable { this.actionsIconTheme, this.textTheme, this.centerTitle, + @Deprecated( + 'Deprecated property to cap text scaling for title. ' + 'This feature was deprecated after v1.19.0.' + ) + // ignore: deprecated_member_use_from_same_package + this.shouldCapTextScaleForTitle = false, }); /// Default value for [AppBar.brightness]. @@ -83,6 +89,19 @@ class AppBarTheme with Diagnosticable { /// If null, the value is adapted to current [TargetPlatform]. final bool centerTitle; + /// Cap text scale to a maximum for [AppBar.title]. + /// + /// This flag is deprecated and caps the text scaling to a maximum for + /// [AppBar.title], to keep the visual hierarchy in an app with large font + /// sizes. It exists to provide backwards compatibility to ease migrations, + /// and will eventually be removed as the maximum text scale will be enabled + /// by default. + @Deprecated( + 'Deprecated property to cap text scaling for title. ' + 'This feature was deprecated after v1.19.0.' + ) + final bool shouldCapTextScaleForTitle; + /// Creates a copy of this object with the given fields replaced with the /// new values. AppBarTheme copyWith({ @@ -94,6 +113,12 @@ class AppBarTheme with Diagnosticable { IconThemeData iconTheme, TextTheme textTheme, bool centerTitle, + @Deprecated( + 'Deprecated property to cap text scaling for title. ' + 'This feature was deprecated after v1.19.0.' + ) + // ignore: deprecated_member_use_from_same_package + bool shouldCapTextScaleForTitle, }) { return AppBarTheme( brightness: brightness ?? this.brightness, @@ -104,6 +129,8 @@ class AppBarTheme with Diagnosticable { actionsIconTheme: actionsIconTheme ?? this.actionsIconTheme, textTheme: textTheme ?? this.textTheme, centerTitle: centerTitle ?? this.centerTitle, + // ignore: deprecated_member_use_from_same_package + shouldCapTextScaleForTitle: shouldCapTextScaleForTitle ?? this.shouldCapTextScaleForTitle, ); } @@ -128,6 +155,8 @@ class AppBarTheme with Diagnosticable { actionsIconTheme: IconThemeData.lerp(a?.actionsIconTheme, b?.actionsIconTheme, t), textTheme: TextTheme.lerp(a?.textTheme, b?.textTheme, t), centerTitle: t < 0.5 ? a?.centerTitle : b?.centerTitle, + // ignore: deprecated_member_use_from_same_package + shouldCapTextScaleForTitle: a?.shouldCapTextScaleForTitle == true || b?.shouldCapTextScaleForTitle == true, ); } @@ -159,7 +188,9 @@ class AppBarTheme with Diagnosticable { && other.iconTheme == iconTheme && other.actionsIconTheme == actionsIconTheme && other.textTheme == textTheme - && other.centerTitle == centerTitle; + && other.centerTitle == centerTitle + // ignore: deprecated_member_use_from_same_package + && other.shouldCapTextScaleForTitle == shouldCapTextScaleForTitle; } @override @@ -173,5 +204,7 @@ class AppBarTheme with Diagnosticable { properties.add(DiagnosticsProperty('actionsIconTheme', actionsIconTheme, defaultValue: null)); properties.add(DiagnosticsProperty('textTheme', textTheme, defaultValue: null)); properties.add(DiagnosticsProperty('centerTitle', centerTitle, defaultValue: null)); + // ignore: deprecated_member_use_from_same_package + properties.add(DiagnosticsProperty('shouldCapTextScaleForTitle', shouldCapTextScaleForTitle, defaultValue: null)); } } diff --git a/packages/flutter/test/material/app_bar_test.dart b/packages/flutter/test/material/app_bar_test.dart index 86385376c0a..ec8bc578af2 100644 --- a/packages/flutter/test/material/app_bar_test.dart +++ b/packages/flutter/test/material/app_bar_test.dart @@ -1810,6 +1810,51 @@ void main() { expect(getMaterialWidget(materialFinder).shape, roundedRectangleBorder); }); + testWidgets('AppBars title has upper limit on text scaling, textScaleFactor = 1, 1.34, 2', (WidgetTester tester) async { + double textScaleFactor; + + Widget buildFrame() { + return MaterialApp( + home: Builder( + builder: (BuildContext context) { + final ThemeData themeData = Theme.of(context); + return Theme( + data: themeData.copyWith( + appBarTheme: themeData.appBarTheme.copyWith( + // ignore: deprecated_member_use_from_same_package + shouldCapTextScaleForTitle: true, + ), + ), + child: MediaQuery( + data: MediaQuery.of(context).copyWith(textScaleFactor: textScaleFactor), + child: Scaffold( + appBar: AppBar( + centerTitle: false, + title: const Text('Jumbo', style: TextStyle(fontSize: 18)), + ), + ), + ), + ); + }, + ), + ); + } + + final Finder appBarTitle = find.text('Jumbo'); + + textScaleFactor = 1; + await tester.pumpWidget(buildFrame()); + expect(tester.getRect(appBarTitle).height, 18); + + textScaleFactor = 1.34; + await tester.pumpWidget(buildFrame()); + expect(tester.getRect(appBarTitle).height, 24); + + textScaleFactor = 2; + await tester.pumpWidget(buildFrame()); + expect(tester.getRect(appBarTitle).height, 24); + }); + testWidgets('AppBars with jumbo titles, textScaleFactor = 3, 3.5, 4', (WidgetTester tester) async { double textScaleFactor; TextDirection textDirection;