Commit f3b3ff47 authored by Mehmet Fidanboylu's avatar Mehmet Fidanboylu Committed by GitHub

Allow apps to change the title spacing in app bar. (#12076)

* Allow apps to change the title margin in app bar.

* Fix documentation links and the shouldLayout function in the _ToolbarLayout

* Rename margin to spacing

* review comments

* Test fixes. Now we also test the width to make sure enough space is left for trailing widget

* Expose the middle spacing default and use it in app bar.

* Fix analyzer break

* Doc fixes due to review
parent 3bab533c
...@@ -148,11 +148,13 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget { ...@@ -148,11 +148,13 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
this.textTheme, this.textTheme,
this.primary: true, this.primary: true,
this.centerTitle, this.centerTitle,
this.titleSpacing: NavigationToolbar.kMiddleSpacing,
this.toolbarOpacity: 1.0, this.toolbarOpacity: 1.0,
this.bottomOpacity: 1.0, this.bottomOpacity: 1.0,
}) : assert(automaticallyImplyLeading != null), }) : assert(automaticallyImplyLeading != null),
assert(elevation != null), assert(elevation != null),
assert(primary != null), assert(primary != null),
assert(titleSpacing != null),
assert(toolbarOpacity != null), assert(toolbarOpacity != null),
assert(bottomOpacity != null), assert(bottomOpacity != null),
preferredSize = new Size.fromHeight(kToolbarHeight + (bottom?.preferredSize?.height ?? 0.0)), preferredSize = new Size.fromHeight(kToolbarHeight + (bottom?.preferredSize?.height ?? 0.0)),
...@@ -268,6 +270,13 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget { ...@@ -268,6 +270,13 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
/// Defaults to being adapted to the current [TargetPlatform]. /// Defaults to being adapted to the current [TargetPlatform].
final bool centerTitle; final bool centerTitle;
/// The spacing around [title] content on the horizontal axis. This spacing is
/// applied even if there is no [leading] content or [actions]. If you want
/// [title] to take all the space available, set this value to 0.0.
///
/// Defaults to [NavigationToolbar.kMiddleSpacing].
final double titleSpacing;
/// How opaque the toolbar part of the app bar is. /// How opaque the toolbar part of the app bar is.
/// ///
/// A value of 1.0 is fully opaque, and a value of 0.0 is fully transparent. /// A value of 1.0 is fully opaque, and a value of 0.0 is fully transparent.
...@@ -393,6 +402,7 @@ class _AppBarState extends State<AppBar> { ...@@ -393,6 +402,7 @@ class _AppBarState extends State<AppBar> {
middle: title, middle: title,
trailing: actions, trailing: actions,
centerMiddle: widget._getEffectiveCenterTitle(themeData), centerMiddle: widget._getEffectiveCenterTitle(themeData),
middleSpacing: widget.titleSpacing,
), ),
); );
...@@ -528,6 +538,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { ...@@ -528,6 +538,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
@required this.textTheme, @required this.textTheme,
@required this.primary, @required this.primary,
@required this.centerTitle, @required this.centerTitle,
@required this.titleSpacing,
@required this.expandedHeight, @required this.expandedHeight,
@required this.collapsedHeight, @required this.collapsedHeight,
@required this.topPadding, @required this.topPadding,
...@@ -551,6 +562,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { ...@@ -551,6 +562,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
final TextTheme textTheme; final TextTheme textTheme;
final bool primary; final bool primary;
final bool centerTitle; final bool centerTitle;
final double titleSpacing;
final double expandedHeight; final double expandedHeight;
final double collapsedHeight; final double collapsedHeight;
final double topPadding; final double topPadding;
...@@ -592,6 +604,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { ...@@ -592,6 +604,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
textTheme: textTheme, textTheme: textTheme,
primary: primary, primary: primary,
centerTitle: centerTitle, centerTitle: centerTitle,
titleSpacing: titleSpacing,
toolbarOpacity: toolbarOpacity, toolbarOpacity: toolbarOpacity,
bottomOpacity: pinned ? 1.0 : (visibleMainHeight / _bottomHeight).clamp(0.0, 1.0), bottomOpacity: pinned ? 1.0 : (visibleMainHeight / _bottomHeight).clamp(0.0, 1.0),
), ),
...@@ -615,6 +628,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { ...@@ -615,6 +628,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
|| textTheme != oldDelegate.textTheme || textTheme != oldDelegate.textTheme
|| primary != oldDelegate.primary || primary != oldDelegate.primary
|| centerTitle != oldDelegate.centerTitle || centerTitle != oldDelegate.centerTitle
|| titleSpacing != oldDelegate.titleSpacing
|| expandedHeight != oldDelegate.expandedHeight || expandedHeight != oldDelegate.expandedHeight
|| topPadding != oldDelegate.topPadding || topPadding != oldDelegate.topPadding
|| pinned != oldDelegate.pinned || pinned != oldDelegate.pinned
...@@ -699,6 +713,7 @@ class SliverAppBar extends StatefulWidget { ...@@ -699,6 +713,7 @@ class SliverAppBar extends StatefulWidget {
this.textTheme, this.textTheme,
this.primary: true, this.primary: true,
this.centerTitle, this.centerTitle,
this.titleSpacing: NavigationToolbar.kMiddleSpacing,
this.expandedHeight, this.expandedHeight,
this.floating: false, this.floating: false,
this.pinned: false, this.pinned: false,
...@@ -706,6 +721,7 @@ class SliverAppBar extends StatefulWidget { ...@@ -706,6 +721,7 @@ class SliverAppBar extends StatefulWidget {
}) : assert(automaticallyImplyLeading != null), }) : assert(automaticallyImplyLeading != null),
assert(forceElevated != null), assert(forceElevated != null),
assert(primary != null), assert(primary != null),
assert(titleSpacing != null),
assert(floating != null), assert(floating != null),
assert(pinned != null), assert(pinned != null),
assert(!pinned || !floating || bottom != null, 'A pinned and floating app bar must have a bottom widget.'), assert(!pinned || !floating || bottom != null, 'A pinned and floating app bar must have a bottom widget.'),
...@@ -841,6 +857,13 @@ class SliverAppBar extends StatefulWidget { ...@@ -841,6 +857,13 @@ class SliverAppBar extends StatefulWidget {
/// Defaults to being adapted to the current [TargetPlatform]. /// Defaults to being adapted to the current [TargetPlatform].
final bool centerTitle; final bool centerTitle;
/// The spacing around [title] content on the horizontal axis. This spacing is
/// applied even if there is no [leading] content or [actions]. If you want
/// [title] to take all the space available, set this value to 0.0.
///
/// Defaults to [NavigationToolbar.kMiddleSpacing].
final double titleSpacing;
/// The size of the app bar when it is fully expanded. /// The size of the app bar when it is fully expanded.
/// ///
/// By default, the total height of the toolbar and the bottom widget (if /// By default, the total height of the toolbar and the bottom widget (if
...@@ -939,6 +962,7 @@ class _SliverAppBarState extends State<SliverAppBar> with TickerProviderStateMix ...@@ -939,6 +962,7 @@ class _SliverAppBarState extends State<SliverAppBar> with TickerProviderStateMix
textTheme: widget.textTheme, textTheme: widget.textTheme,
primary: widget.primary, primary: widget.primary,
centerTitle: widget.centerTitle, centerTitle: widget.centerTitle,
titleSpacing: widget.titleSpacing,
expandedHeight: widget.expandedHeight, expandedHeight: widget.expandedHeight,
collapsedHeight: collapsedHeight, collapsedHeight: collapsedHeight,
topPadding: topPadding, topPadding: topPadding,
......
...@@ -22,6 +22,7 @@ import 'framework.dart'; ...@@ -22,6 +22,7 @@ import 'framework.dart';
/// the iOS [CupertinoNavigationBar] or wrap this widget with more theming /// the iOS [CupertinoNavigationBar] or wrap this widget with more theming
/// specifications for your own custom app bar. /// specifications for your own custom app bar.
class NavigationToolbar extends StatelessWidget { class NavigationToolbar extends StatelessWidget {
/// Creates a widget that lays out its children in a manner suitable for a /// Creates a widget that lays out its children in a manner suitable for a
/// toolbar. /// toolbar.
const NavigationToolbar({ const NavigationToolbar({
...@@ -30,9 +31,14 @@ class NavigationToolbar extends StatelessWidget { ...@@ -30,9 +31,14 @@ class NavigationToolbar extends StatelessWidget {
this.middle, this.middle,
this.trailing, this.trailing,
this.centerMiddle: true, this.centerMiddle: true,
this.middleSpacing: kMiddleSpacing,
}) : assert(centerMiddle != null), }) : assert(centerMiddle != null),
assert(middleSpacing != null),
super(key: key); super(key: key);
/// The default spacing around the [middle] widget in dp.
static const double kMiddleSpacing = 16.0;
/// Widget to place at the start of the horizontal toolbar. /// Widget to place at the start of the horizontal toolbar.
final Widget leading; final Widget leading;
...@@ -47,6 +53,11 @@ class NavigationToolbar extends StatelessWidget { ...@@ -47,6 +53,11 @@ class NavigationToolbar extends StatelessWidget {
/// next to the [leading] widget when false. /// next to the [leading] widget when false.
final bool centerMiddle; final bool centerMiddle;
/// The spacing around the [middle] widget on horizontal axis.
///
/// Defaults to [kMiddleSpacing].
final double middleSpacing;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final List<Widget> children = <Widget>[]; final List<Widget> children = <Widget>[];
...@@ -65,6 +76,7 @@ class NavigationToolbar extends StatelessWidget { ...@@ -65,6 +76,7 @@ class NavigationToolbar extends StatelessWidget {
return new CustomMultiChildLayout( return new CustomMultiChildLayout(
delegate: new _ToolbarLayout( delegate: new _ToolbarLayout(
centerMiddle: centerMiddle, centerMiddle: centerMiddle,
middleSpacing: middleSpacing,
textDirection: textDirection, textDirection: textDirection,
), ),
children: children, children: children,
...@@ -78,13 +90,13 @@ enum _ToolbarSlot { ...@@ -78,13 +90,13 @@ enum _ToolbarSlot {
trailing, trailing,
} }
const double _kMiddleMargin = 16.0;
class _ToolbarLayout extends MultiChildLayoutDelegate { class _ToolbarLayout extends MultiChildLayoutDelegate {
_ToolbarLayout({ _ToolbarLayout({
this.centerMiddle, this.centerMiddle,
@required this.middleSpacing,
@required this.textDirection, @required this.textDirection,
}) : assert(textDirection != null); }) : assert(middleSpacing != null),
assert(textDirection != null);
// If false the middle widget should be start-justified within the space // If false the middle widget should be start-justified within the space
// between the leading and trailing widgets. // between the leading and trailing widgets.
...@@ -92,6 +104,9 @@ class _ToolbarLayout extends MultiChildLayoutDelegate { ...@@ -92,6 +104,9 @@ class _ToolbarLayout extends MultiChildLayoutDelegate {
// space between the leading and trailing widgets). // space between the leading and trailing widgets).
final bool centerMiddle; final bool centerMiddle;
/// The spacing around middle widget on horizontal axis.
final double middleSpacing;
final TextDirection textDirection; final TextDirection textDirection;
@override @override
...@@ -137,11 +152,11 @@ class _ToolbarLayout extends MultiChildLayoutDelegate { ...@@ -137,11 +152,11 @@ class _ToolbarLayout extends MultiChildLayoutDelegate {
} }
if (hasChild(_ToolbarSlot.middle)) { if (hasChild(_ToolbarSlot.middle)) {
final double maxWidth = math.max(size.width - leadingWidth - trailingWidth - _kMiddleMargin * 2.0, 0.0); final double maxWidth = math.max(size.width - leadingWidth - trailingWidth - middleSpacing * 2.0, 0.0);
final BoxConstraints constraints = new BoxConstraints.loose(size).copyWith(maxWidth: maxWidth); final BoxConstraints constraints = new BoxConstraints.loose(size).copyWith(maxWidth: maxWidth);
final Size middleSize = layoutChild(_ToolbarSlot.middle, constraints); final Size middleSize = layoutChild(_ToolbarSlot.middle, constraints);
final double middleStartMargin = hasChild(_ToolbarSlot.leading) ? leadingWidth + _kMiddleMargin : 0.0; final double middleStartMargin = leadingWidth + middleSpacing;
double middleStart = middleStartMargin; double middleStart = middleStartMargin;
final double middleY = (size.height - middleSize.height) / 2.0; final double middleY = (size.height - middleSize.height) / 2.0;
// If the centered middle will not fit between the leading and trailing // If the centered middle will not fit between the leading and trailing
...@@ -171,6 +186,7 @@ class _ToolbarLayout extends MultiChildLayoutDelegate { ...@@ -171,6 +186,7 @@ class _ToolbarLayout extends MultiChildLayoutDelegate {
@override @override
bool shouldRelayout(_ToolbarLayout oldDelegate) { bool shouldRelayout(_ToolbarLayout oldDelegate) {
return oldDelegate.centerMiddle != centerMiddle return oldDelegate.centerMiddle != centerMiddle
|| oldDelegate.middleSpacing != middleSpacing
|| oldDelegate.textDirection != textDirection; || oldDelegate.textDirection != textDirection;
} }
} }
...@@ -157,22 +157,25 @@ void main() { ...@@ -157,22 +157,25 @@ void main() {
expect(center.dx, lessThan(400 + size.width / 2.0)); expect(center.dx, lessThan(400 + size.width / 2.0));
}); });
testWidgets('AppBar centerTitle:false title start edge is 0.0 (LTR)', (WidgetTester tester) async { testWidgets('AppBar centerTitle:false title start edge is 16.0 (LTR)', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
new MaterialApp( new MaterialApp(
home: new Scaffold( home: new Scaffold(
appBar: new AppBar( appBar: new AppBar(
centerTitle: false, centerTitle: false,
title: const Text('X'), title: const Placeholder(key: const Key('X')),
), ),
), ),
), ),
); );
expect(tester.getTopLeft(find.text('X')).dx, 0.0); final Finder titleWidget = find.byKey(const Key('X'));
expect(tester.getTopLeft(titleWidget).dx, 16.0);
// 4.0 is due to AppBar right padding.
expect(tester.getTopRight(titleWidget).dx, 800 - 16.0 - 4.0);
}); });
testWidgets('AppBar centerTitle:false title start edge is 0.0 (RTL)', (WidgetTester tester) async { testWidgets('AppBar centerTitle:false title start edge is 16.0 (RTL)', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
new MaterialApp( new MaterialApp(
home: new Directionality( home: new Directionality(
...@@ -180,14 +183,58 @@ void main() { ...@@ -180,14 +183,58 @@ void main() {
child: new Scaffold( child: new Scaffold(
appBar: new AppBar( appBar: new AppBar(
centerTitle: false, centerTitle: false,
title: const Text('X'), title: const Placeholder(key: const Key('X')),
),
),
),
),
);
final Finder titleWidget = find.byKey(const Key('X'));
expect(tester.getTopRight(titleWidget).dx, 800.0 - 16.0);
// 4.0 is due to AppBar right padding.
expect(tester.getTopLeft(titleWidget).dx, 16.0 + 4.0);
});
testWidgets('AppBar titleSpacing:32 title start edge is 32.0 (LTR)', (WidgetTester tester) async {
await tester.pumpWidget(
new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
centerTitle: false,
titleSpacing: 32.0,
title: const Placeholder(key: const Key('X')),
),
),
),
);
final Finder titleWidget = find.byKey(const Key('X'));
expect(tester.getTopLeft(titleWidget).dx, 32.0);
// 4.0 is due to AppBar right padding.
expect(tester.getTopRight(titleWidget).dx, 800 - 32.0 - 4.0);
});
testWidgets('AppBar titleSpacing:32 title start edge is 32.0 (RTL)', (WidgetTester tester) async {
await tester.pumpWidget(
new MaterialApp(
home: new Directionality(
textDirection: TextDirection.rtl,
child: new Scaffold(
appBar: new AppBar(
centerTitle: false,
titleSpacing: 32.0,
title: const Placeholder(key: const Key('X')),
), ),
), ),
), ),
), ),
); );
expect(tester.getTopRight(find.text('X')).dx, 800.0); final Finder titleWidget = find.byKey(const Key('X'));
expect(tester.getTopRight(titleWidget).dx, 800.0 - 32.0);
// 4.0 is due to AppBar right padding.
expect(tester.getTopLeft(titleWidget).dx, 32.0 + 4.0);
}); });
testWidgets( testWidgets(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment