Improve scroll bar of favorites page.

This commit is contained in:
2025-03-09 12:55:36 +08:00
parent dbc2c27db0
commit 00c0a64de0
2 changed files with 172 additions and 6 deletions

View File

@@ -99,11 +99,13 @@ class _SmoothScrollProviderState extends State<SmoothScrollProvider> {
); );
if (_futurePosition == old) return; if (_futurePosition == old) return;
var target = _futurePosition!; var target = _futurePosition!;
_controller.animateTo( _controller
.animateTo(
_futurePosition!, _futurePosition!,
duration: _fastAnimationDuration, duration: _fastAnimationDuration,
curve: Curves.linear, curve: Curves.linear,
).then((_) { )
.then((_) {
var current = _controller.position.pixels; var current = _controller.position.pixels;
if (current == target && current == _futurePosition) { if (current == target && current == _futurePosition) {
_futurePosition = null; _futurePosition = null;
@@ -144,3 +146,169 @@ class ScrollControllerProvider extends InheritedWidget {
return oldWidget.controller != controller; return oldWidget.controller != controller;
} }
} }
class AppScrollBar extends StatefulWidget {
const AppScrollBar({
super.key,
required this.controller,
required this.child,
this.topPadding = 0,
});
final ScrollController controller;
final Widget child;
final double topPadding;
@override
State<AppScrollBar> createState() => _AppScrollBarState();
}
class _AppScrollBarState extends State<AppScrollBar> {
late final ScrollController _scrollController;
double minExtent = 0;
double maxExtent = 0;
double position = 0;
double viewHeight = 0;
final _scrollIndicatorSize = App.isDesktop ? 42.0 : 48.0;
late final VerticalDragGestureRecognizer _dragGestureRecognizer;
@override
void initState() {
super.initState();
_scrollController = widget.controller;
_scrollController.addListener(onChanged);
Future.microtask(onChanged);
_dragGestureRecognizer = VerticalDragGestureRecognizer()
..onUpdate = onUpdate;
}
void onUpdate(DragUpdateDetails details) {
if (maxExtent - minExtent <= 0 ||
viewHeight == 0 ||
details.primaryDelta == null) {
return;
}
var offset = details.primaryDelta!;
var positionOffset =
offset / (viewHeight - _scrollIndicatorSize) * (maxExtent - minExtent);
_scrollController.jumpTo((position + positionOffset).clamp(
minExtent,
maxExtent,
));
}
void onChanged() {
if (_scrollController.positions.isEmpty) return;
var position = _scrollController.position;
if (position.minScrollExtent != minExtent ||
position.maxScrollExtent != maxExtent ||
position.pixels != this.position) {
setState(() {
minExtent = position.minScrollExtent;
maxExtent = position.maxScrollExtent;
this.position = position.pixels;
});
}
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constrains) {
var scrollHeight = (maxExtent - minExtent);
var height = constrains.maxHeight - widget.topPadding;
viewHeight = height;
var top = scrollHeight == 0
? 0.0
: (position - minExtent) /
scrollHeight *
(height - _scrollIndicatorSize);
return Stack(
children: [
Positioned.fill(
child: widget.child,
),
Positioned(
top: top + widget.topPadding,
right: 0,
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: Listener(
behavior: HitTestBehavior.translucent,
onPointerDown: (event) {
_dragGestureRecognizer.addPointer(event);
},
child: SizedBox(
width: _scrollIndicatorSize/2,
height: _scrollIndicatorSize,
child: CustomPaint(
painter: _ScrollIndicatorPainter(
backgroundColor: context.colorScheme.surface,
shadowColor: context.colorScheme.shadow,
),
child: Column(
children: [
const Spacer(),
Icon(Icons.arrow_drop_up, size: 18),
Icon(Icons.arrow_drop_down, size: 18),
const Spacer(),
],
).paddingLeft(4),
),
),
),
),
),
],
);
},
);
}
}
class _ScrollIndicatorPainter extends CustomPainter {
final Color backgroundColor;
final Color shadowColor;
const _ScrollIndicatorPainter({
required this.backgroundColor,
required this.shadowColor,
});
@override
void paint(Canvas canvas, Size size) {
var path = Path()
..moveTo(size.width, 0)
..lineTo(size.width, size.height)
..arcToPoint(
Offset(size.width, 0),
radius: Radius.circular(size.width),
);
canvas.drawShadow(path, shadowColor, 4, true);
var backgroundPaint = Paint()
..color = backgroundColor
..style = PaintingStyle.fill;
path = Path()
..moveTo(size.width, 0)
..lineTo(size.width, size.height)
..arcToPoint(
Offset(size.width, 0),
radius: Radius.circular(size.width),
);
canvas.drawPath(path, backgroundPaint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return oldDelegate is! _ScrollIndicatorPainter ||
oldDelegate.backgroundColor != backgroundColor ||
oldDelegate.shadowColor != shadowColor;
}
}

View File

@@ -518,11 +518,9 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
), ),
], ],
); );
body = Scrollbar( body = AppScrollBar(
topPadding: 48,
controller: scrollController, controller: scrollController,
thickness: App.isDesktop ? 8 : 12,
radius: const Radius.circular(8),
interactive: true,
child: ScrollConfiguration( child: ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
child: body, child: body,