feat: add animation during auto scrolling (#1153)

This commit is contained in:
Lucas 2025-11-06 12:40:37 +08:00 • committed by GitHub
commit 5c4a6f904c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 37 additions and 9 deletions

View file

@ -20,6 +20,7 @@ class AutoScroller extends EdgeDraggingAutoScroller
super.velocityScalar = _kDefaultAutoScrollVelocityScalar, super.velocityScalar = _kDefaultAutoScrollVelocityScalar,
super.minimumAutoScrollDelta = _kDefaultMinAutoScrollDelta, super.minimumAutoScrollDelta = _kDefaultMinAutoScrollDelta,
super.maxAutoScrollDelta = _kDefaultMaxAutoScrollDelta, super.maxAutoScrollDelta = _kDefaultMaxAutoScrollDelta,
super.animationDuration,
}); });
static const double _kDefaultAutoScrollVelocityScalar = 7; static const double _kDefaultAutoScrollVelocityScalar = 7;
@ -28,6 +29,8 @@ class AutoScroller extends EdgeDraggingAutoScroller
Offset? lastOffset; Offset? lastOffset;
Duration? lastDuration; Duration? lastDuration;
double? lastEdgeOffset;
AxisDirection? lastDirection;
@override @override
void startAutoScroll( void startAutoScroll(
@ -38,9 +41,12 @@ class AutoScroller extends EdgeDraggingAutoScroller
}) { }) {
lastOffset = offset; lastOffset = offset;
lastDuration = duration; lastDuration = duration;
lastEdgeOffset = edgeOffset;
lastDirection = direction;
if (direction != null && direction == AxisDirection.up) { if (direction != null && direction == AxisDirection.up) {
return startAutoScrollIfNecessary( return startAutoScrollIfNecessary(
offset & Size(1, edgeOffset), offset & Size(1, edgeOffset),
duration: duration,
); );
} }
@ -52,6 +58,7 @@ class AutoScroller extends EdgeDraggingAutoScroller
startAutoScrollIfNecessary( startAutoScrollIfNecessary(
dragTarget, dragTarget,
duration: duration,
); );
} }
@ -59,6 +66,8 @@ class AutoScroller extends EdgeDraggingAutoScroller
void stopAutoScroll() { void stopAutoScroll() {
lastOffset = null; lastOffset = null;
lastDuration = null; lastDuration = null;
lastEdgeOffset = null;
lastDirection = null;
super.stopAutoScroll(); super.stopAutoScroll();
} }
@ -66,6 +75,8 @@ class AutoScroller extends EdgeDraggingAutoScroller
if (lastOffset != null) { if (lastOffset != null) {
startAutoScroll( startAutoScroll(
lastOffset!, lastOffset!,
edgeOffset: lastEdgeOffset ?? 200,
direction: lastDirection,
duration: lastDuration, duration: lastDuration,
); );
} }

View file

@ -101,6 +101,14 @@ class _ScrollServiceWidgetState extends State<ScrollServiceWidget>
AxisDirection? direction; AxisDirection? direction;
final dynamic dragMode = final dynamic dragMode =
editorState.selectionExtraInfo?['selection_drag_mode']; editorState.selectionExtraInfo?['selection_drag_mode'];
// For desktop: if auto-scroller is already scrolling (from drag-to-select),
// don't override it here. The desktop_selection_service handles drag scrolling.
if (PlatformExtension.isDesktopOrWeb &&
(editorState.autoScroller?.scrolling ?? false)) {
return;
}
switch (dragMode?.toString()) { switch (dragMode?.toString()) {
case 'MobileSelectionDragMode.leftSelectionHandle': case 'MobileSelectionDragMode.leftSelectionHandle':
targetRect = selectionRects.first; targetRect = selectionRects.first;

View file

@ -345,7 +345,8 @@ class _DesktopSelectionServiceWidgetState
editorState.service.scrollService?.startAutoScroll( editorState.service.scrollService?.startAutoScroll(
_lastPanOffset!, _lastPanOffset!,
edgeOffset: 80, edgeOffset: 200,
duration: const Duration(milliseconds: 2),
); );
} }

View file

