collection: snap to header/row when fast scrolling long content
This commit is contained in:
parent
5677dab9fb
commit
954853643a
2 changed files with 30 additions and 3 deletions
|
@ -45,6 +45,7 @@ import 'package:aves/widgets/common/thumbnail/notifications.dart';
|
||||||
import 'package:aves/widgets/common/tile_extent_controller.dart';
|
import 'package:aves/widgets/common/tile_extent_controller.dart';
|
||||||
import 'package:aves/widgets/navigation/nav_bar/nav_bar.dart';
|
import 'package:aves/widgets/navigation/nav_bar/nav_bar.dart';
|
||||||
import 'package:aves/widgets/viewer/entry_viewer_page.dart';
|
import 'package:aves/widgets/viewer/entry_viewer_page.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||||
|
@ -500,6 +501,8 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge
|
||||||
return Selector<SectionedListLayout<AvesEntry>, List<SectionLayout>>(
|
return Selector<SectionedListLayout<AvesEntry>, List<SectionLayout>>(
|
||||||
selector: (context, layout) => layout.sectionLayouts,
|
selector: (context, layout) => layout.sectionLayouts,
|
||||||
builder: (context, sectionLayouts, child) {
|
builder: (context, sectionLayouts, child) {
|
||||||
|
final scrollController = widget.scrollController;
|
||||||
|
final offsetIncrementSnapThreshold = context.select<TileExtentController, double>((v) => (v.extentNotifier.value + v.spacing) / 4);
|
||||||
return DraggableScrollbar(
|
return DraggableScrollbar(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
scrollThumbSize: Size(avesScrollThumbWidth, avesScrollThumbHeight),
|
scrollThumbSize: Size(avesScrollThumbWidth, avesScrollThumbHeight),
|
||||||
|
@ -507,7 +510,23 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge
|
||||||
height: avesScrollThumbHeight,
|
height: avesScrollThumbHeight,
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
),
|
),
|
||||||
controller: widget.scrollController,
|
controller: scrollController,
|
||||||
|
dragOffsetSnapper: (scrollOffset, offsetIncrement) {
|
||||||
|
if (offsetIncrement > offsetIncrementSnapThreshold && scrollOffset < scrollController.position.maxScrollExtent) {
|
||||||
|
final section = sectionLayouts.firstWhereOrNull((section) => section.hasChildAtOffset(scrollOffset));
|
||||||
|
if (section != null) {
|
||||||
|
if (section.maxOffset - section.minOffset < scrollController.position.viewportDimension) {
|
||||||
|
// snap to section header
|
||||||
|
return section.minOffset;
|
||||||
|
} else {
|
||||||
|
// snap to content row
|
||||||
|
final index = section.getMinChildIndexForScrollOffset(scrollOffset);
|
||||||
|
return section.indexToLayoutOffset(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scrollOffset;
|
||||||
|
},
|
||||||
crumbsBuilder: () => _getCrumbs(sectionLayouts),
|
crumbsBuilder: () => _getCrumbs(sectionLayouts),
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
// padding to keep scroll thumb between app bar above and nav bar below
|
// padding to keep scroll thumb between app bar above and nav bar below
|
||||||
|
|
|
@ -59,6 +59,8 @@ class DraggableScrollbar extends StatefulWidget {
|
||||||
/// The ScrollController for the BoxScrollView
|
/// The ScrollController for the BoxScrollView
|
||||||
final ScrollController controller;
|
final ScrollController controller;
|
||||||
|
|
||||||
|
final double Function(double scrollOffset, double offsetIncrement)? dragOffsetSnapper;
|
||||||
|
|
||||||
/// The view that will be scrolled with the scroll thumb
|
/// The view that will be scrolled with the scroll thumb
|
||||||
final ScrollView child;
|
final ScrollView child;
|
||||||
|
|
||||||
|
@ -68,6 +70,7 @@ class DraggableScrollbar extends StatefulWidget {
|
||||||
required this.scrollThumbSize,
|
required this.scrollThumbSize,
|
||||||
required this.scrollThumbBuilder,
|
required this.scrollThumbBuilder,
|
||||||
required this.controller,
|
required this.controller,
|
||||||
|
this.dragOffsetSnapper,
|
||||||
this.crumbsBuilder,
|
this.crumbsBuilder,
|
||||||
this.padding = EdgeInsets.zero,
|
this.padding = EdgeInsets.zero,
|
||||||
this.scrollbarAnimationDuration = const Duration(milliseconds: 300),
|
this.scrollbarAnimationDuration = const Duration(milliseconds: 300),
|
||||||
|
@ -114,6 +117,7 @@ class DraggableScrollbar extends StatefulWidget {
|
||||||
class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProviderStateMixin {
|
class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProviderStateMixin {
|
||||||
final ValueNotifier<double> _thumbOffsetNotifier = ValueNotifier(0), _viewOffsetNotifier = ValueNotifier(0);
|
final ValueNotifier<double> _thumbOffsetNotifier = ValueNotifier(0), _viewOffsetNotifier = ValueNotifier(0);
|
||||||
bool _isDragInProcess = false;
|
bool _isDragInProcess = false;
|
||||||
|
double _boundlessThumbOffset = 0, _offsetIncrement = 0;
|
||||||
late Offset _longPressLastGlobalPosition;
|
late Offset _longPressLastGlobalPosition;
|
||||||
|
|
||||||
late AnimationController _thumbAnimationController;
|
late AnimationController _thumbAnimationController;
|
||||||
|
@ -281,6 +285,8 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
|
||||||
|
|
||||||
void _onVerticalDragStart() {
|
void _onVerticalDragStart() {
|
||||||
const DraggableScrollbarNotification(DraggableScrollbarEvent.dragStart).dispatch(context);
|
const DraggableScrollbarNotification(DraggableScrollbarEvent.dragStart).dispatch(context);
|
||||||
|
_boundlessThumbOffset = _thumbOffsetNotifier.value;
|
||||||
|
_offsetIncrement = 1 / thumbMaxScrollExtent * controller.position.maxScrollExtent;
|
||||||
_labelAnimationController.forward();
|
_labelAnimationController.forward();
|
||||||
_fadeoutTimer?.cancel();
|
_fadeoutTimer?.cancel();
|
||||||
_showThumb();
|
_showThumb();
|
||||||
|
@ -292,12 +298,14 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
|
||||||
_showThumb();
|
_showThumb();
|
||||||
if (_isDragInProcess) {
|
if (_isDragInProcess) {
|
||||||
// thumb offset
|
// thumb offset
|
||||||
_thumbOffsetNotifier.value = (_thumbOffsetNotifier.value + deltaY).clamp(thumbMinScrollExtent, thumbMaxScrollExtent);
|
_boundlessThumbOffset += deltaY;
|
||||||
|
_thumbOffsetNotifier.value = _boundlessThumbOffset.clamp(thumbMinScrollExtent, thumbMaxScrollExtent);
|
||||||
|
|
||||||
// scroll offset
|
// scroll offset
|
||||||
final min = controller.position.minScrollExtent;
|
final min = controller.position.minScrollExtent;
|
||||||
final max = controller.position.maxScrollExtent;
|
final max = controller.position.maxScrollExtent;
|
||||||
controller.jumpTo((_thumbOffsetNotifier.value / thumbMaxScrollExtent * max).clamp(min, max));
|
final scrollOffset = _thumbOffsetNotifier.value / thumbMaxScrollExtent * max;
|
||||||
|
controller.jumpTo((widget.dragOffsetSnapper?.call(scrollOffset, _offsetIncrement) ?? scrollOffset).clamp(min, max));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue