Unverified Commit 492bfddd authored by chunhtai's avatar chunhtai Committed by GitHub

Fix selection area causes small scrollables to bounce (#112816)

parent d03f6314
...@@ -989,6 +989,11 @@ class EdgeDraggingAutoScroller { ...@@ -989,6 +989,11 @@ class EdgeDraggingAutoScroller {
scrollRenderBox.getTransformTo(null), scrollRenderBox.getTransformTo(null),
Rect.fromLTWH(0, 0, scrollRenderBox.size.width, scrollRenderBox.size.height) Rect.fromLTWH(0, 0, scrollRenderBox.size.width, scrollRenderBox.size.height)
); );
assert(
globalRect.size.width >= _dragTargetRelatedToScrollOrigin.size.width &&
globalRect.size.height >= _dragTargetRelatedToScrollOrigin.size.height,
'Drag target size is larger than scrollable size, which may cause bouncing',
);
_scrolling = true; _scrolling = true;
double? newOffset; double? newOffset;
const double overDragMax = 20.0; const double overDragMax = 20.0;
...@@ -1000,23 +1005,27 @@ class EdgeDraggingAutoScroller { ...@@ -1000,23 +1005,27 @@ class EdgeDraggingAutoScroller {
final double proxyStart = _offsetExtent(_dragTargetRelatedToScrollOrigin.topLeft, _scrollDirection); final double proxyStart = _offsetExtent(_dragTargetRelatedToScrollOrigin.topLeft, _scrollDirection);
final double proxyEnd = _offsetExtent(_dragTargetRelatedToScrollOrigin.bottomRight, _scrollDirection); final double proxyEnd = _offsetExtent(_dragTargetRelatedToScrollOrigin.bottomRight, _scrollDirection);
late double overDrag; switch (_axisDirection) {
if (_axisDirection == AxisDirection.up || _axisDirection == AxisDirection.left) { case AxisDirection.up:
if (proxyEnd > viewportEnd && scrollable.position.pixels > scrollable.position.minScrollExtent) { case AxisDirection.left:
overDrag = math.max(proxyEnd - viewportEnd, overDragMax); if (proxyEnd > viewportEnd && scrollable.position.pixels > scrollable.position.minScrollExtent) {
newOffset = math.max(scrollable.position.minScrollExtent, scrollable.position.pixels - overDrag); final double overDrag = math.min(proxyEnd - viewportEnd, overDragMax);
} else if (proxyStart < viewportStart && scrollable.position.pixels < scrollable.position.maxScrollExtent) { newOffset = math.max(scrollable.position.minScrollExtent, scrollable.position.pixels - overDrag);
overDrag = math.max(viewportStart - proxyStart, overDragMax); } else if (proxyStart < viewportStart && scrollable.position.pixels < scrollable.position.maxScrollExtent) {
newOffset = math.min(scrollable.position.maxScrollExtent, scrollable.position.pixels + overDrag); final double overDrag = math.min(viewportStart - proxyStart, overDragMax);
} newOffset = math.min(scrollable.position.maxScrollExtent, scrollable.position.pixels + overDrag);
} else { }
if (proxyStart < viewportStart && scrollable.position.pixels > scrollable.position.minScrollExtent) { break;
overDrag = math.max(viewportStart - proxyStart, overDragMax); case AxisDirection.right:
newOffset = math.max(scrollable.position.minScrollExtent, scrollable.position.pixels - overDrag); case AxisDirection.down:
} else if (proxyEnd > viewportEnd && scrollable.position.pixels < scrollable.position.maxScrollExtent) { if (proxyStart < viewportStart && scrollable.position.pixels > scrollable.position.minScrollExtent) {
overDrag = math.max(proxyEnd - viewportEnd, overDragMax); final double overDrag = math.min(viewportStart - proxyStart, overDragMax);
newOffset = math.min(scrollable.position.maxScrollExtent, scrollable.position.pixels + overDrag); newOffset = math.max(scrollable.position.minScrollExtent, scrollable.position.pixels - overDrag);
} } else if (proxyEnd > viewportEnd && scrollable.position.pixels < scrollable.position.maxScrollExtent) {
final double overDrag = math.min(proxyEnd - viewportEnd, overDragMax);
newOffset = math.min(scrollable.position.maxScrollExtent, scrollable.position.pixels + overDrag);
}
break;
} }
if (newOffset == null || (newOffset - scrollable.position.pixels).abs() < 1.0) { if (newOffset == null || (newOffset - scrollable.position.pixels).abs() < 1.0) {
...@@ -1055,7 +1064,10 @@ class _ScrollableSelectionContainerDelegate extends MultiSelectableSelectionCont ...@@ -1055,7 +1064,10 @@ class _ScrollableSelectionContainerDelegate extends MultiSelectableSelectionCont
_position.addListener(_scheduleLayoutChange); _position.addListener(_scheduleLayoutChange);
} }
static const double _kDefaultDragTargetSize = 200; // Pointer drag is a single point, it should not have a size.
static const double _kDefaultDragTargetSize = 0;
// An eye-balled value for a smooth scrolling speed.
static const double _kDefaultSelectToScrollVelocityScalar = 30; static const double _kDefaultSelectToScrollVelocityScalar = 30;
final ScrollableState state; final ScrollableState state;
......
...@@ -125,7 +125,8 @@ void main() { ...@@ -125,7 +125,8 @@ void main() {
expect(controller.offset, 0.0); expect(controller.offset, 0.0);
double previousOffset = controller.offset; double previousOffset = controller.offset;
await gesture.moveTo(tester.getBottomRight(find.byType(ListView))); // Scrollable only auto scroll if the drag passes the boundary.
await gesture.moveTo(tester.getBottomRight(find.byType(ListView)) + const Offset(0, 20));
await tester.pump(); await tester.pump();
await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1));
expect(controller.offset > previousOffset, isTrue); expect(controller.offset > previousOffset, isTrue);
...@@ -150,6 +151,50 @@ void main() { ...@@ -150,6 +151,50 @@ void main() {
await gesture.up(); await gesture.up();
}); });
testWidgets('select to scroll works for small scrollable', (WidgetTester tester) async {
final ScrollController controller = ScrollController();
await tester.pumpWidget(MaterialApp(
home: SelectionArea(
selectionControls: materialTextSelectionControls,
child: Scaffold(
body: SizedBox(
height: 10,
child: ListView.builder(
controller: controller,
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
return Text('Item $index');
},
),
),
),
),
));
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Item 0'), matching: find.byType(RichText)));
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph1, 2), kind: ui.PointerDeviceKind.mouse);
addTearDown(gesture.removePointer);
await tester.pump();
expect(controller.offset, 0.0);
double previousOffset = controller.offset;
// Scrollable only auto scroll if the drag passes the boundary
await gesture.moveTo(tester.getBottomRight(find.byType(ListView)) + const Offset(0, 20));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
expect(controller.offset > previousOffset, isTrue);
previousOffset = controller.offset;
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
expect(controller.offset > previousOffset, isTrue);
await gesture.up();
// Shouldn't be stuck if gesture is up.
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(tester.takeException(), isNull);
});
testWidgets('select to scroll backward', (WidgetTester tester) async { testWidgets('select to scroll backward', (WidgetTester tester) async {
final ScrollController controller = ScrollController(); final ScrollController controller = ScrollController();
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
...@@ -174,7 +219,7 @@ void main() { ...@@ -174,7 +219,7 @@ void main() {
expect(controller.offset, 4000); expect(controller.offset, 4000);
double previousOffset = controller.offset; double previousOffset = controller.offset;
await gesture.moveTo(tester.getTopLeft(find.byType(ListView))); await gesture.moveTo(tester.getTopLeft(find.byType(ListView)) + const Offset(0, -20));
await tester.pump(); await tester.pump();
await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1));
expect(controller.offset < previousOffset, isTrue); expect(controller.offset < previousOffset, isTrue);
...@@ -220,7 +265,8 @@ void main() { ...@@ -220,7 +265,8 @@ void main() {
expect(controller.offset, 0.0); expect(controller.offset, 0.0);
double previousOffset = controller.offset; double previousOffset = controller.offset;
await gesture.moveTo(tester.getBottomRight(find.byType(ListView))); // Scrollable only auto scroll if the drag passes the boundary
await gesture.moveTo(tester.getBottomRight(find.byType(ListView)) + const Offset(20, 0));
await tester.pump(); await tester.pump();
await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1));
expect(controller.offset > previousOffset, isTrue); expect(controller.offset > previousOffset, isTrue);
...@@ -268,7 +314,7 @@ void main() { ...@@ -268,7 +314,7 @@ void main() {
expect(controller.offset, 2080); expect(controller.offset, 2080);
double previousOffset = controller.offset; double previousOffset = controller.offset;
await gesture.moveTo(tester.getTopLeft(find.byType(ListView))); await gesture.moveTo(tester.getTopLeft(find.byType(ListView)) + const Offset(-10, 0));
await tester.pump(); await tester.pump();
await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1));
expect(controller.offset < previousOffset, isTrue); expect(controller.offset < previousOffset, isTrue);
...@@ -427,8 +473,8 @@ void main() { ...@@ -427,8 +473,8 @@ void main() {
expect(controller.offset, 0.0); expect(controller.offset, 0.0);
double previousOffset = controller.offset; double previousOffset = controller.offset;
// Scrollable only auto scroll if the drag passes the boundary
await gesture.moveTo(tester.getBottomRight(find.byType(ListView))); await gesture.moveTo(tester.getBottomRight(find.byType(ListView)) + const Offset(0, 40));
await tester.pump(); await tester.pump();
await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1));
expect(controller.offset > previousOffset, isTrue); expect(controller.offset > previousOffset, isTrue);
......
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