Unverified Commit 452e6b17 authored by Per Classon's avatar Per Classon Committed by GitHub

Add collapsed height param to SliverAppBar (#58593)

parent 28020951
......@@ -783,7 +783,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
final double _bottomHeight;
@override
double get minExtent => collapsedHeight ?? (topPadding + kToolbarHeight + _bottomHeight);
double get minExtent => collapsedHeight;
@override
double get maxExtent => math.max(topPadding + (expandedHeight ?? kToolbarHeight + _bottomHeight), minExtent);
......@@ -797,20 +797,12 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
final double visibleMainHeight = maxExtent - shrinkOffset - topPadding;
final double extraToolbarHeight = math.max(minExtent - _bottomHeight - topPadding - kToolbarHeight, 0.0);
final double visibleToolbarHeight = visibleMainHeight - _bottomHeight - extraToolbarHeight;
// Truth table for `toolbarOpacity`:
// pinned | floating | bottom != null || opacity
// ----------------------------------------------
// 0 | 0 | 0 || fade
// 0 | 0 | 1 || fade
// 0 | 1 | 0 || fade
// 0 | 1 | 1 || fade
// 1 | 0 | 0 || 1.0
// 1 | 0 | 1 || 1.0
// 1 | 1 | 0 || 1.0
// 1 | 1 | 1 || fade
final double toolbarOpacity = !pinned || (floating && bottom != null)
? ((visibleMainHeight - _bottomHeight) / kToolbarHeight).clamp(0.0, 1.0) as double
final bool isPinnedWithOpacityFade = pinned && floating && bottom != null && extraToolbarHeight == 0.0;
final double toolbarOpacity = !pinned || isPinnedWithOpacityFade
? (visibleToolbarHeight / kToolbarHeight).clamp(0.0, 1.0) as double
: 1.0;
final Widget appBar = FlexibleSpaceBar.createSettings(
......@@ -979,6 +971,7 @@ class SliverAppBar extends StatefulWidget {
this.centerTitle,
this.excludeHeaderSemantics = false,
this.titleSpacing = NavigationToolbar.kMiddleSpacing,
this.collapsedHeight,
this.expandedHeight,
this.floating = false,
this.pinned = false,
......@@ -997,6 +990,7 @@ class SliverAppBar extends StatefulWidget {
assert(stretch != null),
assert(floating || !snap, 'The "snap" argument only makes sense for floating app bars.'),
assert(stretchTriggerOffset > 0.0),
assert(collapsedHeight == null || collapsedHeight > kToolbarHeight, 'The "collapsedHeight" argument has to be larger than [kToolbarHeight].'),
super(key: key);
/// A widget to display before the [title].
......@@ -1154,6 +1148,18 @@ class SliverAppBar extends StatefulWidget {
/// Defaults to [NavigationToolbar.kMiddleSpacing].
final double titleSpacing;
/// Defines the height of the app bar when it is collapsed.
///
/// By default, the collapsed height is [kToolbarHeight]. If [bottom] widget
/// is specified, then its [bottom.preferredSize.height] is added to the
/// height. If [primary] is true, then the [MediaQuery] top padding,
/// [MediaQueryData.padding.top], is added as well.
///
/// If [pinned] and [floating] are true, with [bottom] set, the default
/// collapsed height is only [bottom.preferredSize.height] with the
/// [MediaQuery] top padding.
final double collapsedHeight;
/// The size of the app bar when it is fully expanded.
///
/// By default, the total height of the toolbar and the bottom widget (if
......@@ -1310,9 +1316,11 @@ class _SliverAppBarState extends State<SliverAppBar> with TickerProviderStateMix
@override
Widget build(BuildContext context) {
assert(!widget.primary || debugCheckHasMediaQuery(context));
final double bottomHeight = widget.bottom?.preferredSize?.height ?? 0.0;
final double topPadding = widget.primary ? MediaQuery.of(context).padding.top : 0.0;
final double collapsedHeight = (widget.pinned && widget.floating && widget.bottom != null)
? widget.bottom.preferredSize.height + topPadding : null;
? (widget.collapsedHeight ?? 0.0) + bottomHeight + topPadding
: (widget.collapsedHeight ?? kToolbarHeight) + bottomHeight + topPadding;
return MediaQuery.removePadding(
context: context,
......
......@@ -10,7 +10,13 @@ import 'package:flutter_test/flutter_test.dart';
import '../widgets/semantics_tester.dart';
Widget buildSliverAppBarApp({ bool floating, bool pinned, double expandedHeight, bool snap = false }) {
Widget buildSliverAppBarApp({
bool floating,
bool pinned,
double collapsedHeight,
double expandedHeight,
bool snap = false,
}) {
return Localizations(
locale: const Locale('en', 'US'),
delegates: const <LocalizationsDelegate<dynamic>>[
......@@ -31,6 +37,7 @@ Widget buildSliverAppBarApp({ bool floating, bool pinned, double expandedHeight,
title: const Text('AppBar Title'),
floating: floating,
pinned: pinned,
collapsedHeight: collapsedHeight,
expandedHeight: expandedHeight,
snap: snap,
bottom: TabBar(
......@@ -844,6 +851,46 @@ void main() {
expect(appBarBottom(tester), kTextTabBarHeight);
});
testWidgets('SliverAppBar expandedHeight, collapsedHeight', (WidgetTester tester) async {
const double expandedAppBarHeight = 400.0;
const double collapsedAppBarHeight = 200.0;
await tester.pumpWidget(buildSliverAppBarApp(
floating: false,
pinned: false,
collapsedHeight: collapsedAppBarHeight,
expandedHeight: expandedAppBarHeight,
));
final ScrollController controller = primaryScrollController(tester);
expect(controller.offset, 0.0);
expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), expandedAppBarHeight);
final double initialTabBarHeight = tabBarHeight(tester);
// Scroll the not-pinned appbar partially out of view.
controller.jumpTo(50.0);
await tester.pump();
expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), expandedAppBarHeight - 50.0);
expect(tabBarHeight(tester), initialTabBarHeight);
// Scroll the not-pinned appbar out of view, to its collapsed height.
controller.jumpTo(600.0);
await tester.pump();
expect(find.byType(SliverAppBar), findsNothing);
expect(appBarHeight(tester), collapsedAppBarHeight + initialTabBarHeight);
expect(tabBarHeight(tester), initialTabBarHeight);
// Scroll the not-pinned appbar back into view.
controller.jumpTo(0.0);
await tester.pump();
expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), expandedAppBarHeight);
expect(tabBarHeight(tester), initialTabBarHeight);
});
testWidgets('AppBar dimensions, with and without bottom, primary', (WidgetTester tester) async {
const MediaQueryData topPadding100 = MediaQueryData(padding: EdgeInsets.only(top: 100.0));
......
......@@ -142,7 +142,7 @@ void main() {
expect(render.text.style.color.opacity, 1.0);
});
testWidgets('pinned && floating && bottom ==> fade opactiy', (WidgetTester tester) async {
testWidgets('pinned && floating && bottom && extraToolbarHeight == 0.0 ==> fade opactiy', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/25993.
final ScrollController controller = ScrollController();
......@@ -162,16 +162,64 @@ void main() {
await tester.pumpAndSettle();
expect(render.text.style.color.opacity, 0.0);
});
testWidgets('pinned && floating && bottom && extraToolbarHeight != 0.0 ==> 1.0 opacity', (WidgetTester tester) async {
final ScrollController controller = ScrollController();
await tester.pumpWidget(
_TestWidget(
pinned: true,
floating: true,
bottom: true,
collapsedHeight: 100.0,
controller: controller,
),
);
final RenderParagraph render = tester.renderObject(find.text('Hallo Welt!!1'));
expect(render.text.style.color.opacity, 1.0);
controller.jumpTo(200.0);
await tester.pumpAndSettle();
expect(render.text.style.color.opacity, 1.0);
});
testWidgets('!pinned && !floating && !bottom && extraToolbarHeight != 0.0 ==> fade opacity', (WidgetTester tester) async {
final ScrollController controller = ScrollController();
const double collapsedHeight = 100.0;
await tester.pumpWidget(
_TestWidget(
pinned: false,
floating: false,
bottom: false,
controller: controller,
collapsedHeight: collapsedHeight,
),
);
final RenderParagraph render = tester.renderObject(find.text('Hallo Welt!!1'));
expect(render.text.style.color.opacity, 1.0);
controller.jumpTo(collapsedHeight);
await tester.pumpAndSettle();
expect(render.text.style.color.opacity, 0.0);
});
}
class _TestWidget extends StatelessWidget {
const _TestWidget({this.pinned, this.floating, this.bottom, this.controller});
const _TestWidget({
this.pinned,
this.floating,
this.bottom,
this.controller,
this.collapsedHeight,
});
final bool pinned;
final bool floating;
final bool bottom;
final ScrollController controller;
final double collapsedHeight;
@override
Widget build(BuildContext context) {
......@@ -183,6 +231,7 @@ class _TestWidget extends StatelessWidget {
pinned: pinned,
floating: floating,
expandedHeight: 120.0,
collapsedHeight: collapsedHeight,
title: const Text('Hallo Welt!!1'),
bottom: !bottom ? null : PreferredSize(
preferredSize: const Size.fromHeight(35.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