feat(library): show a trailing fade on the filter chips row

Extend ScrollEdgeFade with a horizontal axis and wrap the All/Albums/
Singles/Playlists chips so a right-edge fade signals the row scrolls
when chips overflow the screen.
This commit is contained in:
zarzet
2026-07-27 02:34:13 +07:00
parent ef3e019a3a
commit 3b901461f6
3 changed files with 73 additions and 52 deletions
+26 -11
View File
@@ -1,12 +1,20 @@
import 'package:flutter/material.dart';
/// Overlays a bottom gradient on a vertical scrollable while more content
/// remains below, signalling that the view can scroll.
/// Overlays a gradient on the trailing edge of a scrollable while more
/// content remains beyond it, signalling that the view can scroll.
class ScrollEdgeFade extends StatefulWidget {
final Widget child;
final double height;
final Axis axis;
const ScrollEdgeFade({super.key, required this.child, this.height = 96});
/// Thickness of the fade: height for vertical, width for horizontal.
final double size;
const ScrollEdgeFade({
super.key,
required this.child,
this.axis = Axis.vertical,
this.size = 96,
});
@override
State<ScrollEdgeFade> createState() => _ScrollEdgeFadeState();
@@ -16,7 +24,7 @@ class _ScrollEdgeFadeState extends State<ScrollEdgeFade> {
bool _hasMore = false;
bool _onMetrics(ScrollMetrics metrics) {
if (metrics.axis == Axis.vertical) {
if (metrics.axis == widget.axis) {
final hasMore = metrics.extentAfter > 8;
if (hasMore != _hasMore) setState(() => _hasMore = hasMore);
}
@@ -26,6 +34,7 @@ class _ScrollEdgeFadeState extends State<ScrollEdgeFade> {
@override
Widget build(BuildContext context) {
final background = Theme.of(context).scaffoldBackgroundColor;
final horizontal = widget.axis == Axis.horizontal;
return Stack(
children: [
NotificationListener<ScrollMetricsNotification>(
@@ -35,20 +44,26 @@ class _ScrollEdgeFadeState extends State<ScrollEdgeFade> {
child: widget.child,
),
),
Positioned(
left: 0,
right: 0,
PositionedDirectional(
start: horizontal ? null : 0,
end: 0,
top: horizontal ? 0 : null,
bottom: 0,
child: IgnorePointer(
child: AnimatedOpacity(
opacity: _hasMore ? 1 : 0,
duration: const Duration(milliseconds: 200),
child: Container(
height: widget.height,
width: horizontal ? widget.size : null,
height: horizontal ? null : widget.size,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
begin: horizontal
? AlignmentDirectional.centerStart
: Alignment.topCenter,
end: horizontal
? AlignmentDirectional.centerEnd
: Alignment.bottomCenter,
colors: [background.withValues(alpha: 0), background],
),
),