From 4bc35fc87a710e6c3de8a4f061dbecaa4df45014 Mon Sep 17 00:00:00 2001 From: Dan Field Date: Tue, 30 Apr 2019 15:42:42 -0700 Subject: [PATCH] Allow DSS to be dragged when its children do not fill extent (#31832) * Allow DSS to be dragged when its children do not fill extent * Fix when maxChildSize < 1.0 --- .../widgets/draggable_scrollable_sheet.dart | 16 ++++- .../material/modal_bottom_sheet_test.dart | 1 + .../draggable_scrollable_sheet_test.dart | 67 +++++++++++++++++-- 3 files changed, 79 insertions(+), 5 deletions(-) diff --git a/packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart b/packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart index 66559da077..78395a5f9a 100644 --- a/packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart +++ b/packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart @@ -262,13 +262,16 @@ class _DraggableSheetExtent { } double get currentExtent => _currentExtent.value; + double get additionalMinExtent => isAtMin ? 0.0 : 1.0; + double get additionalMaxExtent => isAtMax ? 0.0 : 1.0; + /// The scroll position gets inputs in terms of pixels, but the extent is /// expected to be expressed as a number between 0..1. void addPixelDelta(double delta, BuildContext context) { if (availablePixels == 0) { return; } - currentExtent += delta / availablePixels; + currentExtent += delta / availablePixels * maxExtent; DraggableScrollableNotification( minExtent: minExtent, maxExtent: maxExtent, @@ -426,6 +429,17 @@ class _DraggableScrollableSheetScrollPosition final _DraggableSheetExtent extent; bool get listShouldScroll => pixels > 0.0; + @override + bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) { + // We need to provide some extra extent if we haven't yet reached the max or + // min extents. Otherwise, a list with fewer children than the extent of + // the available space will get stuck. + return super.applyContentDimensions( + minScrollExtent - extent.additionalMinExtent, + maxScrollExtent + extent.additionalMaxExtent, + ); + } + @override void applyUserOffset(double delta) { if (!listShouldScroll && diff --git a/packages/flutter/test/material/modal_bottom_sheet_test.dart b/packages/flutter/test/material/modal_bottom_sheet_test.dart index 3405302e66..1562cdfa76 100644 --- a/packages/flutter/test/material/modal_bottom_sheet_test.dart +++ b/packages/flutter/test/material/modal_bottom_sheet_test.dart @@ -297,6 +297,7 @@ void main() { children: [ TestSemantics( flags: [SemanticsFlag.hasImplicitScrolling], + actions: [SemanticsAction.scrollDown, SemanticsAction.scrollUp], children: [ TestSemantics( label: 'BottomSheet', diff --git a/packages/flutter/test/widgets/draggable_scrollable_sheet_test.dart b/packages/flutter/test/widgets/draggable_scrollable_sheet_test.dart index c10b2099b0..b8924ee04a 100644 --- a/packages/flutter/test/widgets/draggable_scrollable_sheet_test.dart +++ b/packages/flutter/test/widgets/draggable_scrollable_sheet_test.dart @@ -8,7 +8,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { - Widget _boilerplate(VoidCallback onButtonPressed) { + Widget _boilerplate(VoidCallback onButtonPressed, { + int itemCount = 100, + double initialChildSize = .5, + double maxChildSize = 1.0, + double minChildSize = .25, + double itemExtent, + Key containerKey, + }) { return Directionality( textDirection: TextDirection.ltr, child: Stack( @@ -18,14 +25,17 @@ void main() { onPressed: onButtonPressed, ), DraggableScrollableSheet( - maxChildSize: 1.0, - minChildSize: .25, + maxChildSize: maxChildSize, + minChildSize: minChildSize, + initialChildSize: initialChildSize, builder: (BuildContext context, ScrollController scrollController) { return Container( + key: containerKey, color: const Color(0xFFABCDEF), child: ListView.builder( controller: scrollController, - itemCount: 100, + itemExtent: itemExtent, + itemCount: itemCount, itemBuilder: (BuildContext context, int index) => Text('Item $index'), ), ); @@ -36,6 +46,38 @@ void main() { ); } + testWidgets('Scrolls correct amount when maxChildSize < 1.0', (WidgetTester tester) async { + const Key key = ValueKey('container'); + await tester.pumpWidget(_boilerplate( + null, + maxChildSize: .6, + initialChildSize: .25, + itemExtent: 25.0, + containerKey: key, + )); + + expect(tester.getRect(find.byKey(key)), const Rect.fromLTRB(0.0, 450.0, 800.0, 600.0)); + await tester.drag(find.text('Item 5'), const Offset(0, -125)); + await tester.pumpAndSettle(); + expect(tester.getRect(find.byKey(key)), const Rect.fromLTRB(0.0, 325.0, 800.0, 600.0)); + }); + + testWidgets('Scrolls correct amount when maxChildSize == 1.0', (WidgetTester tester) async { + const Key key = ValueKey('container'); + await tester.pumpWidget(_boilerplate( + null, + maxChildSize: 1.0, + initialChildSize: .25, + itemExtent: 25.0, + containerKey: key, + )); + + expect(tester.getRect(find.byKey(key)), const Rect.fromLTRB(0.0, 450.0, 800.0, 600.0)); + await tester.drag(find.text('Item 5'), const Offset(0, -125)); + await tester.pumpAndSettle(); + expect(tester.getRect(find.byKey(key)), const Rect.fromLTRB(0.0, 325.0, 800.0, 600.0)); + }); + for (TargetPlatform platform in TargetPlatform.values) { group('$platform Scroll Physics', () { debugDefaultTargetPlatformOverride = platform; @@ -74,6 +116,23 @@ void main() { expect(find.text('Item 36'), findsNothing); }); + testWidgets('Can be dragged down when list is shorter than full height', (WidgetTester tester) async { + await tester.pumpWidget(_boilerplate(null, itemCount: 30, initialChildSize: .25)); + + expect(find.text('Item 1').hitTestable(), findsOneWidget); + expect(find.text('Item 29').hitTestable(), findsNothing); + + await tester.drag(find.text('Item 1'), const Offset(0, -325)); + await tester.pumpAndSettle(); + expect(find.text('Item 1').hitTestable(), findsOneWidget); + expect(find.text('Item 29').hitTestable(), findsOneWidget); + + await tester.drag(find.text('Item 1'), const Offset(0, 325)); + await tester.pumpAndSettle(); + expect(find.text('Item 1').hitTestable(), findsOneWidget); + expect(find.text('Item 29').hitTestable(), findsNothing); + }); + testWidgets('Can be dragged up and cover its container and scroll in single motion, and then dragged back down', (WidgetTester tester) async { int taps = 0; await tester.pumpWidget(_boilerplate(() => taps++));