Skip to content

Commit ff6aa92

Browse files
authored
Fix Scrollbar thumb drag behavior on desktop. (#111250)
1 parent f4c2ace commit ff6aa92

File tree

2 files changed

+161
-1
lines changed

2 files changed

+161
-1
lines changed

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1447,6 +1447,7 @@ class RawScrollbar extends StatefulWidget {
14471447
/// scrollbar track.
14481448
class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProviderStateMixin<T> {
14491449
Offset? _dragScrollbarAxisOffset;
1450+
late double? _thumbPress;
14501451
ScrollController? _currentController;
14511452
Timer? _fadeoutTimer;
14521453
late AnimationController _fadeoutAnimationController;
@@ -1785,6 +1786,9 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
17851786
_fadeoutTimer?.cancel();
17861787
_fadeoutAnimationController.forward();
17871788
_dragScrollbarAxisOffset = localPosition;
1789+
_thumbPress = direction == Axis.vertical
1790+
? localPosition.dy - scrollbarPainter._thumbOffset
1791+
: localPosition.dx - scrollbarPainter._thumbOffset;
17881792
}
17891793

17901794
/// Handler called when a currently active long press gesture moves.
@@ -1802,10 +1806,28 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
18021806
if (direction == null) {
18031807
return;
18041808
}
1805-
_updateScrollPosition(localPosition);
1809+
switch (position.axisDirection) {
1810+
case AxisDirection.up:
1811+
case AxisDirection.down:
1812+
if (_canDragThumb(_dragScrollbarAxisOffset!.dy, position.viewportDimension, _thumbPress!)) {
1813+
_updateScrollPosition(localPosition);
1814+
}
1815+
break;
1816+
case AxisDirection.left:
1817+
case AxisDirection.right:
1818+
if (_canDragThumb(_dragScrollbarAxisOffset!.dx, position.viewportDimension, _thumbPress!)) {
1819+
_updateScrollPosition(localPosition);
1820+
}
1821+
break;
1822+
}
18061823
_dragScrollbarAxisOffset = localPosition;
18071824
}
18081825

1826+
bool _canDragThumb(double dragOffset, double viewport, double thumbPress) {
1827+
return dragOffset >= thumbPress
1828+
&& dragOffset <= viewport - (scrollbarPainter._thumbExtent - thumbPress);
1829+
}
1830+
18091831
/// Handler called when a long press has ended.
18101832
@protected
18111833
@mustCallSuper

packages/flutter/test/widgets/scrollbar_test.dart

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2717,4 +2717,142 @@ void main() {
27172717

27182718
expect(scrollController.offset, 0.0);
27192719
});
2720+
2721+
testWidgets('Scrollbar thumb can only be dragged from long press point', (WidgetTester tester) async {
2722+
// Regression test for https://github.com/flutter/flutter/issues/107765
2723+
2724+
final ScrollController scrollController = ScrollController();
2725+
final UniqueKey uniqueKey = UniqueKey();
2726+
await tester.pumpWidget(
2727+
Directionality(
2728+
textDirection: TextDirection.ltr,
2729+
child: MediaQuery(
2730+
data: const MediaQueryData(),
2731+
child: ScrollConfiguration(
2732+
behavior: const ScrollBehavior().copyWith(
2733+
scrollbars: false,
2734+
),
2735+
child: PrimaryScrollController(
2736+
controller: scrollController,
2737+
child: RawScrollbar(
2738+
isAlwaysShown: true,
2739+
controller: scrollController,
2740+
child: CustomScrollView(
2741+
primary: true,
2742+
slivers: <Widget>[
2743+
SliverToBoxAdapter(
2744+
child: Container(
2745+
height: 600.0,
2746+
),
2747+
),
2748+
SliverToBoxAdapter(
2749+
key: uniqueKey,
2750+
child: Container(
2751+
height: 600.0,
2752+
),
2753+
),
2754+
SliverToBoxAdapter(
2755+
child: Container(
2756+
height: 600.0,
2757+
),
2758+
),
2759+
],
2760+
),
2761+
),
2762+
),
2763+
),
2764+
),
2765+
),
2766+
);
2767+
await tester.pumpAndSettle();
2768+
expect(scrollController.offset, 0.0);
2769+
expect(
2770+
find.byType(RawScrollbar),
2771+
paints
2772+
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
2773+
..rect(
2774+
rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 200.0),
2775+
color: const Color(0x66BCBCBC),
2776+
),
2777+
);
2778+
2779+
// Long press on the thumb in the center and drag down to the bottom.
2780+
const double scrollAmount = 400.0;
2781+
final TestGesture dragScrollbarGesture = await tester.startGesture(const Offset(797.0, 100.0));
2782+
await tester.pumpAndSettle();
2783+
await dragScrollbarGesture.moveBy(const Offset(0.0, scrollAmount));
2784+
await tester.pumpAndSettle();
2785+
2786+
// Drag down past the long press point.
2787+
await dragScrollbarGesture.moveBy(const Offset(0.0, 100));
2788+
await tester.pumpAndSettle();
2789+
2790+
// Drag up without reaching press point on the thumb.
2791+
await dragScrollbarGesture.moveBy(const Offset(0.0, -50));
2792+
await tester.pumpAndSettle();
2793+
2794+
// Thumb should not move yet.
2795+
expect(
2796+
find.byType(RawScrollbar),
2797+
paints
2798+
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
2799+
..rect(
2800+
rect: const Rect.fromLTRB(794.0, 400.0, 800.0, 600.0),
2801+
color: const Color(0x66BCBCBC),
2802+
),
2803+
);
2804+
2805+
// Drag up to reach press point on the thumb.
2806+
await dragScrollbarGesture.moveBy(const Offset(0.0, -50));
2807+
await tester.pumpAndSettle();
2808+
2809+
// Drag up.
2810+
await dragScrollbarGesture.moveBy(const Offset(0.0, -300));
2811+
await tester.pumpAndSettle();
2812+
2813+
// Thumb should be moved.
2814+
expect(
2815+
find.byType(RawScrollbar),
2816+
paints
2817+
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
2818+
..rect(
2819+
rect: const Rect.fromLTRB(794.0, 100.0, 800.0, 300.0),
2820+
color: const Color(0x66BCBCBC),
2821+
),
2822+
);
2823+
2824+
// Drag up to reach the top and exceed the long press point.
2825+
await dragScrollbarGesture.moveBy(const Offset(0.0, -200));
2826+
await tester.pumpAndSettle();
2827+
2828+
// Drag down to reach the long press point.
2829+
await dragScrollbarGesture.moveBy(const Offset(0.0, 100));
2830+
await tester.pumpAndSettle();
2831+
2832+
// Thumb should not move yet.
2833+
expect(
2834+
find.byType(RawScrollbar),
2835+
paints
2836+
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
2837+
..rect(
2838+
rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 200.0),
2839+
color: const Color(0x66BCBCBC),
2840+
),
2841+
);
2842+
2843+
// Drag down past the long press point.
2844+
await dragScrollbarGesture.moveBy(const Offset(0.0, 100));
2845+
await tester.pumpAndSettle();
2846+
2847+
// Thumb should be moved.
2848+
expect(
2849+
find.byType(RawScrollbar),
2850+
paints
2851+
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
2852+
..rect(
2853+
rect: const Rect.fromLTRB(794.0, 100.0, 800.0, 300.0),
2854+
color: const Color(0x66BCBCBC),
2855+
),
2856+
);
2857+
}, variant: TargetPlatformVariant.desktop());
27202858
}

0 commit comments

Comments
 (0)