Commit 4d80f3cb authored by Hans Muller's avatar Hans Muller Committed by GitHub

Fix app bar title overflow with centerTitle:true (#5773)

parent 5a802995
......@@ -24,6 +24,65 @@ abstract class AppBarBottomWidget extends Widget {
double get bottomHeight;
}
enum _ToolBarSlot {
leading,
title,
actions,
}
class _ToolBarLayout extends MultiChildLayoutDelegate {
_ToolBarLayout({ this.centerTitle });
// If false the title should be left or right justified within the space bewteen
// the leading and actions widgets, depending on the locale's writing direction.
// If true the title is centered within the toolbar (not within the horizontal
// space bewteen the leading and actions widgets).
final bool centerTitle;
static const double kLeadingWidth = 24.0;
static const double kTitleLeft = 64.0; // The AppBar pads left and right an additional 8.0.
@override
void performLayout(Size size) {
double actionsWidth = 0.0;
if (hasChild(_ToolBarSlot.leading)) {
final BoxConstraints constraints = new BoxConstraints.tight(new Size(kLeadingWidth, size.height));
layoutChild(_ToolBarSlot.leading, constraints);
positionChild(_ToolBarSlot.leading, Offset.zero);
}
if (hasChild(_ToolBarSlot.actions)) {
final BoxConstraints constraints = new BoxConstraints.loose(size);
actionsWidth = layoutChild(_ToolBarSlot.actions, constraints).width;
positionChild(_ToolBarSlot.actions, new Offset(size.width - actionsWidth, 0.0));
}
if (hasChild(_ToolBarSlot.title)) {
final double maxWidth = size.width - kTitleLeft - actionsWidth;
final BoxConstraints constraints = new BoxConstraints.loose(size).copyWith(maxWidth: maxWidth);
final Size titleSize = layoutChild(_ToolBarSlot.title, constraints);
final double titleY = (size.height - titleSize.height) / 2.0;
double titleX = kTitleLeft;
// If the centered title will not fit between the leading and actions
// widgets, then align its left or right edge with the adjacent boundary.
if (centerTitle) {
titleX = (size.width - titleSize.width) / 2.0;
if (titleX + titleSize.width > size.width - actionsWidth)
titleX = size.width - actionsWidth - titleSize.width;
else if (titleX < kTitleLeft)
titleX = kTitleLeft;
}
positionChild(_ToolBarSlot.title, new Offset(titleX, titleY));
}
}
@override
bool shouldRelayout(_ToolBarLayout oldDelegate) => centerTitle != oldDelegate.centerTitle;
}
// TODO(eseidel) Toolbar needs to change size based on orientation:
// http://www.google.com/design/spec/layout/structure.html#structure-app-bar
// Mobile Landscape: 48dp
......@@ -215,6 +274,7 @@ class AppBar extends StatelessWidget {
iconTheme: iconTheme ?? this.iconTheme,
textTheme: textTheme ?? this.textTheme,
padding: padding ?? this.padding,
centerTitle: centerTitle ?? this.centerTitle,
heroTag: heroTag ?? this.heroTag,
expandedHeight: expandedHeight ?? this._expandedHeight,
collapsedHeight: collapsedHeight ?? this._collapsedHeight
......@@ -272,8 +332,6 @@ class AppBar extends StatelessWidget {
TextStyle centerStyle = textTheme?.title ?? themeData.primaryTextTheme.title;
TextStyle sideStyle = textTheme?.body1 ?? themeData.primaryTextTheme.body1;
final bool effectiveCenterTitle = _getEffectiveCenterTitle(themeData);
Brightness brightness = this.brightness ?? themeData.primaryColorBrightness;
SystemChrome.setSystemUIOverlayStyle(brightness == Brightness.dark
? mojom.SystemUiOverlayStyle.light
......@@ -291,52 +349,50 @@ class AppBar extends StatelessWidget {
);
}
Widget centerWidget;
if (title != null) {
centerWidget = new DefaultTextStyle(
style: centerStyle,
softWrap: false,
overflow: TextOverflow.ellipsis,
child: title
final List<Widget> toolBarChildren = <Widget>[];
if (leading != null) {
toolBarChildren.add(
new LayoutId(
id: _ToolBarSlot.leading,
child: leading
)
);
}
final List<Widget> toolBarRow = <Widget>[];
if (leading != null) {
toolBarRow.add(new Padding(
padding: new EdgeInsets.only(right: 16.0),
child: leading
));
if (title != null) {
toolBarChildren.add(
new LayoutId(
id: _ToolBarSlot.title,
child: new DefaultTextStyle(
style: centerStyle,
softWrap: false,
overflow: TextOverflow.ellipsis,
child: title
)
)
);
}
toolBarRow.add(new Flexible(
child: new Align(
// TODO(abarth): In RTL this should be aligned to the right.
alignment: FractionalOffset.centerLeft,
child: new Padding(
padding: new EdgeInsets.only(left: 8.0),
child: effectiveCenterTitle ? null : centerWidget
if (actions != null && actions.isNotEmpty) {
toolBarChildren.add(
new LayoutId(
id: _ToolBarSlot.actions,
child: new Row(
mainAxisSize: MainAxisSize.min,
children: actions
)
)
)
));
if (actions != null)
toolBarRow.addAll(actions);
);
}
Widget toolBar = new Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: new Row(children: toolBarRow)
child: new CustomMultiChildLayout(
delegate: new _ToolBarLayout(
centerTitle: _getEffectiveCenterTitle(themeData)
),
children: toolBarChildren
)
);
if (effectiveCenterTitle && centerWidget != null) {
toolBar = new Stack(
children: <Widget>[
// TODO(abarth): If there isn't enough room, we should move the title
// off center rather than overlap the actions.
new Center(child: centerWidget),
toolBar
]
);
}
Widget appBar = new SizedBox(
height: kToolBarHeight,
child: new IconTheme.merge(
......
......@@ -42,4 +42,141 @@ void main() {
expect(center.x, greaterThan(400 - size.width / 2.0));
expect(center.x, lessThan(400 + size.width / 2.0));
});
testWidgets('AppBar centerTitle:true centers on Android', (WidgetTester tester) async {
await tester.pumpWidget(
new MaterialApp(
theme: new ThemeData(platform: TargetPlatform.android),
home: new Scaffold(
appBar: new AppBar(
centerTitle: true,
title: new Text('X')
)
)
)
);
Finder title = find.text('X');
Point center = tester.getCenter(title);
Size size = tester.getSize(title);
expect(center.x, greaterThan(400 - size.width / 2.0));
expect(center.x, lessThan(400 + size.width / 2.0));
});
testWidgets('AppBar centerTitle:false title left edge is 72.0 ', (WidgetTester tester) async {
await tester.pumpWidget(
new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
centerTitle: false,
title: new Text('X')
)
)
)
);
expect(tester.getTopLeft(find.text('X')).x, 72.0);
});
testWidgets('AppBar centerTitle:false title overflow OK ', (WidgetTester tester) async {
// The app bar's title should be constrained to fit within the available space
// between the leading and actions widgets.
Key titleKey = new UniqueKey();
Widget leading;
List<Widget> actions;
Widget buildApp() {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
leading: leading,
centerTitle: false,
title: new Container(
key: titleKey,
constraints: new BoxConstraints.loose(const Size(1000.0, 1000.0))
),
actions: actions
)
)
);
}
await tester.pumpWidget(buildApp());
Finder title = find.byKey(titleKey);
expect(tester.getTopLeft(title).x, 72.0);
// The toolbar's contents are padded on the right by 8.0
expect(tester.getSize(title).width, equals(800.0 - 72.0 - 8.0));
actions = <Widget>[
new SizedBox(width: 100.0),
new SizedBox(width: 100.0)
];
await tester.pumpWidget(buildApp());
expect(tester.getTopLeft(title).x, 72.0);
// The title shrinks by 200.0 to allow for the actions widgets.
expect(tester.getSize(title).width, equals(800.0 - 72.0 - 8.0 - 200.0));
leading = new Container(); // AppBar will constrain the width to 24.0
await tester.pumpWidget(buildApp());
expect(tester.getTopLeft(title).x, 72.0);
// Adding a leading widget shouldn't effect the title's size
expect(tester.getSize(title).width, equals(800.0 - 72.0 - 8.0 - 200.0));
});
testWidgets('AppBar centerTitle:true title overflow OK ', (WidgetTester tester) async {
// The app bar's title should be constrained to fit within the available space
// between the leading and actions widgets. When it's also centered it may
// also be left or right justified if it doesn't fit in the overall center.
Key titleKey = new UniqueKey();
double titleWidth = 700.0;
Widget leading = new Container();
List<Widget> actions;
Widget buildApp() {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
leading: leading,
centerTitle: true,
title: new Container(
key: titleKey,
constraints: new BoxConstraints.loose(new Size(titleWidth, 1000.0))
),
actions: actions
)
)
);
}
// Centering a title with width 700 within the 800 pixel wide test widget
// would mean that its left edge would have to be 50. The material spec says
// that the left edge of the title must be atleast 72.
await tester.pumpWidget(buildApp());
Finder title = find.byKey(titleKey);
expect(tester.getTopLeft(title).x, 72.0);
expect(tester.getSize(title).width, equals(700.0));
// Centering a title with width 620 within the 800 pixel wide test widget
// would mean that its left edge would have to be 90. We reserve 72
// on the left and the padded actions occupy 90 + 8 on the right. That
// leaves 630, so the title is right justified but its width isn't changed.
await tester.pumpWidget(buildApp());
leading = null;
titleWidth = 620.0;
actions = <Widget>[
new SizedBox(width: 45.0),
new SizedBox(width: 45.0)
];
await tester.pumpWidget(buildApp());
expect(tester.getTopLeft(title).x, 800 - 620 - 45 - 45 - 8);
expect(tester.getSize(title).width, equals(620.0));
});
}
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