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