@ -636,9 +636,10 @@ class EditorState {
late AutoScroller scroller; late AutoScroller scroller;
scroller = AutoScroller( scroller = AutoScroller(
scrollableState, scrollableState,
velocityScalar: isDesktopOrWeb ? 0.125 : 0.02, velocityScalar: isDesktopOrWeb ? 0.15 : 0.02,
minimumAutoScrollDelta: isDesktopOrWeb ? 0.07 : 0.004, minimumAutoScrollDelta: isDesktopOrWeb ? 0.07 : 0.004,
maxAutoScrollDelta: isDesktopOrWeb ? 2.75 : 0.053, maxAutoScrollDelta: isDesktopOrWeb ? 3.5 : 0.053,
animationDuration: Duration.zero,
onScrollViewScrolled: () { onScrollViewScrolled: () {
_notifyScrollViewScrolledListeners(); _notifyScrollViewScrolledListeners();
if (!isDesktopOrWeb) { if (!isDesktopOrWeb) {

View file

@ -150,10 +150,13 @@ class EdgeDraggingAutoScroller {
required this.velocityScalar, required this.velocityScalar,
double minimumAutoScrollDelta = 1.0, double minimumAutoScrollDelta = 1.0,
double maxAutoScrollDelta = 20.0, double maxAutoScrollDelta = 20.0,
Duration? animationDuration,
}) : assert(minimumAutoScrollDelta >= 0), }) : assert(minimumAutoScrollDelta >= 0),
assert(maxAutoScrollDelta >= minimumAutoScrollDelta), assert(maxAutoScrollDelta >= minimumAutoScrollDelta),
_minimumAutoScrollDelta = minimumAutoScrollDelta, _minimumAutoScrollDelta = minimumAutoScrollDelta,
_maxAutoScrollDelta = maxAutoScrollDelta; _maxAutoScrollDelta = maxAutoScrollDelta,
_animationDuration =
animationDuration ?? const Duration(milliseconds: 5);
/// The [Scrollable] this auto scroller is scrolling. /// The [Scrollable] this auto scroller is scrolling.
final ScrollableState scrollable; final ScrollableState scrollable;
@ -180,6 +183,8 @@ class EdgeDraggingAutoScroller {
/// moving rather than treat it as too small to scroll. /// moving rather than treat it as too small to scroll.
final double _minimumAutoScrollDelta; final double _minimumAutoScrollDelta;
final double _maxAutoScrollDelta; final double _maxAutoScrollDelta;
final Duration _animationDuration;
Duration? _currentDuration;
double? _previousScrollDelta; double? _previousScrollDelta;
late Rect _dragTargetRelatedToScrollOrigin; late Rect _dragTargetRelatedToScrollOrigin;
@ -212,10 +217,11 @@ class EdgeDraggingAutoScroller {
/// ///
/// If the scrollable is already scrolling, calling this method updates the /// If the scrollable is already scrolling, calling this method updates the
/// previous dragTarget to the new value and continues scrolling if necessary. /// previous dragTarget to the new value and continues scrolling if necessary.
void startAutoScrollIfNecessary(Rect dragTarget) { void startAutoScrollIfNecessary(Rect dragTarget, {Duration? duration}) {
final Offset deltaToOrigin = scrollable.deltaToScrollOrigin; final Offset deltaToOrigin = scrollable.deltaToScrollOrigin;
_dragTargetRelatedToScrollOrigin = _dragTargetRelatedToScrollOrigin =
dragTarget.translate(deltaToOrigin.dx, deltaToOrigin.dy); dragTarget.translate(deltaToOrigin.dx, deltaToOrigin.dy);
_currentDuration = duration;
if (_scrolling) { if (_scrolling) {
// The change will be picked up in the next scroll. // The change will be picked up in the next scroll.
return; return;
@ -228,9 +234,10 @@ class EdgeDraggingAutoScroller {
void stopAutoScroll() { void stopAutoScroll() {
_scrolling = false; _scrolling = false;
_previousScrollDelta = null; _previousScrollDelta = null;
_currentDuration = null;
} }
Future<void> _scroll({Duration? duration}) async { Future<void> _scroll() async {
try { try {
final RenderBox scrollRenderBox = final RenderBox scrollRenderBox =
scrollable.context.findRenderObject()! as RenderBox; scrollable.context.findRenderObject()! as RenderBox;
@ -354,13 +361,13 @@ class EdgeDraggingAutoScroller {
} }
await scrollable.position.moveTo( await scrollable.position.moveTo(
newOffset, newOffset,
duration: duration, duration: _currentDuration ?? _animationDuration,
curve: Curves.linear, curve: Curves.linear,
clamp: true, // clamp: true,
); );
onScrollViewScrolled?.call(); onScrollViewScrolled?.call();
if (_scrolling) { if (_scrolling) {
await _scroll(duration: duration); await _scroll();
} }
} catch (e) { } catch (e) {
debugPrint(e.toString()); debugPrint(e.toString());