Add iOS 11 style large titles to cupertino nav bar (#12002)
* Things lay out but the effects not right yet * Remaining functionalities and tests * one line large title only * Add more docs * review
This commit is contained in:
parent
e830c5ebf6
commit
fa0bcce708
@ -9,12 +9,32 @@ import 'package:flutter/widgets.dart';
|
|||||||
|
|
||||||
import 'colors.dart';
|
import 'colors.dart';
|
||||||
|
|
||||||
// Standard iOS 10 nav bar height without the status bar.
|
/// Standard iOS nav bar height without the status bar.
|
||||||
const double _kNavBarHeight = 44.0;
|
const double _kNavBarPersistentHeight = 44.0;
|
||||||
|
|
||||||
|
/// Size increase from expanding the nav bar into an iOS 11 style large title
|
||||||
|
/// form in a [CustomScrollView].
|
||||||
|
const double _kNavBarLargeTitleHeightExtension = 56.0;
|
||||||
|
|
||||||
|
/// Number of logical pixels scrolled down before the title text is transferred
|
||||||
|
/// from the normal nav bar to a big title below the nav bar.
|
||||||
|
const double _kNavBarShowLargeTitleThreshold = 10.0;
|
||||||
|
|
||||||
|
const double _kNavBarEdgePadding = 16.0;
|
||||||
|
|
||||||
|
/// Title text transfer fade.
|
||||||
|
const Duration _kNavBarTitleFadeDuration = const Duration(milliseconds: 150);
|
||||||
|
|
||||||
const Color _kDefaultNavBarBackgroundColor = const Color(0xCCF8F8F8);
|
const Color _kDefaultNavBarBackgroundColor = const Color(0xCCF8F8F8);
|
||||||
const Color _kDefaultNavBarBorderColor = const Color(0x4C000000);
|
const Color _kDefaultNavBarBorderColor = const Color(0x4C000000);
|
||||||
|
|
||||||
|
const TextStyle _kLargeTitleTextStyle = const TextStyle(
|
||||||
|
fontSize: 34.0,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
letterSpacing: 0.41,
|
||||||
|
color: CupertinoColors.black,
|
||||||
|
);
|
||||||
|
|
||||||
/// An iOS-styled navigation bar.
|
/// An iOS-styled navigation bar.
|
||||||
///
|
///
|
||||||
/// The navigation bar is a toolbar that minimally consists of a widget, normally
|
/// The navigation bar is a toolbar that minimally consists of a widget, normally
|
||||||
@ -28,6 +48,10 @@ const Color _kDefaultNavBarBorderColor = const Color(0x4C000000);
|
|||||||
///
|
///
|
||||||
/// If the given [backgroundColor]'s opacity is not 1.0 (which is the case by
|
/// If the given [backgroundColor]'s opacity is not 1.0 (which is the case by
|
||||||
/// default), it will produce a blurring effect to the content behind it.
|
/// default), it will produce a blurring effect to the content behind it.
|
||||||
|
///
|
||||||
|
/// Enabling [largeTitle] will create a scrollable second row showing the title
|
||||||
|
/// in a larger font introduced in iOS 11. The [middle] widget must be a text
|
||||||
|
/// and the [CupertinoNavigationBar] must be placed in a sliver group in this case.
|
||||||
//
|
//
|
||||||
// TODO(xster): document automatic addition of a CupertinoBackButton.
|
// TODO(xster): document automatic addition of a CupertinoBackButton.
|
||||||
// TODO(xster): add sample code using icons.
|
// TODO(xster): add sample code using icons.
|
||||||
@ -41,8 +65,9 @@ class CupertinoNavigationBar extends StatelessWidget implements PreferredSizeWid
|
|||||||
this.trailing,
|
this.trailing,
|
||||||
this.backgroundColor: _kDefaultNavBarBackgroundColor,
|
this.backgroundColor: _kDefaultNavBarBackgroundColor,
|
||||||
this.actionsForegroundColor: CupertinoColors.activeBlue,
|
this.actionsForegroundColor: CupertinoColors.activeBlue,
|
||||||
|
this.largeTitle: false,
|
||||||
}) : assert(middle != null, 'There must be a middle widget, usually a title.'),
|
}) : assert(middle != null, 'There must be a middle widget, usually a title.'),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
/// Widget to place at the start of the nav bar. Normally a back button
|
/// Widget to place at the start of the nav bar. Normally a back button
|
||||||
/// for a normal page or a cancel button for full page dialogs.
|
/// for a normal page or a cancel button for full page dialogs.
|
||||||
@ -73,8 +98,110 @@ class CupertinoNavigationBar extends StatelessWidget implements PreferredSizeWid
|
|||||||
/// True if the nav bar's background color has no transparency.
|
/// True if the nav bar's background color has no transparency.
|
||||||
bool get opaque => backgroundColor.alpha == 0xFF;
|
bool get opaque => backgroundColor.alpha == 0xFF;
|
||||||
|
|
||||||
|
/// Use iOS 11 style large title navigation bars.
|
||||||
|
///
|
||||||
|
/// When true, the navigation bar will split into 2 sections. The static
|
||||||
|
/// top 44px section will be wrapped in a SliverPersistentHeader and a
|
||||||
|
/// second scrollable section behind it will show and replace the `middle`
|
||||||
|
/// text in a larger font when scrolled down.
|
||||||
|
///
|
||||||
|
/// Navigation bars with large titles must be used in a sliver group such
|
||||||
|
/// as [CustomScrollView].
|
||||||
|
final bool largeTitle;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Size get preferredSize => const Size.fromHeight(_kNavBarHeight);
|
Size get preferredSize => const Size.fromHeight(_kNavBarPersistentHeight);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
assert(
|
||||||
|
!largeTitle || middle is Text,
|
||||||
|
"largeTitle mode is only possible when 'middle' is a Text widget",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!largeTitle) {
|
||||||
|
return _wrapWithBackground(
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
child: new _CupertinoPersistentNavigationBar(
|
||||||
|
leading: leading,
|
||||||
|
middle: middle,
|
||||||
|
trailing: trailing,
|
||||||
|
actionsForegroundColor: actionsForegroundColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return new SliverPersistentHeader(
|
||||||
|
pinned: true, // iOS navigation bars are always pinned.
|
||||||
|
delegate: new _CupertinoLargeTitleNavigationBarSliverDelegate(
|
||||||
|
persistentHeight: _kNavBarPersistentHeight + MediaQuery.of(context).padding.top,
|
||||||
|
leading: leading,
|
||||||
|
middle: middle,
|
||||||
|
trailing: trailing,
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
actionsForegroundColor: actionsForegroundColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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}) {
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
color: backgroundColor,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (backgroundColor.alpha == 0xFF)
|
||||||
|
return childWithBackground;
|
||||||
|
|
||||||
|
return new ClipRect(
|
||||||
|
child: new BackdropFilter(
|
||||||
|
filter: new ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
|
||||||
|
child: childWithBackground,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The top part of the nav bar that's never scrolled away.
|
||||||
|
///
|
||||||
|
/// Consists of the entire nav bar without background and border when used
|
||||||
|
/// without large titles. With large titles, it's the top static half that
|
||||||
|
/// doesn't scroll.
|
||||||
|
class _CupertinoPersistentNavigationBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
|
const _CupertinoPersistentNavigationBar({
|
||||||
|
Key key,
|
||||||
|
this.leading,
|
||||||
|
@required this.middle,
|
||||||
|
this.trailing,
|
||||||
|
this.actionsForegroundColor,
|
||||||
|
this.middleVisible,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final Widget leading;
|
||||||
|
|
||||||
|
final Widget middle;
|
||||||
|
|
||||||
|
final Widget trailing;
|
||||||
|
|
||||||
|
final Color actionsForegroundColor;
|
||||||
|
|
||||||
|
/// Whether the middle widget has a visible animated opacity. A null value
|
||||||
|
/// means the middle opacity will not be animated.
|
||||||
|
final bool middleVisible;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Size get preferredSize => const Size.fromHeight(_kNavBarPersistentHeight);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -101,55 +228,139 @@ class CupertinoNavigationBar extends StatelessWidget implements PreferredSizeWid
|
|||||||
child: middle,
|
child: middle,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final Widget animatedStyledMiddle = middleVisible == null
|
||||||
|
? styledMiddle
|
||||||
|
: new AnimatedOpacity(
|
||||||
|
opacity: middleVisible ? 1.0 : 0.0,
|
||||||
|
duration: _kNavBarTitleFadeDuration,
|
||||||
|
child: styledMiddle,
|
||||||
|
);
|
||||||
|
|
||||||
// TODO(xster): automatically build a CupertinoBackButton.
|
// TODO(xster): automatically build a CupertinoBackButton.
|
||||||
|
|
||||||
Widget result = new DecoratedBox(
|
return new SizedBox(
|
||||||
decoration: new BoxDecoration(
|
height: _kNavBarPersistentHeight + MediaQuery.of(context).padding.top,
|
||||||
border: const Border(
|
child: IconTheme.merge(
|
||||||
bottom: const BorderSide(
|
data: new IconThemeData(
|
||||||
color: _kDefaultNavBarBorderColor,
|
color: actionsForegroundColor,
|
||||||
width: 0.0, // One physical pixel.
|
size: 22.0,
|
||||||
style: BorderStyle.solid,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
color: backgroundColor,
|
child: new Padding(
|
||||||
),
|
padding: new EdgeInsets.only(
|
||||||
child: new SizedBox(
|
top: MediaQuery.of(context).padding.top,
|
||||||
height: _kNavBarHeight + MediaQuery.of(context).padding.top,
|
// TODO(xster): dynamically reduce padding when an automatic
|
||||||
child: IconTheme.merge(
|
// CupertinoBackButton is present.
|
||||||
data: new IconThemeData(
|
left: _kNavBarEdgePadding,
|
||||||
color: actionsForegroundColor,
|
right: _kNavBarEdgePadding,
|
||||||
size: 22.0,
|
|
||||||
),
|
),
|
||||||
child: new Padding(
|
child: new NavigationToolbar(
|
||||||
padding: new EdgeInsets.only(
|
leading: styledLeading,
|
||||||
top: MediaQuery.of(context).padding.top,
|
middle: animatedStyledMiddle,
|
||||||
// TODO(xster): dynamically reduce padding when an automatic
|
trailing: styledTrailing,
|
||||||
// CupertinoBackButton is present.
|
centerMiddle: true,
|
||||||
left: 16.0,
|
|
||||||
right: 16.0,
|
|
||||||
),
|
|
||||||
child: new NavigationToolbar(
|
|
||||||
leading: styledLeading,
|
|
||||||
middle: styledMiddle,
|
|
||||||
trailing: styledTrailing,
|
|
||||||
centerMiddle: true,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
if (!opaque) {
|
}
|
||||||
// For non-opaque backgrounds, apply a blur effect.
|
|
||||||
result = new ClipRect(
|
class _CupertinoLargeTitleNavigationBarSliverDelegate extends SliverPersistentHeaderDelegate {
|
||||||
child: new BackdropFilter(
|
const _CupertinoLargeTitleNavigationBarSliverDelegate({
|
||||||
filter: new ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
|
@required this.persistentHeight,
|
||||||
child: result,
|
this.leading,
|
||||||
),
|
@required this.middle,
|
||||||
);
|
this.trailing,
|
||||||
}
|
this.backgroundColor,
|
||||||
|
this.actionsForegroundColor,
|
||||||
return result;
|
}) : assert(persistentHeight != null);
|
||||||
|
|
||||||
|
final double persistentHeight;
|
||||||
|
|
||||||
|
final Widget leading;
|
||||||
|
|
||||||
|
final Text middle;
|
||||||
|
|
||||||
|
final Widget trailing;
|
||||||
|
|
||||||
|
final Color backgroundColor;
|
||||||
|
|
||||||
|
final Color actionsForegroundColor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
double get minExtent => persistentHeight;
|
||||||
|
|
||||||
|
@override
|
||||||
|
double get maxExtent => persistentHeight + _kNavBarLargeTitleHeightExtension;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
|
||||||
|
final bool showLargeTitle = shrinkOffset < maxExtent - minExtent - _kNavBarShowLargeTitleThreshold;
|
||||||
|
|
||||||
|
final _CupertinoPersistentNavigationBar persistentNavigationBar =
|
||||||
|
new _CupertinoPersistentNavigationBar(
|
||||||
|
leading: leading,
|
||||||
|
middle: middle,
|
||||||
|
trailing: trailing,
|
||||||
|
middleVisible: !showLargeTitle,
|
||||||
|
actionsForegroundColor: actionsForegroundColor,
|
||||||
|
);
|
||||||
|
|
||||||
|
return _wrapWithBackground(
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
child: new Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: <Widget>[
|
||||||
|
new Positioned(
|
||||||
|
top: persistentHeight,
|
||||||
|
left: 0.0,
|
||||||
|
right: 0.0,
|
||||||
|
bottom: 0.0,
|
||||||
|
child: new ClipRect(
|
||||||
|
// The large title starts at the persistent bar.
|
||||||
|
// It's aligned with the bottom of the sliver and expands clipped
|
||||||
|
// and behind the persistent bar.
|
||||||
|
child: new OverflowBox(
|
||||||
|
minHeight: 0.0,
|
||||||
|
maxHeight: double.INFINITY,
|
||||||
|
alignment: FractionalOffsetDirectional.bottomStart,
|
||||||
|
child: new Padding(
|
||||||
|
padding: const EdgeInsetsDirectional.only(
|
||||||
|
start: _kNavBarEdgePadding,
|
||||||
|
bottom: 8.0, // Bottom has a different padding.
|
||||||
|
),
|
||||||
|
child: new DefaultTextStyle(
|
||||||
|
style: _kLargeTitleTextStyle,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
child: new AnimatedOpacity(
|
||||||
|
opacity: showLargeTitle ? 1.0 : 0.0,
|
||||||
|
duration: _kNavBarTitleFadeDuration,
|
||||||
|
child: middle,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
new Positioned(
|
||||||
|
left: 0.0,
|
||||||
|
right: 0.0,
|
||||||
|
top: 0.0,
|
||||||
|
child: persistentNavigationBar,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRebuild(_CupertinoLargeTitleNavigationBarSliverDelegate oldDelegate) {
|
||||||
|
return persistentHeight != oldDelegate.persistentHeight ||
|
||||||
|
leading != oldDelegate.leading ||
|
||||||
|
middle != oldDelegate.middle ||
|
||||||
|
trailing != oldDelegate.trailing ||
|
||||||
|
backgroundColor != oldDelegate.backgroundColor ||
|
||||||
|
actionsForegroundColor != oldDelegate.actionsForegroundColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,9 +113,10 @@ class _CupertinoScaffoldState extends State<CupertinoScaffold> {
|
|||||||
/// Pad the given middle widget with or without top and bottom offsets depending
|
/// Pad the given middle widget with or without top and bottom offsets depending
|
||||||
/// on whether the middle widget should slide behind translucent bars.
|
/// on whether the middle widget should slide behind translucent bars.
|
||||||
Widget _padMiddle(Widget middle) {
|
Widget _padMiddle(Widget middle) {
|
||||||
double topPadding = MediaQuery.of(context).padding.top;
|
double topPadding = 0.0;
|
||||||
if (widget.navigationBar is CupertinoNavigationBar) {
|
if (widget.navigationBar is CupertinoNavigationBar) {
|
||||||
final CupertinoNavigationBar top = widget.navigationBar;
|
final CupertinoNavigationBar top = widget.navigationBar;
|
||||||
|
topPadding += MediaQuery.of(context).padding.top;
|
||||||
if (top.opaque)
|
if (top.opaque)
|
||||||
topPadding += top.preferredSize.height;
|
topPadding += top.preferredSize.height;
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart' hide TypeMatcher;
|
||||||
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
@ -14,9 +14,9 @@ void main() {
|
|||||||
new WidgetsApp(
|
new WidgetsApp(
|
||||||
color: const Color(0xFFFFFFFF),
|
color: const Color(0xFFFFFFFF),
|
||||||
onGenerateRoute: (RouteSettings settings) {
|
onGenerateRoute: (RouteSettings settings) {
|
||||||
return new PageRouteBuilder<Null>(
|
return new CupertinoPageRoute<Null>(
|
||||||
settings: settings,
|
settings: settings,
|
||||||
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
|
builder: (BuildContext context) {
|
||||||
return const CupertinoNavigationBar(
|
return const CupertinoNavigationBar(
|
||||||
leading: const CupertinoButton(child: const Text('Something'), onPressed: null,),
|
leading: const CupertinoButton(child: const Text('Something'), onPressed: null,),
|
||||||
middle: const Text('Title'),
|
middle: const Text('Title'),
|
||||||
@ -36,9 +36,9 @@ void main() {
|
|||||||
new WidgetsApp(
|
new WidgetsApp(
|
||||||
color: const Color(0xFFFFFFFF),
|
color: const Color(0xFFFFFFFF),
|
||||||
onGenerateRoute: (RouteSettings settings) {
|
onGenerateRoute: (RouteSettings settings) {
|
||||||
return new PageRouteBuilder<Null>(
|
return new CupertinoPageRoute<Null>(
|
||||||
settings: settings,
|
settings: settings,
|
||||||
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
|
builder: (BuildContext context) {
|
||||||
return const CupertinoNavigationBar(
|
return const CupertinoNavigationBar(
|
||||||
middle: const Text('Title'),
|
middle: const Text('Title'),
|
||||||
backgroundColor: const Color(0xFFE5E5E5),
|
backgroundColor: const Color(0xFFE5E5E5),
|
||||||
@ -56,9 +56,9 @@ void main() {
|
|||||||
new WidgetsApp(
|
new WidgetsApp(
|
||||||
color: const Color(0xFFFFFFFF),
|
color: const Color(0xFFFFFFFF),
|
||||||
onGenerateRoute: (RouteSettings settings) {
|
onGenerateRoute: (RouteSettings settings) {
|
||||||
return new PageRouteBuilder<Null>(
|
return new CupertinoPageRoute<Null>(
|
||||||
settings: settings,
|
settings: settings,
|
||||||
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
|
builder: (BuildContext context) {
|
||||||
return const CupertinoNavigationBar(
|
return const CupertinoNavigationBar(
|
||||||
middle: const Text('Title'),
|
middle: const Text('Title'),
|
||||||
);
|
);
|
||||||
@ -76,9 +76,9 @@ void main() {
|
|||||||
new WidgetsApp(
|
new WidgetsApp(
|
||||||
color: const Color(0xFFFFFFFF),
|
color: const Color(0xFFFFFFFF),
|
||||||
onGenerateRoute: (RouteSettings settings) {
|
onGenerateRoute: (RouteSettings settings) {
|
||||||
return new PageRouteBuilder<Null>(
|
return new CupertinoPageRoute<Null>(
|
||||||
settings: settings,
|
settings: settings,
|
||||||
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
|
builder: (BuildContext context) {
|
||||||
return const CupertinoNavigationBar(
|
return const CupertinoNavigationBar(
|
||||||
leading: const _ExpectStyles(color: const Color(0xFF001122), index: 0x000001),
|
leading: const _ExpectStyles(color: const Color(0xFF001122), index: 0x000001),
|
||||||
middle: const _ExpectStyles(color: const Color(0xFF000000), index: 0x000100),
|
middle: const _ExpectStyles(color: const Color(0xFF000000), index: 0x000100),
|
||||||
@ -92,6 +92,118 @@ void main() {
|
|||||||
);
|
);
|
||||||
expect(count, 0x010101);
|
expect(count, 0x010101);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('No slivers with no large titles', (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 CupertinoScaffold(
|
||||||
|
navigationBar: const CupertinoNavigationBar(
|
||||||
|
middle: const Text('Title'),
|
||||||
|
),
|
||||||
|
child: const Center(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byType(SliverPersistentHeader), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Large title nav bar scrolls', (WidgetTester tester) async {
|
||||||
|
final ScrollController scrollController = new ScrollController();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new WidgetsApp(
|
||||||
|
color: const Color(0xFFFFFFFF),
|
||||||
|
onGenerateRoute: (RouteSettings settings) {
|
||||||
|
return new CupertinoPageRoute<Null>(
|
||||||
|
settings: settings,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return new CupertinoScaffold(
|
||||||
|
child: new CustomScrollView(
|
||||||
|
controller: scrollController,
|
||||||
|
slivers: <Widget>[
|
||||||
|
const CupertinoNavigationBar(
|
||||||
|
middle: const Text('Title'),
|
||||||
|
largeTitle: true,
|
||||||
|
),
|
||||||
|
new SliverToBoxAdapter(
|
||||||
|
child: new Container(
|
||||||
|
height: 1200.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(scrollController.offset, 0.0);
|
||||||
|
expect(tester.getTopLeft(find.byType(NavigationToolbar)).dy, 0.0);
|
||||||
|
expect(tester.getSize(find.byType(NavigationToolbar)).height, 44.0);
|
||||||
|
|
||||||
|
expect(find.text('Title'), findsNWidgets(2)); // Though only one is visible.
|
||||||
|
|
||||||
|
List<Element> titles = tester.elementList(find.text('Title'))
|
||||||
|
.toList()
|
||||||
|
..sort((Element a, Element b) {
|
||||||
|
final RenderParagraph aParagraph = a.renderObject;
|
||||||
|
final RenderParagraph bParagraph = b.renderObject;
|
||||||
|
return aParagraph.text.style.fontSize.compareTo(bParagraph.text.style.fontSize);
|
||||||
|
});
|
||||||
|
|
||||||
|
Iterable<double> opacities = titles.map((Element element) {
|
||||||
|
final RenderOpacity renderOpacity = element.ancestorRenderObjectOfType(const TypeMatcher<RenderOpacity>());
|
||||||
|
return renderOpacity.opacity;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(opacities, <double> [
|
||||||
|
0.0, // Initially the smaller font title is invisible.
|
||||||
|
1.0, // The larger font title is visible.
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(tester.getTopLeft(find.widgetWithText(OverflowBox, 'Title')).dy, 44.0);
|
||||||
|
expect(tester.getSize(find.widgetWithText(OverflowBox, 'Title')).height, 56.0);
|
||||||
|
|
||||||
|
scrollController.jumpTo(600.0);
|
||||||
|
await tester.pump(); // Once to trigger the opacity animation.
|
||||||
|
await tester.pump(const Duration(milliseconds: 300));
|
||||||
|
|
||||||
|
titles = tester.elementList(find.text('Title'))
|
||||||
|
.toList()
|
||||||
|
..sort((Element a, Element b) {
|
||||||
|
final RenderParagraph aParagraph = a.renderObject;
|
||||||
|
final RenderParagraph bParagraph = b.renderObject;
|
||||||
|
return aParagraph.text.style.fontSize.compareTo(bParagraph.text.style.fontSize);
|
||||||
|
});
|
||||||
|
|
||||||
|
opacities = titles.map((Element element) {
|
||||||
|
final RenderOpacity renderOpacity = element.ancestorRenderObjectOfType(const TypeMatcher<RenderOpacity>());
|
||||||
|
return renderOpacity.opacity;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(opacities, <double> [
|
||||||
|
1.0, // Smaller font title now visiblee
|
||||||
|
0.0, // Larger font title invisible.
|
||||||
|
]);
|
||||||
|
|
||||||
|
// The persistent toolbar doesn't move or change size.
|
||||||
|
expect(tester.getTopLeft(find.byType(NavigationToolbar)).dy, 0.0);
|
||||||
|
expect(tester.getSize(find.byType(NavigationToolbar)).height, 44.0);
|
||||||
|
|
||||||
|
expect(tester.getTopLeft(find.widgetWithText(OverflowBox, 'Title')).dy, 44.0);
|
||||||
|
// The OverflowBox is squished with the text in it.
|
||||||
|
expect(tester.getSize(find.widgetWithText(OverflowBox, 'Title')).height, 0.0);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ExpectStyles extends StatelessWidget {
|
class _ExpectStyles extends StatelessWidget {
|
||||||
|
@ -20,10 +20,9 @@ void main() {
|
|||||||
new WidgetsApp(
|
new WidgetsApp(
|
||||||
color: const Color(0xFFFFFFFF),
|
color: const Color(0xFFFFFFFF),
|
||||||
onGenerateRoute: (RouteSettings settings) {
|
onGenerateRoute: (RouteSettings settings) {
|
||||||
// TODO(xster): change to a CupertinoPageRoute.
|
return new CupertinoPageRoute<Null>(
|
||||||
return new PageRouteBuilder<Null>(
|
|
||||||
settings: settings,
|
settings: settings,
|
||||||
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
|
builder: (BuildContext context) {
|
||||||
return const CupertinoScaffold(
|
return const CupertinoScaffold(
|
||||||
// Default nav bar is translucent.
|
// Default nav bar is translucent.
|
||||||
navigationBar: const CupertinoNavigationBar(
|
navigationBar: const CupertinoNavigationBar(
|
||||||
@ -47,10 +46,9 @@ void main() {
|
|||||||
new WidgetsApp(
|
new WidgetsApp(
|
||||||
color: const Color(0xFFFFFFFF),
|
color: const Color(0xFFFFFFFF),
|
||||||
onGenerateRoute: (RouteSettings settings) {
|
onGenerateRoute: (RouteSettings settings) {
|
||||||
// TODO(xster): change to a CupertinoPageRoute.
|
return new CupertinoPageRoute<Null>(
|
||||||
return new PageRouteBuilder<Null>(
|
|
||||||
settings: settings,
|
settings: settings,
|
||||||
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
|
builder: (BuildContext context) {
|
||||||
return new CupertinoScaffold.tabbed(
|
return new CupertinoScaffold.tabbed(
|
||||||
navigationBar: const CupertinoNavigationBar(
|
navigationBar: const CupertinoNavigationBar(
|
||||||
backgroundColor: CupertinoColors.white,
|
backgroundColor: CupertinoColors.white,
|
||||||
@ -77,10 +75,9 @@ void main() {
|
|||||||
new WidgetsApp(
|
new WidgetsApp(
|
||||||
color: const Color(0xFFFFFFFF),
|
color: const Color(0xFFFFFFFF),
|
||||||
onGenerateRoute: (RouteSettings settings) {
|
onGenerateRoute: (RouteSettings settings) {
|
||||||
// TODO(xster): change to a CupertinoPageRoute.
|
return new CupertinoPageRoute<Null>(
|
||||||
return new PageRouteBuilder<Null>(
|
|
||||||
settings: settings,
|
settings: settings,
|
||||||
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
|
builder: (BuildContext context) {
|
||||||
return new CupertinoScaffold.tabbed(
|
return new CupertinoScaffold.tabbed(
|
||||||
navigationBar: const CupertinoNavigationBar(
|
navigationBar: const CupertinoNavigationBar(
|
||||||
backgroundColor: CupertinoColors.white,
|
backgroundColor: CupertinoColors.white,
|
||||||
@ -142,10 +139,9 @@ void main() {
|
|||||||
new WidgetsApp(
|
new WidgetsApp(
|
||||||
color: const Color(0xFFFFFFFF),
|
color: const Color(0xFFFFFFFF),
|
||||||
onGenerateRoute: (RouteSettings settings) {
|
onGenerateRoute: (RouteSettings settings) {
|
||||||
// TODO(xster): change to a CupertinoPageRoute.
|
return new CupertinoPageRoute<Null>(
|
||||||
return new PageRouteBuilder<Null>(
|
|
||||||
settings: settings,
|
settings: settings,
|
||||||
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
|
builder: (BuildContext context) {
|
||||||
return new CupertinoScaffold.tabbed(
|
return new CupertinoScaffold.tabbed(
|
||||||
navigationBar: const CupertinoNavigationBar(
|
navigationBar: const CupertinoNavigationBar(
|
||||||
backgroundColor: CupertinoColors.white,
|
backgroundColor: CupertinoColors.white,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user