Skip to content

Commit b753476

Browse files
authored
Add a snapAnimationDuration param in DraggableScrollableSheet (#107396)
* Add a snapAnimationDuration param in DraggableScrollableSheet * snapAnimationDuration.inMilliseconds > 0 * Update draggable_scrollable_sheet.dart
1 parent d092601 commit b753476

File tree

2 files changed

+76
-48
lines changed

2 files changed

+76
-48
lines changed

packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ class DraggableScrollableSheet extends StatefulWidget {
289289
this.expand = true,
290290
this.snap = false,
291291
this.snapSizes,
292+
this.snapAnimationDuration,
292293
this.controller,
293294
required this.builder,
294295
}) : assert(initialChildSize != null),
@@ -298,6 +299,7 @@ class DraggableScrollableSheet extends StatefulWidget {
298299
assert(maxChildSize <= 1.0),
299300
assert(minChildSize <= initialChildSize),
300301
assert(initialChildSize <= maxChildSize),
302+
assert(snapAnimationDuration == null || snapAnimationDuration > Duration.zero),
301303
assert(expand != null),
302304
assert(builder != null);
303305

@@ -365,6 +367,12 @@ class DraggableScrollableSheet extends StatefulWidget {
365367
/// being built or since the last call to [DraggableScrollableActuator.reset].
366368
final List<double>? snapSizes;
367369

370+
/// Defines a duration for the snap animations.
371+
///
372+
/// If it's not set, then the animation duration is the distance to the snap
373+
/// target divided by the velocity of the widget.
374+
final Duration? snapAnimationDuration;
375+
368376
/// A controller that can be used to programmatically control this sheet.
369377
final DraggableScrollableController? controller;
370378

@@ -466,6 +474,7 @@ class _DraggableSheetExtent {
466474
required this.snapSizes,
467475
required this.initialSize,
468476
required this.onSizeChanged,
477+
this.snapAnimationDuration,
469478
ValueNotifier<double>? currentSize,
470479
bool? hasDragged,
471480
}) : assert(minSize != null),
@@ -486,6 +495,7 @@ class _DraggableSheetExtent {
486495
final double maxSize;
487496
final bool snap;
488497
final List<double> snapSizes;
498+
final Duration? snapAnimationDuration;
489499
final double initialSize;
490500
final ValueNotifier<double> _currentSize;
491501
final VoidCallback onSizeChanged;
@@ -570,12 +580,14 @@ class _DraggableSheetExtent {
570580
required List<double> snapSizes,
571581
required double initialSize,
572582
required VoidCallback onSizeChanged,
583+
Duration? snapAnimationDuration,
573584
}) {
574585
return _DraggableSheetExtent(
575586
minSize: minSize,
576587
maxSize: maxSize,
577588
snap: snap,
578589
snapSizes: snapSizes,
590+
snapAnimationDuration: snapAnimationDuration,
579591
initialSize: initialSize,
580592
onSizeChanged: onSizeChanged,
581593
// Use the possibly updated initialSize if the user hasn't dragged yet.
@@ -599,6 +611,7 @@ class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> {
599611
maxSize: widget.maxChildSize,
600612
snap: widget.snap,
601613
snapSizes: _impliedSnapSizes(),
614+
snapAnimationDuration: widget.snapAnimationDuration,
602615
initialSize: widget.initialChildSize,
603616
onSizeChanged: _setExtent,
604617
);
@@ -679,6 +692,7 @@ class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> {
679692
maxSize: widget.maxChildSize,
680693
snap: widget.snap,
681694
snapSizes: _impliedSnapSizes(),
695+
snapAnimationDuration: widget.snapAnimationDuration,
682696
initialSize: widget.initialChildSize,
683697
onSizeChanged: _setExtent,
684698
);
@@ -902,6 +916,7 @@ class _DraggableScrollableSheetScrollPosition extends ScrollPositionWithSingleCo
902916
position: extent.currentPixels,
903917
initialVelocity: velocity,
904918
pixelSnapSize: extent.pixelSnapSizes,
919+
snapAnimationDuration: extent.snapAnimationDuration,
905920
tolerance: physics.tolerance,
906921
);
907922
} else {
@@ -1065,12 +1080,17 @@ class _SnappingSimulation extends Simulation {
10651080
required this.position,
10661081
required double initialVelocity,
10671082
required List<double> pixelSnapSize,
1083+
Duration? snapAnimationDuration,
10681084
super.tolerance,
10691085
}) {
10701086
_pixelSnapSize = _getSnapSize(initialVelocity, pixelSnapSize);
1087+
1088+
if (snapAnimationDuration != null && snapAnimationDuration.inMilliseconds > 0) {
1089+
velocity = (_pixelSnapSize - position) * 1000 / snapAnimationDuration.inMilliseconds;
1090+
}
10711091
// Check the direction of the target instead of the sign of the velocity because
10721092
// we may snap in the opposite direction of velocity if velocity is very low.
1073-
if (_pixelSnapSize < position) {
1093+
else if (_pixelSnapSize < position) {
10741094
velocity = math.min(-minimumSpeed, initialVelocity);
10751095
} else {
10761096
velocity = math.max(minimumSpeed, initialVelocity);

packages/flutter/test/widgets/draggable_scrollable_sheet_test.dart

Lines changed: 55 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ void main() {
1515
double minChildSize = .25,
1616
bool snap = false,
1717
List<double>? snapSizes,
18+
Duration? snapAnimationDuration,
1819
double? itemExtent,
1920
Key? containerKey,
2021
Key? stackKey,
@@ -40,6 +41,7 @@ void main() {
4041
initialChildSize: initialChildSize,
4142
snap: snap,
4243
snapSizes: snapSizes,
44+
snapAnimationDuration: snapAnimationDuration,
4345
builder: (BuildContext context, ScrollController scrollController) {
4446
return NotificationListener<ScrollNotification>(
4547
onNotification: onScrollNotification,
@@ -485,58 +487,64 @@ void main() {
485487
});
486488
}
487489

488-
testWidgets('Zero velocity drag snaps to nearest snap target', (WidgetTester tester) async {
489-
const Key stackKey = ValueKey<String>('stack');
490-
const Key containerKey = ValueKey<String>('container');
491-
await tester.pumpWidget(boilerplateWidget(null,
492-
snap: true,
493-
stackKey: stackKey,
494-
containerKey: containerKey,
495-
snapSizes: <double>[.25, .5, .75, 1.0],
496-
));
497-
await tester.pumpAndSettle();
498-
final double screenHeight = tester.getSize(find.byKey(stackKey)).height;
490+
for (final Duration? snapAnimationDuration in <Duration?>[null, const Duration(seconds: 2)]) {
491+
testWidgets(
492+
'Zero velocity drag snaps to nearest snap target with '
493+
'snapAnimationDuration: $snapAnimationDuration',
494+
(WidgetTester tester) async {
495+
const Key stackKey = ValueKey<String>('stack');
496+
const Key containerKey = ValueKey<String>('container');
497+
await tester.pumpWidget(boilerplateWidget(null,
498+
snap: true,
499+
stackKey: stackKey,
500+
containerKey: containerKey,
501+
snapSizes: <double>[.25, .5, .75, 1.0],
502+
snapAnimationDuration: snapAnimationDuration
503+
));
504+
await tester.pumpAndSettle();
505+
final double screenHeight = tester.getSize(find.byKey(stackKey)).height;
499506

500-
// We are dragging up, but we'll snap down because we're closer to .75 than 1.
501-
await tester.drag(find.text('Item 1'), Offset(0, -.35 * screenHeight));
502-
await tester.pumpAndSettle();
503-
expect(
504-
tester.getSize(find.byKey(containerKey)).height / screenHeight,
505-
closeTo(.75, precisionErrorTolerance),
506-
);
507+
// We are dragging up, but we'll snap down because we're closer to .75 than 1.
508+
await tester.drag(find.text('Item 1'), Offset(0, -.35 * screenHeight));
509+
await tester.pumpAndSettle();
510+
expect(
511+
tester.getSize(find.byKey(containerKey)).height / screenHeight,
512+
closeTo(.75, precisionErrorTolerance),
513+
);
507514

508-
// Drag up and snap up.
509-
await tester.drag(find.text('Item 1'), Offset(0, -.2 * screenHeight));
510-
await tester.pumpAndSettle();
511-
expect(
512-
tester.getSize(find.byKey(containerKey)).height / screenHeight,
513-
closeTo(1.0, precisionErrorTolerance),
514-
);
515+
// Drag up and snap up.
516+
await tester.drag(find.text('Item 1'), Offset(0, -.2 * screenHeight));
517+
await tester.pumpAndSettle();
518+
expect(
519+
tester.getSize(find.byKey(containerKey)).height / screenHeight,
520+
closeTo(1.0, precisionErrorTolerance),
521+
);
515522

516-
// Drag down and snap up.
517-
await tester.drag(find.text('Item 1'), Offset(0, .1 * screenHeight));
518-
await tester.pumpAndSettle();
519-
expect(
520-
tester.getSize(find.byKey(containerKey)).height / screenHeight,
521-
closeTo(1.0, precisionErrorTolerance),
522-
);
523+
// Drag down and snap up.
524+
await tester.drag(find.text('Item 1'), Offset(0, .1 * screenHeight));
525+
await tester.pumpAndSettle();
526+
expect(
527+
tester.getSize(find.byKey(containerKey)).height / screenHeight,
528+
closeTo(1.0, precisionErrorTolerance),
529+
);
523530

524-
// Drag down and snap down.
525-
await tester.drag(find.text('Item 1'), Offset(0, .45 * screenHeight));
526-
await tester.pumpAndSettle();
527-
expect(
528-
tester.getSize(find.byKey(containerKey)).height / screenHeight,
529-
closeTo(.5, precisionErrorTolerance),
530-
);
531+
// Drag down and snap down.
532+
await tester.drag(find.text('Item 1'), Offset(0, .45 * screenHeight));
533+
await tester.pumpAndSettle();
534+
expect(
535+
tester.getSize(find.byKey(containerKey)).height / screenHeight,
536+
closeTo(.5, precisionErrorTolerance),
537+
);
531538

532-
// Fling up with negligible velocity and snap down.
533-
await tester.fling(find.text('Item 1'), Offset(0, .1 * screenHeight), 1);
534-
await tester.pumpAndSettle();
535-
expect(
536-
tester.getSize(find.byKey(containerKey)).height / screenHeight,
537-
closeTo(.5, precisionErrorTolerance),
538-
);
539-
}, variant: TargetPlatformVariant.all());
539+
// Fling up with negligible velocity and snap down.
540+
await tester.fling(find.text('Item 1'), Offset(0, .1 * screenHeight), 1);
541+
await tester.pumpAndSettle();
542+
expect(
543+
tester.getSize(find.byKey(containerKey)).height / screenHeight,
544+
closeTo(.5, precisionErrorTolerance),
545+
);
546+
}, variant: TargetPlatformVariant.all());
547+
}
540548

541549
for (final List<double>? snapSizes in <List<double>?>[null, <double>[]]) {
542550
testWidgets('Setting snapSizes to $snapSizes resolves to min and max', (WidgetTester tester) async {

0 commit comments

Comments
 (0)