Support keeping a bottom sheet with a DraggableScrollableSheet from closing on drag/fling to min extent (#127339)
This commit is contained in:
parent
758ea6c096
commit
a6d62ca8de
@ -315,7 +315,7 @@ class _BottomSheetState extends State<BottomSheet> {
|
||||
}
|
||||
|
||||
bool extentChanged(DraggableScrollableNotification notification) {
|
||||
if (notification.extent == notification.minExtent) {
|
||||
if (notification.extent == notification.minExtent && notification.shouldCloseOnMinExtent) {
|
||||
widget.onClosing();
|
||||
}
|
||||
return false;
|
||||
|
@ -3190,7 +3190,9 @@ class _StandardBottomSheetState extends State<_StandardBottomSheet> {
|
||||
scaffold.showBodyScrim(false, 0.0);
|
||||
}
|
||||
// If the Scaffold.bottomSheet != null, we're a persistent bottom sheet.
|
||||
if (notification.extent == notification.minExtent && scaffold.widget.bottomSheet == null) {
|
||||
if (notification.extent == notification.minExtent &&
|
||||
scaffold.widget.bottomSheet == null &&
|
||||
notification.shouldCloseOnMinExtent) {
|
||||
close();
|
||||
}
|
||||
return false;
|
||||
|
@ -308,6 +308,7 @@ class DraggableScrollableSheet extends StatefulWidget {
|
||||
this.snapSizes,
|
||||
this.snapAnimationDuration,
|
||||
this.controller,
|
||||
this.shouldCloseOnMinExtent = true,
|
||||
required this.builder,
|
||||
}) : assert(minChildSize >= 0.0),
|
||||
assert(maxChildSize <= 1.0),
|
||||
@ -391,6 +392,13 @@ class DraggableScrollableSheet extends StatefulWidget {
|
||||
/// A controller that can be used to programmatically control this sheet.
|
||||
final DraggableScrollableController? controller;
|
||||
|
||||
/// Whether the sheet, when dragged (or flung) to its minimum size, should
|
||||
/// cause its parent sheet to close.
|
||||
///
|
||||
/// Set on emitted [DraggableScrollableNotification]s. It is up to parent
|
||||
/// classes to properly read and handle this value.
|
||||
final bool shouldCloseOnMinExtent;
|
||||
|
||||
/// The builder that creates a child to display in this widget, which will
|
||||
/// use the provided [ScrollController] to enable dragging and scrolling
|
||||
/// of the contents.
|
||||
@ -432,6 +440,7 @@ class DraggableScrollableNotification extends Notification with ViewportNotifica
|
||||
required this.maxExtent,
|
||||
required this.initialExtent,
|
||||
required this.context,
|
||||
this.shouldCloseOnMinExtent = true,
|
||||
}) : assert(0.0 <= minExtent),
|
||||
assert(maxExtent <= 1.0),
|
||||
assert(minExtent <= extent),
|
||||
@ -458,6 +467,12 @@ class DraggableScrollableNotification extends Notification with ViewportNotifica
|
||||
/// is live when it first gets the notification.
|
||||
final BuildContext context;
|
||||
|
||||
/// Whether the widget that fired this notification, when dragged (or flung)
|
||||
/// to minExtent, should cause its parent sheet to close.
|
||||
///
|
||||
/// It is up to parent classes to properly read and handle this value.
|
||||
final bool shouldCloseOnMinExtent;
|
||||
|
||||
@override
|
||||
void debugFillDescription(List<String> description) {
|
||||
super.debugFillDescription(description);
|
||||
@ -487,6 +502,7 @@ class _DraggableSheetExtent {
|
||||
ValueNotifier<double>? currentSize,
|
||||
bool? hasDragged,
|
||||
bool? hasChanged,
|
||||
this.shouldCloseOnMinExtent = true,
|
||||
}) : assert(minSize >= 0),
|
||||
assert(maxSize <= 1),
|
||||
assert(minSize <= initialSize),
|
||||
@ -504,6 +520,7 @@ class _DraggableSheetExtent {
|
||||
final List<double> snapSizes;
|
||||
final Duration? snapAnimationDuration;
|
||||
final double initialSize;
|
||||
final bool shouldCloseOnMinExtent;
|
||||
final ValueNotifier<double> _currentSize;
|
||||
double availablePixels;
|
||||
|
||||
@ -576,6 +593,7 @@ class _DraggableSheetExtent {
|
||||
extent: currentSize,
|
||||
initialExtent: initialSize,
|
||||
context: context,
|
||||
shouldCloseOnMinExtent: shouldCloseOnMinExtent,
|
||||
).dispatch(context);
|
||||
}
|
||||
|
||||
@ -598,6 +616,7 @@ class _DraggableSheetExtent {
|
||||
required List<double> snapSizes,
|
||||
required double initialSize,
|
||||
Duration? snapAnimationDuration,
|
||||
bool shouldCloseOnMinExtent = true,
|
||||
}) {
|
||||
return _DraggableSheetExtent(
|
||||
minSize: minSize,
|
||||
@ -613,6 +632,7 @@ class _DraggableSheetExtent {
|
||||
: initialSize),
|
||||
hasDragged: hasDragged,
|
||||
hasChanged: hasChanged,
|
||||
shouldCloseOnMinExtent: shouldCloseOnMinExtent,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -631,6 +651,7 @@ class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> {
|
||||
snapSizes: _impliedSnapSizes(),
|
||||
snapAnimationDuration: widget.snapAnimationDuration,
|
||||
initialSize: widget.initialChildSize,
|
||||
shouldCloseOnMinExtent: widget.shouldCloseOnMinExtent,
|
||||
);
|
||||
_scrollController = _DraggableScrollableSheetScrollController(extent: _extent);
|
||||
widget.controller?._attach(_scrollController);
|
||||
|
@ -185,6 +185,44 @@ void main() {
|
||||
expect(find.text('Two'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Verify DraggableScrollableSheet.shouldCloseOnMinExtent == false prevents dismissal', (WidgetTester tester) async {
|
||||
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: Scaffold(
|
||||
key: scaffoldKey,
|
||||
body: const Center(child: Text('body')),
|
||||
),
|
||||
));
|
||||
|
||||
scaffoldKey.currentState!.showBottomSheet<void>((BuildContext context) {
|
||||
return DraggableScrollableSheet(
|
||||
expand: false,
|
||||
shouldCloseOnMinExtent: false,
|
||||
builder: (_, ScrollController controller) {
|
||||
return ListView(
|
||||
controller: controller,
|
||||
shrinkWrap: true,
|
||||
children: const <Widget>[
|
||||
SizedBox(height: 100.0, child: Text('One')),
|
||||
SizedBox(height: 100.0, child: Text('Two')),
|
||||
SizedBox(height: 100.0, child: Text('Three')),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Two'), findsOneWidget);
|
||||
|
||||
await tester.drag(find.text('Two'), const Offset(0.0, 400.0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Two'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Verify that a BottomSheet animates non-linearly', (WidgetTester tester) async {
|
||||
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
|
@ -20,7 +20,9 @@ void main() {
|
||||
Key? containerKey,
|
||||
Key? stackKey,
|
||||
NotificationListenerCallback<ScrollNotification>? onScrollNotification,
|
||||
NotificationListenerCallback<DraggableScrollableNotification>? onDraggableScrollableNotification,
|
||||
bool ignoreController = false,
|
||||
bool shouldCloseOnMinExtent = true,
|
||||
}) {
|
||||
return Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
@ -42,9 +44,12 @@ void main() {
|
||||
snap: snap,
|
||||
snapSizes: snapSizes,
|
||||
snapAnimationDuration: snapAnimationDuration,
|
||||
shouldCloseOnMinExtent: shouldCloseOnMinExtent,
|
||||
builder: (BuildContext context, ScrollController scrollController) {
|
||||
return NotificationListener<ScrollNotification>(
|
||||
onNotification: onScrollNotification,
|
||||
child: NotificationListener<DraggableScrollableNotification>(
|
||||
onNotification: onDraggableScrollableNotification,
|
||||
child:ColoredBox(
|
||||
key: containerKey,
|
||||
color: const Color(0xFFABCDEF),
|
||||
@ -55,6 +60,7 @@ void main() {
|
||||
itemBuilder: (BuildContext context, int index) => Text('Item $index'),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -864,6 +870,22 @@ void main() {
|
||||
expect(notificationTypes, types);
|
||||
});
|
||||
|
||||
testWidgets('Emits DraggableScrollableNotification with shouldCloseOnMinExtent set to non-default value', (WidgetTester tester) async {
|
||||
DraggableScrollableNotification? receivedNotification;
|
||||
await tester.pumpWidget(boilerplateWidget(
|
||||
null,
|
||||
shouldCloseOnMinExtent: false,
|
||||
onDraggableScrollableNotification: (DraggableScrollableNotification notification) {
|
||||
receivedNotification = notification;
|
||||
return false;
|
||||
},
|
||||
));
|
||||
|
||||
await tester.flingFrom(const Offset(0, 325), const Offset(0, -325), 200);
|
||||
await tester.pumpAndSettle();
|
||||
expect(receivedNotification!.shouldCloseOnMinExtent, isFalse);
|
||||
});
|
||||
|
||||
testWidgets('Do not crash when remove the tree during animation.', (WidgetTester tester) async {
|
||||
// Regression test for https://github.com/flutter/flutter/issues/89214
|
||||
await tester.pumpWidget(boilerplateWidget(
|
||||
|
Loading…
x
Reference in New Issue
Block a user