diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index 75a377012f..621c506742 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -1436,8 +1436,6 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr /// within the [Scaffold]. The [FloatingActionButton] is connected to a /// callback that increments a counter. /// -/// ![The Scaffold has a white background with a blue AppBar at the top. A blue FloatingActionButton is positioned at the bottom right corner of the Scaffold.](https://flutter.github.io/assets-for-api-docs/assets/material/scaffold.png) -/// /// ** See code in examples/api/lib/material/scaffold/scaffold.0.dart ** /// {@end-tool} /// @@ -1485,6 +1483,19 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr /// widget can be used within the scaffold's body to avoid areas /// like display notches. /// +/// ## Floating action button with a draggable scrollable bottom sheet +/// +/// If [Scaffold.bottomSheet] is a [DraggableScrollableSheet], +/// [Scaffold.floatingActionButton] is set, and the bottom sheet is dragged to +/// cover greater than 70% of the Scaffold's height, two things happen in parallel: +/// +/// * Scaffold starts to show scrim (see [ScaffoldState.showBodyScrim]), and +/// * [Scaffold.floatingActionButton] is scaled down through an animation with a [Curves.easeIn], and +/// disappears when the bottom sheet covers the entire Scaffold. +/// +/// And as soon as the bottom sheet is dragged down to cover less than 70% of the [Scaffold], the scrim +/// disappears and [Scaffold.floatingActionButton] animates back to its normal size. +/// /// ## Troubleshooting /// /// ### Nested Scaffolds diff --git a/packages/flutter/test/material/scaffold_test.dart b/packages/flutter/test/material/scaffold_test.dart index 3f040b1f65..2b57d1893d 100644 --- a/packages/flutter/test/material/scaffold_test.dart +++ b/packages/flutter/test/material/scaffold_test.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:math' as math; + import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart' show DragStartBehavior; import 'package:flutter/material.dart'; @@ -266,6 +268,84 @@ void main() { expect(tester.binding.transientCallbackCount, greaterThan(0)); }); + testWidgets('Floating action button shrinks when bottom sheet becomes dominant', (WidgetTester tester) async { + final DraggableScrollableController draggableController = DraggableScrollableController(); + const double kBottomSheetDominatesPercentage = 0.3; + + await tester.pumpWidget(MaterialApp(home: Scaffold( + floatingActionButton: const FloatingActionButton( + key: Key('one'), + onPressed: null, + child: Text('1'), + ), + bottomSheet: DraggableScrollableSheet( + expand: false, + controller: draggableController, + builder: (BuildContext context, ScrollController scrollController) { + return SingleChildScrollView( + controller: scrollController, + child: const SizedBox(), + ); + }, + ), + ))); + + double getScale() => tester.firstWidget(find.byType(ScaleTransition)).scale.value; + + for (double i = 0, extent = i / 10; i <= 10; i++, extent = i / 10) { + draggableController.jumpTo(extent); + + final double extentRemaining = 1.0 - extent; + if (extentRemaining < kBottomSheetDominatesPercentage) { + final double visValue = extentRemaining * kBottomSheetDominatesPercentage * 10; + // since FAB uses easeIn curve, we're testing this by using the fact that + // easeIn curve is always less than or equal to x=y curve. + expect(getScale(), lessThanOrEqualTo(visValue)); + } else { + expect(getScale(), equals(1.0)); + } + } + }); + + testWidgets('Scaffold shows scrim when bottom sheet becomes dominant', (WidgetTester tester) async { + final DraggableScrollableController draggableController = DraggableScrollableController(); + const double kBottomSheetDominatesPercentage = 0.3; + const double kMinBottomSheetScrimOpacity = 0.1; + const double kMaxBottomSheetScrimOpacity = 0.6; + + await tester.pumpWidget(MaterialApp(home: Scaffold( + bottomSheet: DraggableScrollableSheet( + expand: false, + controller: draggableController, + builder: (BuildContext context, ScrollController scrollController) { + return SingleChildScrollView( + controller: scrollController, + child: const SizedBox(), + ); + }, + ), + ))); + + Finder findModalBarrier() => find.descendant(of: find.byType(Scaffold), matching: find.byType(ModalBarrier)); + double getOpacity() => tester.firstWidget(findModalBarrier()).color!.opacity; + double getExpectedOpacity(double visValue) => math.max(kMinBottomSheetScrimOpacity, kMaxBottomSheetScrimOpacity - visValue); + + for (double i = 0, extent = i / 10; i <= 10; i++, extent = i / 10) { + draggableController.jumpTo(extent); + await tester.pump(); + + final double extentRemaining = 1.0 - extent; + if (extentRemaining < kBottomSheetDominatesPercentage) { + final double visValue = extentRemaining * kBottomSheetDominatesPercentage * 10; + + expect(findModalBarrier(), findsOneWidget); + expect(getOpacity(), moreOrLessEquals(getExpectedOpacity(visValue), epsilon: 0.02)); + } else { + expect(findModalBarrier(), findsNothing); + } + } + }); + testWidgets('Floating action button directionality', (WidgetTester tester) async { Widget build(TextDirection textDirection) { return Directionality(