Added support for a pinned floating SliverAppBar (#8345)
This commit is contained in:
parent
9ada90a160
commit
3a0b83b1b6
@ -96,6 +96,29 @@ class _ToolbarLayout extends MultiChildLayoutDelegate {
|
|||||||
bool shouldRelayout(_ToolbarLayout oldDelegate) => centerTitle != oldDelegate.centerTitle;
|
bool shouldRelayout(_ToolbarLayout oldDelegate) => centerTitle != oldDelegate.centerTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bottom justify the kToolbarHeight child which may overflow the top.
|
||||||
|
class _ToolbarContainerLayout extends SingleChildLayoutDelegate {
|
||||||
|
const _ToolbarContainerLayout();
|
||||||
|
|
||||||
|
@override
|
||||||
|
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
|
||||||
|
return constraints.tighten(height: kToolbarHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Size getSize(BoxConstraints constraints) {
|
||||||
|
return new Size(constraints.maxWidth, kToolbarHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Offset getPositionForChild(Size size, Size childSize) {
|
||||||
|
return new Offset(0.0, size.height - childSize.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRelayout(_ToolbarContainerLayout oldDelegate) => false;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(eseidel) Toolbar needs to change size based on orientation:
|
// TODO(eseidel) Toolbar needs to change size based on orientation:
|
||||||
// http://material.google.com/layout/structure.html#structure-app-bar
|
// http://material.google.com/layout/structure.html#structure-app-bar
|
||||||
// Mobile Landscape: 48dp
|
// Mobile Landscape: 48dp
|
||||||
@ -425,8 +448,11 @@ class _AppBarState extends State<AppBar> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget appBar = new SizedBox(
|
// If the toolbar is allocated less than kToolbarHeight make it
|
||||||
height: kToolbarHeight,
|
// appear to scroll upwards within its shrinking container.
|
||||||
|
Widget appBar = new ClipRect(
|
||||||
|
child: new CustomSingleChildLayout(
|
||||||
|
delegate: const _ToolbarContainerLayout(),
|
||||||
child: new IconTheme.merge(
|
child: new IconTheme.merge(
|
||||||
context: context,
|
context: context,
|
||||||
data: appBarIconTheme,
|
data: appBarIconTheme,
|
||||||
@ -435,14 +461,19 @@ class _AppBarState extends State<AppBar> {
|
|||||||
child: toolbar,
|
child: toolbar,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
if (config.bottom != null) {
|
if (config.bottom != null) {
|
||||||
appBar = new Column(
|
appBar = new Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
appBar,
|
new Flexible(
|
||||||
|
child: new ConstrainedBox(
|
||||||
|
constraints: new BoxConstraints(maxHeight: kToolbarHeight),
|
||||||
|
child: appBar,
|
||||||
|
),
|
||||||
|
),
|
||||||
config.bottomOpacity == 1.0 ? config.bottom : new Opacity(
|
config.bottomOpacity == 1.0 ? config.bottom : new Opacity(
|
||||||
opacity: const Interval(0.25, 1.0, curve: Curves.fastOutSlowIn).transform(config.bottomOpacity),
|
opacity: const Interval(0.25, 1.0, curve: Curves.fastOutSlowIn).transform(config.bottomOpacity),
|
||||||
child: config.bottom,
|
child: config.bottom,
|
||||||
@ -494,7 +525,9 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
|
|||||||
@required this.primary,
|
@required this.primary,
|
||||||
@required this.centerTitle,
|
@required this.centerTitle,
|
||||||
@required this.expandedHeight,
|
@required this.expandedHeight,
|
||||||
|
@required this.collapsedHeight,
|
||||||
@required this.topPadding,
|
@required this.topPadding,
|
||||||
|
@required this.floating,
|
||||||
@required this.pinned,
|
@required this.pinned,
|
||||||
}) : bottom = bottom,
|
}) : bottom = bottom,
|
||||||
_bottomHeight = bottom?.bottomHeight ?? 0.0 {
|
_bottomHeight = bottom?.bottomHeight ?? 0.0 {
|
||||||
@ -514,21 +547,24 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
|
|||||||
final bool primary;
|
final bool primary;
|
||||||
final bool centerTitle;
|
final bool centerTitle;
|
||||||
final double expandedHeight;
|
final double expandedHeight;
|
||||||
|
final double collapsedHeight;
|
||||||
final double topPadding;
|
final double topPadding;
|
||||||
|
final bool floating;
|
||||||
final bool pinned;
|
final bool pinned;
|
||||||
|
|
||||||
final double _bottomHeight;
|
final double _bottomHeight;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
double get minExtent => topPadding + kToolbarHeight + _bottomHeight;
|
double get minExtent => collapsedHeight ?? (topPadding + kToolbarHeight + _bottomHeight);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
double get maxExtent => math.max(topPadding + (expandedHeight ?? kToolbarHeight), minExtent);
|
double get maxExtent => math.max(topPadding + (expandedHeight ?? kToolbarHeight + _bottomHeight), minExtent);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
|
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
|
||||||
double visibleMainHeight = maxExtent - shrinkOffset - topPadding;
|
final double visibleMainHeight = maxExtent - shrinkOffset - topPadding;
|
||||||
double toolbarOpacity = pinned ? 1.0 : ((visibleMainHeight - _bottomHeight) / kToolbarHeight).clamp(0.0, 1.0);
|
final double toolbarOpacity = pinned && !floating ? 1.0
|
||||||
|
: ((visibleMainHeight - _bottomHeight) / kToolbarHeight).clamp(0.0, 1.0);
|
||||||
return FlexibleSpaceBar.createSettings(
|
return FlexibleSpaceBar.createSettings(
|
||||||
minExtent: minExtent,
|
minExtent: minExtent,
|
||||||
maxExtent: maxExtent,
|
maxExtent: maxExtent,
|
||||||
@ -569,7 +605,9 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
|
|||||||
|| primary != oldDelegate.primary
|
|| primary != oldDelegate.primary
|
||||||
|| centerTitle != oldDelegate.centerTitle
|
|| centerTitle != oldDelegate.centerTitle
|
||||||
|| expandedHeight != oldDelegate.expandedHeight
|
|| expandedHeight != oldDelegate.expandedHeight
|
||||||
|| topPadding != oldDelegate.topPadding;
|
|| topPadding != oldDelegate.topPadding
|
||||||
|
|| pinned != oldDelegate.pinned
|
||||||
|
|| floating != oldDelegate.floating;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -630,7 +668,7 @@ class SliverAppBar extends StatelessWidget {
|
|||||||
assert(primary != null);
|
assert(primary != null);
|
||||||
assert(floating != null);
|
assert(floating != null);
|
||||||
assert(pinned != null);
|
assert(pinned != null);
|
||||||
assert(!floating || !pinned);
|
assert(pinned && floating ? bottom != null : true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A widget to display before the [title].
|
/// A widget to display before the [title].
|
||||||
@ -763,6 +801,10 @@ class SliverAppBar extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final double topPadding = primary ? MediaQuery.of(context).padding.top : 0.0;
|
||||||
|
final double collapsedHeight = (pinned && floating && bottom != null)
|
||||||
|
? bottom.bottomHeight + topPadding : null;
|
||||||
|
|
||||||
return new SliverPersistentHeader(
|
return new SliverPersistentHeader(
|
||||||
floating: floating,
|
floating: floating,
|
||||||
pinned: pinned,
|
pinned: pinned,
|
||||||
@ -780,7 +822,9 @@ class SliverAppBar extends StatelessWidget {
|
|||||||
primary: primary,
|
primary: primary,
|
||||||
centerTitle: centerTitle,
|
centerTitle: centerTitle,
|
||||||
expandedHeight: expandedHeight,
|
expandedHeight: expandedHeight,
|
||||||
topPadding: primary ? MediaQuery.of(context).padding.top : 0.0,
|
collapsedHeight: collapsedHeight,
|
||||||
|
topPadding: topPadding,
|
||||||
|
floating: floating,
|
||||||
pinned: pinned,
|
pinned: pinned,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -700,6 +700,10 @@ class RenderFractionallySizedOverflowBox extends RenderAligningShiftedBox {
|
|||||||
|
|
||||||
/// A delegate for computing the layout of a render object with a single child.
|
/// A delegate for computing the layout of a render object with a single child.
|
||||||
abstract class SingleChildLayoutDelegate {
|
abstract class SingleChildLayoutDelegate {
|
||||||
|
/// Abstract const constructor. This constructor enables subclasses to provide
|
||||||
|
/// const constructors so that they can be used in const expressions.
|
||||||
|
const SingleChildLayoutDelegate();
|
||||||
|
|
||||||
// TODO(abarth): This class should take a Listenable to drive relayout.
|
// TODO(abarth): This class should take a Listenable to drive relayout.
|
||||||
|
|
||||||
/// The size of this object given the incoming constraints.
|
/// The size of this object given the incoming constraints.
|
||||||
|
@ -265,6 +265,23 @@ abstract class RenderSliverFloatingPersistentHeader extends RenderSliverPersiste
|
|||||||
// direction. Negative if we're scrolled off the top.
|
// direction. Negative if we're scrolled off the top.
|
||||||
double _childPosition;
|
double _childPosition;
|
||||||
|
|
||||||
|
// Update [geometry] and return the new value for [childMainAxisPosition].
|
||||||
|
@protected
|
||||||
|
double updateGeometry() {
|
||||||
|
final double maxExtent = this.maxExtent;
|
||||||
|
final double paintExtent = maxExtent - _effectiveScrollOffset;
|
||||||
|
final double layoutExtent = maxExtent - constraints.scrollOffset;
|
||||||
|
geometry = new SliverGeometry(
|
||||||
|
scrollExtent: maxExtent,
|
||||||
|
paintExtent: paintExtent.clamp(0.0, constraints.remainingPaintExtent),
|
||||||
|
layoutExtent: layoutExtent.clamp(0.0, constraints.remainingPaintExtent),
|
||||||
|
maxPaintExtent: maxExtent,
|
||||||
|
hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity.
|
||||||
|
);
|
||||||
|
return math.min(0.0, paintExtent - childExtent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void performLayout() {
|
void performLayout() {
|
||||||
final double maxExtent = this.maxExtent;
|
final double maxExtent = this.maxExtent;
|
||||||
@ -285,16 +302,7 @@ abstract class RenderSliverFloatingPersistentHeader extends RenderSliverPersiste
|
|||||||
_effectiveScrollOffset = constraints.scrollOffset;
|
_effectiveScrollOffset = constraints.scrollOffset;
|
||||||
}
|
}
|
||||||
layoutChild(_effectiveScrollOffset, maxExtent, overlapsContent: _effectiveScrollOffset < constraints.scrollOffset);
|
layoutChild(_effectiveScrollOffset, maxExtent, overlapsContent: _effectiveScrollOffset < constraints.scrollOffset);
|
||||||
final double paintExtent = maxExtent - _effectiveScrollOffset;
|
_childPosition = updateGeometry();
|
||||||
final double layoutExtent = (maxExtent - constraints.scrollOffset).clamp(0.0, constraints.remainingPaintExtent);
|
|
||||||
geometry = new SliverGeometry(
|
|
||||||
scrollExtent: maxExtent,
|
|
||||||
paintExtent: paintExtent.clamp(0.0, constraints.remainingPaintExtent),
|
|
||||||
layoutExtent: layoutExtent,
|
|
||||||
maxPaintExtent: maxExtent,
|
|
||||||
hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity.
|
|
||||||
);
|
|
||||||
_childPosition = math.min(0.0, paintExtent - childExtent);
|
|
||||||
_lastActualScrollOffset = constraints.scrollOffset;
|
_lastActualScrollOffset = constraints.scrollOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,3 +318,25 @@ abstract class RenderSliverFloatingPersistentHeader extends RenderSliverPersiste
|
|||||||
description.add('effective scroll offset: ${_effectiveScrollOffset?.toStringAsFixed(1)}');
|
description.add('effective scroll offset: ${_effectiveScrollOffset?.toStringAsFixed(1)}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class RenderSliverFloatingPinnedPersistentHeader extends RenderSliverFloatingPersistentHeader {
|
||||||
|
RenderSliverFloatingPinnedPersistentHeader({
|
||||||
|
RenderBox child,
|
||||||
|
}) : super(child: child);
|
||||||
|
|
||||||
|
@override
|
||||||
|
double updateGeometry() {
|
||||||
|
final double minExtent = this.maxExtent;
|
||||||
|
final double maxExtent = this.maxExtent;
|
||||||
|
final double paintExtent = (maxExtent - _effectiveScrollOffset);
|
||||||
|
final double layoutExtent = (maxExtent - constraints.scrollOffset);
|
||||||
|
geometry = new SliverGeometry(
|
||||||
|
scrollExtent: maxExtent,
|
||||||
|
paintExtent: paintExtent.clamp(minExtent, constraints.remainingPaintExtent),
|
||||||
|
layoutExtent: layoutExtent.clamp(0.0, constraints.remainingPaintExtent - minExtent),
|
||||||
|
maxPaintExtent: maxExtent,
|
||||||
|
hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity.
|
||||||
|
);
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -31,7 +31,6 @@ class SliverPersistentHeader extends StatelessWidget {
|
|||||||
assert(delegate != null);
|
assert(delegate != null);
|
||||||
assert(pinned != null);
|
assert(pinned != null);
|
||||||
assert(floating != null);
|
assert(floating != null);
|
||||||
assert(!pinned || !floating);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final SliverPersistentHeaderDelegate delegate;
|
final SliverPersistentHeaderDelegate delegate;
|
||||||
@ -42,6 +41,8 @@ class SliverPersistentHeader extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
if (floating && pinned)
|
||||||
|
return new _SliverFloatingPinnedPersistentHeader(delegate: delegate);
|
||||||
if (pinned)
|
if (pinned)
|
||||||
return new _SliverPinnedPersistentHeader(delegate: delegate);
|
return new _SliverPinnedPersistentHeader(delegate: delegate);
|
||||||
if (floating)
|
if (floating)
|
||||||
@ -227,6 +228,23 @@ class _SliverFloatingPersistentHeader extends _SliverPersistentHeaderRenderObjec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This class exists to work around https://github.com/dart-lang/sdk/issues/15101
|
||||||
|
abstract class _RenderSliverFloatingPinnedPersistentHeader extends RenderSliverFloatingPinnedPersistentHeader { }
|
||||||
|
|
||||||
|
class _RenderSliverFloatingPinnedPersistentHeaderForWidgets extends _RenderSliverFloatingPinnedPersistentHeader with _RenderSliverPersistentHeaderForWidgetsMixin { }
|
||||||
|
|
||||||
|
class _SliverFloatingPinnedPersistentHeader extends _SliverPersistentHeaderRenderObjectWidget {
|
||||||
|
_SliverFloatingPinnedPersistentHeader({
|
||||||
|
Key key,
|
||||||
|
@required SliverPersistentHeaderDelegate delegate,
|
||||||
|
}) : super(key: key, delegate: delegate);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context) {
|
||||||
|
return new _RenderSliverFloatingPinnedPersistentHeaderForWidgets();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This class exists to work around https://github.com/dart-lang/sdk/issues/15101
|
// This class exists to work around https://github.com/dart-lang/sdk/issues/15101
|
||||||
abstract class _RenderSliverFloatingPersistentHeader extends RenderSliverFloatingPersistentHeader { }
|
abstract class _RenderSliverFloatingPersistentHeader extends RenderSliverFloatingPersistentHeader { }
|
||||||
|
|
||||||
|
@ -4,8 +4,55 @@
|
|||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
Widget buildSliverAppBarApp({ bool floating, bool pinned, double expandedHeight }) {
|
||||||
|
return new Scaffold(
|
||||||
|
body: new CustomScrollView(
|
||||||
|
primary: true,
|
||||||
|
slivers: <Widget>[
|
||||||
|
new SliverAppBar(
|
||||||
|
title: new Text('AppBar Title'),
|
||||||
|
floating: floating,
|
||||||
|
pinned: pinned,
|
||||||
|
expandedHeight: expandedHeight,
|
||||||
|
bottom: new TabBar(
|
||||||
|
tabs: <String>['A','B','C'].map((String t) => new Tab(text: 'TAB $t')).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
new SliverToBoxAdapter(
|
||||||
|
child: new Container(
|
||||||
|
height: 1200.0,
|
||||||
|
decoration: new BoxDecoration(backgroundColor: Colors.orange[400]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollController primaryScrollController(WidgetTester tester) {
|
||||||
|
return PrimaryScrollController.of(tester.element(find.byType(CustomScrollView)));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool appBarIsVisible(WidgetTester tester) {
|
||||||
|
final RenderSliver sliver = tester.element(find.byType(SliverAppBar)).findRenderObject();
|
||||||
|
return sliver.geometry.visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
double appBarHeight(WidgetTester tester) {
|
||||||
|
final Element element = tester.element(find.byType(AppBar));
|
||||||
|
final RenderBox box = element.findRenderObject();
|
||||||
|
return box.size.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
double tabBarHeight(WidgetTester tester) {
|
||||||
|
final Element element = tester.element(find.byType(TabBar));
|
||||||
|
final RenderBox box = element.findRenderObject();
|
||||||
|
return box.size.height;
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('AppBar centers title on iOS', (WidgetTester tester) async {
|
testWidgets('AppBar centers title on iOS', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
@ -305,4 +352,105 @@ void main() {
|
|||||||
expect(tester.getSize(shareButton), new Size(48.0, 56.0));
|
expect(tester.getSize(shareButton), new Size(48.0, 56.0));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('SliverAppBar default configuration', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(buildSliverAppBarApp(
|
||||||
|
floating: false,
|
||||||
|
pinned: false,
|
||||||
|
expandedHeight: null,
|
||||||
|
));
|
||||||
|
|
||||||
|
ScrollController controller = primaryScrollController(tester);
|
||||||
|
expect(controller.offset, 0.0);
|
||||||
|
expect(appBarIsVisible(tester), true);
|
||||||
|
|
||||||
|
final double initialAppBarHeight = appBarHeight(tester);
|
||||||
|
final double initialTabBarHeight = tabBarHeight(tester);
|
||||||
|
|
||||||
|
// Scroll the not-pinned appbar partially out of view
|
||||||
|
controller.jumpTo(50.0);
|
||||||
|
await tester.pump();
|
||||||
|
expect(appBarIsVisible(tester), true);
|
||||||
|
expect(appBarHeight(tester), initialAppBarHeight);
|
||||||
|
expect(tabBarHeight(tester), initialTabBarHeight);
|
||||||
|
|
||||||
|
// Scroll the not-pinned appbar out of view
|
||||||
|
controller.jumpTo(600.0);
|
||||||
|
await tester.pump();
|
||||||
|
expect(appBarIsVisible(tester), false);
|
||||||
|
expect(appBarHeight(tester), initialAppBarHeight);
|
||||||
|
expect(tabBarHeight(tester), initialTabBarHeight);
|
||||||
|
|
||||||
|
// Scroll the not-pinned appbar back into view
|
||||||
|
controller.jumpTo(0.0);
|
||||||
|
await tester.pump();
|
||||||
|
expect(appBarIsVisible(tester), true);
|
||||||
|
expect(appBarHeight(tester), initialAppBarHeight);
|
||||||
|
expect(tabBarHeight(tester), initialTabBarHeight);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('SliverAppBar expandedHeight, pinned', (WidgetTester tester) async {
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildSliverAppBarApp(
|
||||||
|
floating: false,
|
||||||
|
pinned: true,
|
||||||
|
expandedHeight: 128.0,
|
||||||
|
));
|
||||||
|
|
||||||
|
ScrollController controller = primaryScrollController(tester);
|
||||||
|
expect(controller.offset, 0.0);
|
||||||
|
expect(appBarIsVisible(tester), true);
|
||||||
|
expect(appBarHeight(tester), 128.0);
|
||||||
|
|
||||||
|
final double initialAppBarHeight = 128.0;
|
||||||
|
final double initialTabBarHeight = tabBarHeight(tester);
|
||||||
|
|
||||||
|
// Scroll the not-pinned appbar, collapsing the expanded height. At this
|
||||||
|
// point both the toolbar and the tabbar are visible.
|
||||||
|
controller.jumpTo(600.0);
|
||||||
|
await tester.pump();
|
||||||
|
expect(appBarIsVisible(tester), true);
|
||||||
|
expect(tabBarHeight(tester), initialTabBarHeight);
|
||||||
|
expect(appBarHeight(tester), lessThan(initialAppBarHeight));
|
||||||
|
expect(appBarHeight(tester), greaterThan(initialTabBarHeight));
|
||||||
|
|
||||||
|
// Scroll the not-pinned appbar back into view
|
||||||
|
controller.jumpTo(0.0);
|
||||||
|
await tester.pump();
|
||||||
|
expect(appBarIsVisible(tester), true);
|
||||||
|
expect(appBarHeight(tester), initialAppBarHeight);
|
||||||
|
expect(tabBarHeight(tester), initialTabBarHeight);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('SliverAppBar expandedHeight, pinned and floating', (WidgetTester tester) async {
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildSliverAppBarApp(
|
||||||
|
floating: true,
|
||||||
|
pinned: true,
|
||||||
|
expandedHeight: 128.0,
|
||||||
|
));
|
||||||
|
|
||||||
|
ScrollController controller = primaryScrollController(tester);
|
||||||
|
expect(controller.offset, 0.0);
|
||||||
|
expect(appBarIsVisible(tester), true);
|
||||||
|
expect(appBarHeight(tester), 128.0);
|
||||||
|
|
||||||
|
final double initialAppBarHeight = 128.0;
|
||||||
|
final double initialTabBarHeight = tabBarHeight(tester);
|
||||||
|
|
||||||
|
// Scroll the not-pinned appbar, collapsing the expanded height. At this
|
||||||
|
// point only the tabBar is visible.
|
||||||
|
controller.jumpTo(600.0);
|
||||||
|
await tester.pump();
|
||||||
|
expect(appBarIsVisible(tester), true);
|
||||||
|
expect(tabBarHeight(tester), initialTabBarHeight);
|
||||||
|
expect(appBarHeight(tester), lessThan(initialAppBarHeight));
|
||||||
|
expect(appBarHeight(tester), initialTabBarHeight);
|
||||||
|
|
||||||
|
// Scroll the not-pinned appbar back into view
|
||||||
|
controller.jumpTo(0.0);
|
||||||
|
await tester.pump();
|
||||||
|
expect(appBarIsVisible(tester), true);
|
||||||
|
expect(appBarHeight(tester), initialAppBarHeight);
|
||||||
|
expect(tabBarHeight(tester), initialTabBarHeight);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user