Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Improve Scrollbar drag behavior #112434

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 26 additions & 14 deletions packages/flutter/lib/src/widgets/scrollbar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,17 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
return scrollableExtent * thumbOffsetLocal / thumbMovableExtent;
}

/// The thumb's corresponding scroll offset in the track.
double getThumbScrollOffset() {
final double scrollableExtent = _lastMetrics!.maxScrollExtent - _lastMetrics!.minScrollExtent;

final double fractionPast = (scrollableExtent > 0)
? clampDouble(_lastMetrics!.pixels / scrollableExtent, 0.0, 1.0)
: 0;

return fractionPast * (_traversableTrackExtent - _thumbExtent);
}

// Converts between a scroll position and the corresponding position in the
// thumb track.
double _getScrollToTrack(ScrollMetrics metrics, double thumbExtent) {
Expand Down Expand Up @@ -1446,15 +1457,15 @@ class RawScrollbar extends StatefulWidget {
/// Provides defaults gestures for dragging the scrollbar thumb and tapping on the
/// scrollbar track.
class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProviderStateMixin<T> {
Offset? _dragScrollbarAxisOffset;
Offset? _startDragScrollbarAxisOffset;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TahaTesser Thanks for the sync info, I tested it and it did fix the issue by accident. : )

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Piinks Hi, do you have a chance to take a look at this change? : )

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, apologies! I was out of town and this got buried in my inbox. I checked it out before I left and forgot to click submit - sorry! 😓

double? _startDragThumbOffset;
ScrollController? _currentController;
Timer? _fadeoutTimer;
late AnimationController _fadeoutAnimationController;
late Animation<double> _fadeoutOpacityAnimation;
final GlobalKey _scrollbarPainterKey = GlobalKey();
bool _hoverIsActive = false;


/// Used to paint the scrollbar.
///
/// Can be customized by subclasses to change scrollbar behavior by overriding
Expand Down Expand Up @@ -1688,30 +1699,30 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv

void _updateScrollPosition(Offset updatedOffset) {
assert(_currentController != null);
assert(_dragScrollbarAxisOffset != null);
assert(_startDragScrollbarAxisOffset != null);
assert(_startDragThumbOffset != null);

final ScrollPosition position = _currentController!.position;
late double primaryDelta;
switch (position.axisDirection) {
case AxisDirection.up:
primaryDelta = _dragScrollbarAxisOffset!.dy - updatedOffset.dy;
primaryDelta = _startDragScrollbarAxisOffset!.dy - updatedOffset.dy;
break;
case AxisDirection.right:
primaryDelta = updatedOffset.dx -_dragScrollbarAxisOffset!.dx;
primaryDelta = updatedOffset.dx -_startDragScrollbarAxisOffset!.dx;
break;
case AxisDirection.down:
primaryDelta = updatedOffset.dy -_dragScrollbarAxisOffset!.dy;
primaryDelta = updatedOffset.dy -_startDragScrollbarAxisOffset!.dy;
break;
case AxisDirection.left:
primaryDelta = _dragScrollbarAxisOffset!.dx - updatedOffset.dx;
primaryDelta = _startDragScrollbarAxisOffset!.dx - updatedOffset.dx;
break;
}

// Convert primaryDelta, the amount that the scrollbar moved since the last
// time _updateScrollPosition was called, into the coordinate space of the scroll
// time when drag started, into the coordinate space of the scroll
// position, and jump to that position.
final double scrollOffsetLocal = scrollbarPainter.getTrackToScroll(primaryDelta);
final double scrollOffsetGlobal = scrollOffsetLocal + position.pixels;
final double scrollOffsetGlobal = scrollbarPainter.getTrackToScroll(primaryDelta + _startDragThumbOffset!);
if (scrollOffsetGlobal != position.pixels) {
// Ensure we don't drag into overscroll if the physics do not allow it.
final double physicsAdjustment = position.physics.applyBoundaryConditions(position, scrollOffsetGlobal);
Expand Down Expand Up @@ -1784,7 +1795,8 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
}
_fadeoutTimer?.cancel();
_fadeoutAnimationController.forward();
_dragScrollbarAxisOffset = localPosition;
_startDragScrollbarAxisOffset = localPosition;
_startDragThumbOffset = scrollbarPainter.getThumbScrollOffset();
}

/// Handler called when a currently active long press gesture moves.
Expand All @@ -1803,7 +1815,6 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
return;
}
_updateScrollPosition(localPosition);
_dragScrollbarAxisOffset = localPosition;
}

/// Handler called when a long press has ended.
Expand All @@ -1816,7 +1827,8 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
return;
}
_maybeStartFadeoutTimer();
_dragScrollbarAxisOffset = null;
_startDragScrollbarAxisOffset = null;
_startDragThumbOffset = null;
_currentController = null;
}

Expand Down Expand Up @@ -1958,7 +1970,7 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
scrollbarPainter.update(metrics, metrics.axisDirection);
}
} else if (notification is ScrollEndNotification) {
if (_dragScrollbarAxisOffset == null) {
if (_startDragScrollbarAxisOffset == null) {
_maybeStartFadeoutTimer();
}
}
Expand Down
71 changes: 71 additions & 0 deletions packages/flutter/test/widgets/scrollbar_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2717,4 +2717,75 @@ void main() {

expect(scrollController.offset, 0.0);
});

testWidgets('The thumb should follow the pointer when the scroll metrics changed during dragging', (WidgetTester tester) async {
// Regressing test for https://github.com/flutter/flutter/issues/112072
final ScrollController scrollController = ScrollController();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: PrimaryScrollController(
controller: scrollController,
child: RawScrollbar(
isAlwaysShown: true,
controller: scrollController,
child: CustomScrollView(
controller: scrollController,
// cacheExtent: double.maxFinite,
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
final double height;
if (index < 10) {
height = 100;
} else {
height = 500;
}
return SizedBox(
height: height,
child: Text('$index'),
);
},
childCount: 100,
),
),
],
),
),
),
),
),
);
await tester.pumpAndSettle();
expect(scrollController.offset, 0.0);

// Drag the thumb down to scroll down.
const double scrollAmount = 100;
final TestGesture dragScrollbarGesture = await tester.startGesture(const Offset(797.0, 5.0));
await tester.pumpAndSettle();
await dragScrollbarGesture.moveBy(const Offset(0.0, scrollAmount));
await tester.pumpAndSettle();

await dragScrollbarGesture.moveBy(const Offset(0.0, scrollAmount));
await tester.pumpAndSettle();

await dragScrollbarGesture.up();
await tester.pumpAndSettle();

// The view has scrolled more than it would have by a swipe gesture of the
// same distance.
expect(scrollController.offset, greaterThan((100.0 * 10 + 500.0 * 90) / 3));
expect(
find.byType(RawScrollbar),
paints
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
..rect(
rect: const Rect.fromLTRB(794.0, 200.0, 800.0, 218.0),
color: const Color(0x66BCBCBC),
),
);
});
}