Commit b30959d7 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Center AppBar title on iOS (#5039)

This patch adapts the AppBar to feel more like iOS by centering the title.

Fixes #4962
parent e4bee6b2
...@@ -395,7 +395,7 @@ class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<do ...@@ -395,7 +395,7 @@ class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<do
final double roundedTransformedValue = transformedValue.round().toDouble(); final double roundedTransformedValue = transformedValue.round().toDouble();
if (roundedTransformedValue != t) { if (roundedTransformedValue != t) {
throw new FlutterError( throw new FlutterError(
'Invalided curve endpoint at $t.\n' 'Invalid curve endpoint at $t.\n'
'Curves must map 0.0 to near zero and 1.0 to near one but ' 'Curves must map 0.0 to near zero and 1.0 to near one but '
'${activeCurve.runtimeType} mapped $t to $transformedValue, which ' '${activeCurve.runtimeType} mapped $t to $transformedValue, which '
'is near $roundedTransformedValue.' 'is near $roundedTransformedValue.'
......
...@@ -71,6 +71,7 @@ class AppBar extends StatelessWidget { ...@@ -71,6 +71,7 @@ class AppBar extends StatelessWidget {
this.iconTheme, this.iconTheme,
this.textTheme, this.textTheme,
this.padding: EdgeInsets.zero, this.padding: EdgeInsets.zero,
this.centerTitle,
double expandedHeight, double expandedHeight,
double collapsedHeight double collapsedHeight
}) : _expandedHeight = expandedHeight, }) : _expandedHeight = expandedHeight,
...@@ -146,6 +147,11 @@ class AppBar extends StatelessWidget { ...@@ -146,6 +147,11 @@ class AppBar extends StatelessWidget {
/// status bar so that the toolbar appears below the status bar. /// status bar so that the toolbar appears below the status bar.
final EdgeInsets padding; final EdgeInsets padding;
/// Whether the title should be centered.
///
/// Defaults to being adapted to the current [TargetPlatform].
final bool centerTitle;
final double _expandedHeight; final double _expandedHeight;
final double _collapsedHeight; final double _collapsedHeight;
...@@ -210,17 +216,32 @@ class AppBar extends StatelessWidget { ...@@ -210,17 +216,32 @@ class AppBar extends StatelessWidget {
return ((appBarHeight - statusBarHeight) / bottomHeight).clamp(0.0, 1.0); return ((appBarHeight - statusBarHeight) / bottomHeight).clamp(0.0, 1.0);
} }
bool _getEffectiveCenterTitle(ThemeData themeData) {
if (centerTitle != null)
return centerTitle;
assert(themeData.platform != null);
switch (themeData.platform) {
case TargetPlatform.android:
return false;
case TargetPlatform.iOS:
return true;
}
return null;
}
Widget _buildForSize(BuildContext context, BoxConstraints constraints) { Widget _buildForSize(BuildContext context, BoxConstraints constraints) {
assert(constraints.maxHeight < double.INFINITY); assert(constraints.maxHeight < double.INFINITY);
final Size size = constraints.biggest; final Size size = constraints.biggest;
final double statusBarHeight = MediaQuery.of(context).padding.top; final double statusBarHeight = MediaQuery.of(context).padding.top;
final ThemeData theme = Theme.of(context); final ThemeData themeData = Theme.of(context);
IconThemeData appBarIconTheme = iconTheme ?? themeData.primaryIconTheme;
TextStyle centerStyle = textTheme?.title ?? themeData.primaryTextTheme.title;
TextStyle sideStyle = textTheme?.body1 ?? themeData.primaryTextTheme.body1;
IconThemeData appBarIconTheme = iconTheme ?? theme.primaryIconTheme; final bool effectiveCenterTitle = _getEffectiveCenterTitle(themeData);
TextStyle centerStyle = textTheme?.title ?? theme.primaryTextTheme.title;
TextStyle sideStyle = textTheme?.body1 ?? theme.primaryTextTheme.body1;
Brightness brightness = this.brightness ?? theme.primaryColorBrightness; Brightness brightness = this.brightness ?? themeData.primaryColorBrightness;
SystemChrome.setSystemUIOverlayStyle(brightness == Brightness.dark SystemChrome.setSystemUIOverlayStyle(brightness == Brightness.dark
? mojom.SystemUiOverlayStyle.light ? mojom.SystemUiOverlayStyle.light
: mojom.SystemUiOverlayStyle.dark); : mojom.SystemUiOverlayStyle.dark);
...@@ -237,6 +258,16 @@ class AppBar extends StatelessWidget { ...@@ -237,6 +258,16 @@ class AppBar extends StatelessWidget {
); );
} }
Widget centerWidget;
if (title != null) {
centerWidget = new DefaultTextStyle(
style: centerStyle,
softWrap: false,
overflow: TextOverflow.ellipsis,
child: title
);
}
final List<Widget> toolBarRow = <Widget>[]; final List<Widget> toolBarRow = <Widget>[];
if (leading != null) { if (leading != null) {
toolBarRow.add(new Padding( toolBarRow.add(new Padding(
...@@ -245,20 +276,31 @@ class AppBar extends StatelessWidget { ...@@ -245,20 +276,31 @@ class AppBar extends StatelessWidget {
)); ));
} }
toolBarRow.add(new Flexible( toolBarRow.add(new Flexible(
child: new Padding( child: new Align(
padding: new EdgeInsets.only(left: 8.0), // TODO(abarth): In RTL this should be aligned to the right.
child: title != null ? alignment: FractionalOffset.centerLeft,
new DefaultTextStyle( child: new Padding(
style: centerStyle, padding: new EdgeInsets.only(left: 8.0),
softWrap: false, child: effectiveCenterTitle ? null : centerWidget
overflow: TextOverflow.ellipsis, )
child: title
) : null
) )
)); ));
if (actions != null) if (actions != null)
toolBarRow.addAll(actions); toolBarRow.addAll(actions);
Widget toolBar = new Row(children: toolBarRow);
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( Widget appBar = new SizedBox(
height: kToolBarHeight, height: kToolBarHeight,
child: new IconTheme.merge( child: new IconTheme.merge(
...@@ -266,7 +308,7 @@ class AppBar extends StatelessWidget { ...@@ -266,7 +308,7 @@ class AppBar extends StatelessWidget {
data: appBarIconTheme, data: appBarIconTheme,
child: new DefaultTextStyle( child: new DefaultTextStyle(
style: sideStyle, style: sideStyle,
child: new Row(children: toolBarRow) child: toolBar
) )
) )
); );
...@@ -323,7 +365,7 @@ class AppBar extends StatelessWidget { ...@@ -323,7 +365,7 @@ class AppBar extends StatelessWidget {
} }
appBar = new Material( appBar = new Material(
color: backgroundColor ?? theme.primaryColor, color: backgroundColor ?? themeData.primaryColor,
elevation: elevation, elevation: elevation,
child: appBar child: appBar
); );
......
...@@ -83,7 +83,7 @@ class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> { ...@@ -83,7 +83,7 @@ class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> {
case TargetPlatform.iOS: case TargetPlatform.iOS:
return true; return true;
} }
return false; return null;
} }
@override @override
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('AppBar centers title on iOS', (WidgetTester tester) async {
await tester.pumpWidget(
new MaterialApp(
theme: new ThemeData(platform: TargetPlatform.android),
home: new Scaffold(
appBar: new AppBar(
title: new Text('X')
)
)
)
);
Finder title = find.text('X');
Point center = tester.getCenter(title);
Size size = tester.getSize(title);
expect(center.x, lessThan(400 - size.width / 2.0));
// Clear the widget tree to avoid animating between Android and iOS.
await tester.pumpWidget(new Container(key: new UniqueKey()));
await tester.pumpWidget(
new MaterialApp(
theme: new ThemeData(platform: TargetPlatform.iOS),
home: new Scaffold(
appBar: new AppBar(
title: new Text('X')
)
)
)
);
center = tester.getCenter(title);
size = tester.getSize(title);
expect(center.x, greaterThan(400 - size.width / 2.0));
expect(center.x, lessThan(400 + size.width / 2.0));
});
}
...@@ -6,7 +6,7 @@ import 'package:flutter/material.dart'; ...@@ -6,7 +6,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
testWidgets('Flexible space back centers on iOS', (WidgetTester tester) async { testWidgets('FlexibleSpaceBar centers title on iOS', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
new MaterialApp( new MaterialApp(
theme: new ThemeData(platform: TargetPlatform.android), theme: new ThemeData(platform: TargetPlatform.android),
......
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