mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Fix selection area causes small scrollables to bounce (#112816)
This commit is contained in:
parent
d03f631423
commit
492bfddd9f
@ -989,6 +989,11 @@ class EdgeDraggingAutoScroller {
|
||||
scrollRenderBox.getTransformTo(null),
|
||||
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;
|
||||
double? newOffset;
|
||||
const double overDragMax = 20.0;
|
||||
@ -1000,23 +1005,27 @@ class EdgeDraggingAutoScroller {
|
||||
|
||||
final double proxyStart = _offsetExtent(_dragTargetRelatedToScrollOrigin.topLeft, _scrollDirection);
|
||||
final double proxyEnd = _offsetExtent(_dragTargetRelatedToScrollOrigin.bottomRight, _scrollDirection);
|
||||
late double overDrag;
|
||||
if (_axisDirection == AxisDirection.up || _axisDirection == AxisDirection.left) {
|
||||
if (proxyEnd > viewportEnd && scrollable.position.pixels > scrollable.position.minScrollExtent) {
|
||||
overDrag = math.max(proxyEnd - viewportEnd, overDragMax);
|
||||
newOffset = math.max(scrollable.position.minScrollExtent, scrollable.position.pixels - overDrag);
|
||||
} else if (proxyStart < viewportStart && scrollable.position.pixels < scrollable.position.maxScrollExtent) {
|
||||
overDrag = math.max(viewportStart - proxyStart, overDragMax);
|
||||
newOffset = math.min(scrollable.position.maxScrollExtent, scrollable.position.pixels + overDrag);
|
||||
}
|
||||
} else {
|
||||
if (proxyStart < viewportStart && scrollable.position.pixels > scrollable.position.minScrollExtent) {
|
||||
overDrag = math.max(viewportStart - proxyStart, overDragMax);
|
||||
newOffset = math.max(scrollable.position.minScrollExtent, scrollable.position.pixels - overDrag);
|
||||
} else if (proxyEnd > viewportEnd && scrollable.position.pixels < scrollable.position.maxScrollExtent) {
|
||||
overDrag = math.max(proxyEnd - viewportEnd, overDragMax);
|
||||
newOffset = math.min(scrollable.position.maxScrollExtent, scrollable.position.pixels + overDrag);
|
||||
}
|
||||
switch (_axisDirection) {
|
||||
case AxisDirection.up:
|
||||
case AxisDirection.left:
|
||||
if (proxyEnd > viewportEnd && scrollable.position.pixels > scrollable.position.minScrollExtent) {
|
||||
final double overDrag = math.min(proxyEnd - viewportEnd, overDragMax);
|
||||
newOffset = math.max(scrollable.position.minScrollExtent, scrollable.position.pixels - overDrag);
|
||||
} else if (proxyStart < viewportStart && scrollable.position.pixels < scrollable.position.maxScrollExtent) {
|
||||
final double overDrag = math.min(viewportStart - proxyStart, overDragMax);
|
||||
newOffset = math.min(scrollable.position.maxScrollExtent, scrollable.position.pixels + overDrag);
|
||||
}
|
||||
break;
|
||||
case AxisDirection.right:
|
||||
case AxisDirection.down:
|
||||
if (proxyStart < viewportStart && scrollable.position.pixels > scrollable.position.minScrollExtent) {
|
||||
final double overDrag = math.min(viewportStart - proxyStart, overDragMax);
|
||||
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) {
|
||||
@ -1055,7 +1064,10 @@ class _ScrollableSelectionContainerDelegate extends MultiSelectableSelectionCont
|
||||
_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;
|
||||
|
||||
final ScrollableState state;
|
||||
|
||||
@ -125,7 +125,8 @@ void main() {
|
||||
expect(controller.offset, 0.0);
|
||||
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(const Duration(seconds: 1));
|
||||
expect(controller.offset > previousOffset, isTrue);
|
||||
@ -150,6 +151,50 @@ void main() {
|
||||
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 {
|
||||
final ScrollController controller = ScrollController();
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
@ -174,7 +219,7 @@ void main() {
|
||||
expect(controller.offset, 4000);
|
||||
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(const Duration(seconds: 1));
|
||||
expect(controller.offset < previousOffset, isTrue);
|
||||
@ -220,7 +265,8 @@ void main() {
|
||||
expect(controller.offset, 0.0);
|
||||
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(const Duration(seconds: 1));
|
||||
expect(controller.offset > previousOffset, isTrue);
|
||||
@ -268,7 +314,7 @@ void main() {
|
||||
expect(controller.offset, 2080);
|
||||
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(const Duration(seconds: 1));
|
||||
expect(controller.offset < previousOffset, isTrue);
|
||||
@ -427,8 +473,8 @@ void main() {
|
||||
|
||||
expect(controller.offset, 0.0);
|
||||
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, 40));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
expect(controller.offset > previousOffset, isTrue);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user