Customized Cupertino navigation bar. (#14307)

- Add override of border color of CupertinoNavigationBar
- Add background color to CupertinoPageScaffold
This commit is contained in:
Volodymyr Lykhonis 2018-02-05 14:01:55 -08:00 committed by Ian Hickson
parent e810bee6e5
commit b88ba79c04
5 changed files with 174 additions and 15 deletions

View file

@ -79,7 +79,7 @@ class CupertinoIcons {
/// A checkmark in a circle.
static const IconData check_mark_circled = const IconData(0xf3fe, fontFamily: iconFont, fontPackage: iconFontPackage);
/// A thicker left chevron used in iOS for the nav bar back button.
/// A thicker left chevron used in iOS for the navigation bar back button.
static const IconData back = const IconData(0xf3cf, fontFamily: iconFont, fontPackage: iconFontPackage, matchTextDirection: true);
/// Outline of a simple front-facing house.

View file

@ -37,6 +37,14 @@ const Duration _kNavBarTitleFadeDuration = const Duration(milliseconds: 150);
const Color _kDefaultNavBarBackgroundColor = const Color(0xCCF8F8F8);
const Color _kDefaultNavBarBorderColor = const Color(0x4C000000);
const Border _kDefaultNavBarBorder = const Border(
bottom: const BorderSide(
color: _kDefaultNavBarBorderColor,
width: 0.0, // One physical pixel.
style: BorderStyle.solid,
),
);
const TextStyle _kLargeTitleTextStyle = const TextStyle(
fontFamily: '.SF UI Text',
fontSize: 34.0,
@ -77,6 +85,7 @@ class CupertinoNavigationBar extends StatelessWidget implements ObstructingPrefe
this.automaticallyImplyLeading: true,
this.middle,
this.trailing,
this.border: _kDefaultNavBarBorder,
this.backgroundColor: _kDefaultNavBarBackgroundColor,
this.actionsForegroundColor: CupertinoColors.activeBlue,
}) : assert(automaticallyImplyLeading != null),
@ -109,6 +118,11 @@ class CupertinoNavigationBar extends StatelessWidget implements ObstructingPrefe
/// behind it.
final Color backgroundColor;
/// The border of the navigation bar. By default renders a single pixel bottom border side.
///
/// If a border is null, the navigation bar will not display a border.
final Border border;
/// Default color used for text and icons of the [leading] and [trailing]
/// widgets in the navigation bar.
///
@ -128,6 +142,7 @@ class CupertinoNavigationBar extends StatelessWidget implements ObstructingPrefe
@override
Widget build(BuildContext context) {
return _wrapWithBackground(
border: border,
backgroundColor: backgroundColor,
child: new _CupertinoPersistentNavigationBar(
leading: leading,
@ -265,16 +280,14 @@ class CupertinoSliverNavigationBar extends StatelessWidget {
/// Returns `child` wrapped with background and a bottom border if background color
/// is opaque. Otherwise, also blur with [BackdropFilter].
Widget _wrapWithBackground({Color backgroundColor, Widget child}) {
Widget _wrapWithBackground({
Border border,
Color backgroundColor,
Widget child,
}) {
final DecoratedBox childWithBackground = new DecoratedBox(
decoration: new BoxDecoration(
border: const Border(
bottom: const BorderSide(
color: _kDefaultNavBarBorderColor,
width: 0.0, // One physical pixel.
style: BorderStyle.solid,
),
),
border: border,
color: backgroundColor,
),
child: child,
@ -282,7 +295,8 @@ Widget _wrapWithBackground({Color backgroundColor, Widget child}) {
final bool darkBackground = backgroundColor.computeLuminance() < 0.179;
SystemChrome.setSystemUIOverlayStyle(
darkBackground ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark);
darkBackground ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark
);
if (backgroundColor.alpha == 0xFF)
return childWithBackground;
@ -424,7 +438,8 @@ class _CupertinoLargeTitleNavigationBarSliverDelegate
this.automaticallyImplyLeading,
this.middle,
this.trailing,
this.backgroundColor,
this.border: _kDefaultNavBarBorder,
this.backgroundColor: _kDefaultNavBarBackgroundColor,
this.actionsForegroundColor,
}) : assert(persistentHeight != null);
@ -442,6 +457,8 @@ class _CupertinoLargeTitleNavigationBarSliverDelegate
final Color backgroundColor;
final Border border;
final Color actionsForegroundColor;
@override
@ -467,6 +484,7 @@ class _CupertinoLargeTitleNavigationBarSliverDelegate
);
return _wrapWithBackground(
border: border,
backgroundColor: backgroundColor,
child: new Stack(
fit: StackFit.expand,

View file

@ -22,6 +22,7 @@ class CupertinoPageScaffold extends StatelessWidget {
const CupertinoPageScaffold({
Key key,
this.navigationBar,
this.backgroundColor: CupertinoColors.white,
@required this.child,
}) : assert(child != null),
super(key: key);
@ -32,7 +33,7 @@ class CupertinoPageScaffold extends StatelessWidget {
/// If translucent, the main content may slide behind it.
/// Otherwise, the main content's top margin will be offset by its height.
///
/// The scaffold assumes the nav bar will consume the [MediaQuery] top padding.
/// The scaffold assumes the navigation bar will consume the [MediaQuery] top padding.
// TODO(xster): document its page transition animation when ready
final ObstructingPreferredSizeWidget navigationBar;
@ -44,6 +45,11 @@ class CupertinoPageScaffold extends StatelessWidget {
/// [navigationBar].
final Widget child;
/// The color of the widget that underlies the entire scaffold.
///
/// By default uses [CupertinoColors.white] color.
final Color backgroundColor;
@override
Widget build(BuildContext context) {
final List<Widget> stacked = <Widget>[];
@ -57,8 +63,8 @@ class CupertinoPageScaffold extends StatelessWidget {
final double topPadding = navigationBar.preferredSize.height
+ existingMediaQuery.padding.top;
// If nav bar is opaquely obstructing, directly shift the main content
// down. If translucent, let main content draw behind nav bar but hint the
// If navigation bar is opaquely obstructing, directly shift the main content
// down. If translucent, let main content draw behind navigation bar but hint the
// obstructed area.
if (navigationBar.fullObstruction) {
paddedContent = new Padding(
@ -90,7 +96,7 @@ class CupertinoPageScaffold extends StatelessWidget {
}
return new DecoratedBox(
decoration: const BoxDecoration(color: CupertinoColors.white),
decoration: new BoxDecoration(color: backgroundColor),
child: new Stack(
children: stacked,
),

View file

@ -395,6 +395,92 @@ void main() {
expect(find.text('Home page'), findsOneWidget);
});
testWidgets('Border should be displayed by default', (WidgetTester tester) async {
await tester.pumpWidget(
new WidgetsApp(
color: const Color(0xFFFFFFFF),
onGenerateRoute: (RouteSettings settings) {
return new CupertinoPageRoute<Null>(
settings: settings,
builder: (BuildContext context) {
return const CupertinoNavigationBar(
middle: const Text('Title'),
);
},
);
},
),
);
final DecoratedBox decoratedBox = tester.widgetList(find.byType(DecoratedBox)).elementAt(1);
expect(decoratedBox.decoration.runtimeType, BoxDecoration);
final BoxDecoration decoration = decoratedBox.decoration;
expect(decoration.border, isNotNull);
final BorderSide side = decoration.border.bottom;
expect(side, isNotNull);
});
testWidgets('Overrides border color', (WidgetTester tester) async {
await tester.pumpWidget(
new WidgetsApp(
color: const Color(0xFFFFFFFF),
onGenerateRoute: (RouteSettings settings) {
return new CupertinoPageRoute<Null>(
settings: settings,
builder: (BuildContext context) {
return const CupertinoNavigationBar(
middle: const Text('Title'),
border: const Border(
bottom: const BorderSide(
color: const Color(0xFFAABBCC),
width: 0.0,
),
),
);
},
);
},
),
);
final DecoratedBox decoratedBox = tester.widgetList(find.byType(DecoratedBox)).elementAt(1);
expect(decoratedBox.decoration.runtimeType, BoxDecoration);
final BoxDecoration decoration = decoratedBox.decoration;
expect(decoration.border, isNotNull);
final BorderSide side = decoration.border.bottom;
expect(side, isNotNull);
expect(side.color, const Color(0xFFAABBCC));
});
testWidgets('Border should not be displayed when null', (WidgetTester tester) async {
await tester.pumpWidget(
new WidgetsApp(
color: const Color(0xFFFFFFFF),
onGenerateRoute: (RouteSettings settings) {
return new CupertinoPageRoute<Null>(
settings: settings,
builder: (BuildContext context) {
return const CupertinoNavigationBar(
middle: const Text('Title'),
border: null,
);
},
);
},
),
);
final DecoratedBox decoratedBox = tester.widgetList(find.byType(DecoratedBox)).elementAt(1);
expect(decoratedBox.decoration.runtimeType, BoxDecoration);
final BoxDecoration decoration = decoratedBox.decoration;
expect(decoration.border, isNull);
});
}
class _ExpectStyles extends StatelessWidget {

View file

@ -254,4 +254,53 @@ void main() {
expect(find.text('Page 1 of tab 2'), isOnstage);
expect(find.text('Page 2 of tab 1', skipOffstage: false), isOffstage);
});
testWidgets('Decorated with white background by default', (WidgetTester tester) async {
await tester.pumpWidget(
new WidgetsApp(
color: const Color(0xFFFFFFFF),
onGenerateRoute: (RouteSettings settings) {
return new CupertinoPageRoute<Null>(
settings: settings,
builder: (BuildContext context) {
return const CupertinoPageScaffold(
child: const Center(),
);
},
);
},
),
);
final DecoratedBox decoratedBox = tester.widgetList(find.byType(DecoratedBox)).elementAt(1);
expect(decoratedBox.decoration.runtimeType, BoxDecoration);
final BoxDecoration decoration = decoratedBox.decoration;
expect(decoration.color, CupertinoColors.white);
});
testWidgets('Overrides background color', (WidgetTester tester) async {
await tester.pumpWidget(
new WidgetsApp(
color: const Color(0xFFFFFFFF),
onGenerateRoute: (RouteSettings settings) {
return new CupertinoPageRoute<Null>(
settings: settings,
builder: (BuildContext context) {
return const CupertinoPageScaffold(
child: const Center(),
backgroundColor: const Color(0xFF010203),
);
},
);
},
),
);
final DecoratedBox decoratedBox = tester.widgetList(find.byType(DecoratedBox)).elementAt(1);
expect(decoratedBox.decoration.runtimeType, BoxDecoration);
final BoxDecoration decoration = decoratedBox.decoration;
expect(decoration.color, const Color(0xFF010203));
});
}