overscroll_feedback 0.0.5
overscroll_feedback: ^0.0.5 copied to clipboard
Flutter overscroll feedback for any scrollable, with custom indicators, haptics, and reach-hold-release callbacks.
import 'package:flutter/material.dart';
import 'package:overscroll_feedback/overscroll_feedback.dart';
void main() {
runApp(const OverscrollFeedbackExampleApp());
}
class OverscrollFeedbackExampleApp extends StatelessWidget {
const OverscrollFeedbackExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF2F6FED)),
useMaterial3: true,
),
home: const OverscrollFeedbackDemo(),
);
}
}
class OverscrollFeedbackDemo extends StatefulWidget {
const OverscrollFeedbackDemo({super.key});
@override
State<OverscrollFeedbackDemo> createState() => _OverscrollFeedbackDemoState();
}
class _OverscrollFeedbackDemoState extends State<OverscrollFeedbackDemo> {
int _reachCount = 0;
int _holdReleaseCount = 0;
static const _messages = [
'Welcome aboard',
'Pull gently',
'Keep scrolling',
'Nearly there',
'Now pull past the end',
];
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: colorScheme.surface,
appBar: AppBar(title: const Text('OverscrollFeedback')),
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
child: Text(
'Reached: $_reachCount · hold releases: $_holdReleaseCount',
style: Theme.of(context).textTheme.titleMedium,
),
),
Expanded(
child: OverscrollFeedback.strings(
position: OverscrollFeedbackPosition.both,
messages: _messages,
itemColor: colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(12),
startReachLimitBuilder: (context, progress) {
return _LimitFeedback(
icon: Icons.arrow_upward_rounded,
label: 'Start of list',
progress: progress,
color: colorScheme.tertiaryContainer,
foregroundColor: colorScheme.onTertiaryContainer,
);
},
reachLimitBuilder: (context, progress) {
return _LimitFeedback(
icon: Icons.touch_app_rounded,
label: 'You reached the end',
progress: progress,
color: colorScheme.secondaryContainer,
foregroundColor: colorScheme.onSecondaryContainer,
);
},
onReachLimit: () {
setState(() {
_reachCount += 1;
});
},
holdTriggerDistance: 138,
onReachLimitHoldRelease: (pullDistance) {
setState(() {
_holdReleaseCount += 1;
});
},
),
),
],
),
),
);
}
}
class _LimitFeedback extends StatelessWidget {
const _LimitFeedback({
required this.icon,
required this.label,
required this.progress,
required this.color,
required this.foregroundColor,
});
final IconData icon;
final String label;
final double progress;
final Color color;
final Color foregroundColor;
@override
Widget build(BuildContext context) {
return Center(
child: Transform.scale(
scale: 0.86 + (progress * 0.14),
child: DecoratedBox(
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(999),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 18, color: foregroundColor),
const SizedBox(width: 8),
Text(
label,
style: TextStyle(
color: foregroundColor,
fontWeight: FontWeight.w700,
),
),
],
),
),
),
),
);
}
}