overscroll_feedback 0.0.3
overscroll_feedback: ^0.0.3 copied to clipboard
A Flutter ListView that reveals custom feedback when users pull beyond the scroll edge, with optional haptics and callbacks.
overscroll_feedback #
A Flutter widget that reveals custom feedback when users pull beyond a list edge.
It is useful for chat screens, message lists, feed pages, horizontal carousels, and any scrollable UI where you want to show a small "start reached" or "you reached the end" indicator, trigger haptic feedback, or run a callback when the user keeps pulling past the scroll limit.
Demo #
[Overscroll Feedback demo]
Features #
- Reveal a custom widget when the user pulls beyond the end of a list.
- Reveal feedback at the start, end, or both edges of the list.
- Supports vertical and horizontal
ListView. - Works with simple string lists, custom item types, or lazy builders.
- Progress-aware feedback builder for custom animations.
- Optional haptic feedback when the pull distance reaches a threshold.
- Custom haptic callback support.
- One-shot callback when the scroll limit is reached.
- Repeated hold callback while the user keeps pulling.
- Configurable reveal distance, haptic trigger distance, feedback size, and offset.
Installation #
Add the package to your pubspec.yaml:
dependencies:
overscroll_feedback: ^0.0.3
Then run:
flutter pub get
Import it:
import 'package:overscroll_feedback/overscroll_feedback.dart';
Which API should I use? #
Use the helper that matches your list:
| API | Best for |
|---|---|
OverscrollFeedback.strings |
Quick demos, settings pages, or simple text lists. |
OverscrollFeedback<T> |
Lists backed by your own model objects. |
OverscrollFeedback.builder |
Large, paged, or dynamic lists where items should be built lazily. |
ReachLimitListView |
Lower-level control when you want to provide every ListView option yourself. |
Quick start #
Use OverscrollFeedback.strings when you want a simple list of text items.
OverscrollFeedback.strings(
messages: const [
'Welcome aboard',
'Pull gently',
'Keep scrolling',
'Now pull past the end',
],
reachLimitWidget: DecoratedBox(
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(999),
),
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 18, vertical: 12),
child: Text(
'You reached the end',
style: TextStyle(color: Colors.white),
),
),
),
onReachLimit: () {
debugPrint('Reached the bottom');
},
)
Start, end, or both edges #
By default, feedback appears when the user pulls beyond the end of the list.
Use position when you want top/start feedback too.
OverscrollFeedback.strings(
position: OverscrollFeedbackPosition.both,
messages: const [
'First message',
'Second message',
'Third message',
],
startReachLimitWidget: const Text('Start of list'),
reachLimitWidget: const Text('End of list'),
)
For a top-only indicator:
OverscrollFeedback.strings(
position: OverscrollFeedbackPosition.start,
messages: const ['One', 'Two', 'Three'],
startReachLimitWidget: const Text('Nothing above this'),
)
Custom item type #
Use the generic constructor when your list contains custom model objects.
class Message {
const Message({
required this.text,
required this.sender,
});
final String text;
final String sender;
}
final messages = [
const Message(text: 'Hello', sender: 'Me'),
const Message(text: 'Hi there', sender: 'Friend'),
];
OverscrollFeedback<Message>(
items: messages,
itemBuilder: (context, message, index) {
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: BorderRadius.circular(16),
),
child: Text('${message.sender}: ${message.text}'),
);
},
reachLimitWidget: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(999),
),
child: const Text(
'No more messages',
style: TextStyle(color: Colors.white),
),
),
)
Lazy builder #
Use OverscrollFeedback.builder for large or dynamic lists. It works like
Flutter's ListView.builder, so items are created only when needed.
OverscrollFeedback.builder(
itemCount: 1000,
itemBuilder: (context, index) {
return ListTile(
title: Text('Message $index'),
);
},
reachLimitWidget: const Text('No more messages'),
)
Animated feedback #
Use reachLimitBuilder when your feedback should react to pull progress.
progress goes from 0.0 to 1.0.
OverscrollFeedback.builder(
itemCount: 30,
itemBuilder: (context, index) {
return ListTile(title: Text('Item $index'));
},
reachLimitBuilder: (context, progress) {
return Transform.scale(
scale: 0.8 + (progress * 0.2),
child: Opacity(
opacity: progress,
child: const Text('You reached the end'),
),
);
},
)
You can also animate the start indicator separately:
OverscrollFeedback.builder(
position: OverscrollFeedbackPosition.both,
itemCount: 30,
itemBuilder: (context, index) {
return ListTile(title: Text('Item $index'));
},
startReachLimitBuilder: (context, progress) {
return Opacity(
opacity: progress,
child: const Text('Start reached'),
);
},
reachLimitBuilder: (context, progress) {
return Transform.scale(
scale: 0.8 + (progress * 0.2),
child: const Text('End reached'),
);
},
)
Horizontal list #
For horizontal lists, set isHorizontal: true.
A horizontal list needs a fixed height, so height is required when
isHorizontal is true.
OverscrollFeedback.strings(
isHorizontal: true,
height: 140,
messages: const [
'Card',
'Article',
'Story',
'Preview',
],
itemWidth: 180,
reachLimitWidget: Container(
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: Colors.deepPurple,
borderRadius: BorderRadius.circular(16),
),
child: const Text(
'End',
style: TextStyle(color: Colors.white),
),
),
)
Callbacks #
onReachLimit #
Called once when the user pulls far enough to reach the feedback trigger.
OverscrollFeedback.strings(
messages: const ['One', 'Two', 'Three'],
reachLimitWidget: const Text('End reached'),
onReachLimit: () {
debugPrint('User reached the scroll limit');
},
)
onReachLimitHold #
Called repeatedly while the user keeps holding/pulling beyond the limit.
OverscrollFeedback.strings(
messages: const ['One', 'Two', 'Three'],
reachLimitWidget: const Text('Keep holding...'),
holdTriggerDistance: 140,
holdInterval: const Duration(milliseconds: 500),
onReachLimitHold: (pullDistance) {
debugPrint('Still pulling: $pullDistance');
},
)
Custom haptic feedback #
By default, the widget uses Flutter haptic vibration when the haptic trigger distance is reached.
You can provide your own haptic feedback callback:
OverscrollFeedback.strings(
messages: const ['One', 'Two', 'Three'],
reachLimitWidget: const Text('Limit reached'),
hapticFeedback: (pullDistance) {
if (pullDistance > 180) {
return HapticFeedback.heavyImpact();
}
return HapticFeedback.selectionClick();
},
)
You can also disable haptics:
OverscrollFeedback.strings(
messages: const ['One', 'Two', 'Three'],
reachLimitWidget: const Text('Limit reached'),
hapticsEnabled: false,
)
Common customization #
These options are useful for tuning the feel of the interaction:
| Option | What it controls |
|---|---|
position |
Which edge can reveal feedback: start, end, or both. |
reachLimitExtent |
The space reserved for the revealed feedback widget. |
offset |
How far the feedback slides while it is being revealed. |
revealStartDistance |
Pull distance before feedback starts appearing. |
revealEndDistance |
Pull distance where feedback reaches full opacity. |
hapticTriggerDistance |
Pull distance where haptics and onReachLimit trigger. |
holdTriggerDistance |
Pull distance where repeated hold callbacks begin. |
holdInterval |
How often onReachLimitHold runs while the user keeps pulling. |
Notes #
This package listens to overscroll notifications from the internal ListView.
For the best effect on iOS-style bouncing scroll, keep the default
BouncingScrollPhysics. On platforms that normally clamp overscroll, the
package uses AlwaysScrollableScrollPhysics by default so short lists can still
be pulled.
Tuning the reveal behavior #
You can control when the feedback starts appearing, when it becomes fully visible, and when haptics/callbacks are triggered.
OverscrollFeedback.strings(
messages: const ['One', 'Two', 'Three'],
reachLimitWidget: const Text('You reached the end'),
revealStartDistance: 32,
revealEndDistance: 120,
hapticTriggerDistance: 140,
)
Important values #
| Property | Description |
|---|---|
revealStartDistance |
Pull distance where the feedback widget starts appearing. |
revealEndDistance |
Pull distance where the feedback widget becomes fully visible. |
hapticTriggerDistance |
Pull distance where haptic feedback and onReachLimit trigger. |
holdTriggerDistance |
Pull distance where the repeated hold callback starts. |
holdInterval |
How often onReachLimitHold is called while holding. |
reachLimitExtent |
Space reserved for the revealed feedback widget. |
offset |
Starting offset animation for the revealed widget. |
hapticsEnabled |
Enables or disables built-in haptic feedback. |
hapticFeedback |
Custom haptic callback. |
Full example #
import 'package:flutter/material.dart';
import 'package:overscroll_feedback/overscroll_feedback.dart';
class OverscrollDemoPage extends StatelessWidget {
const OverscrollDemoPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: OverscrollFeedback.strings(
messages: List.generate(12, (index) => 'Message'),
revealStartDistance: 40,
revealEndDistance: 120,
hapticTriggerDistance: 135,
reachLimitWidget: Container(
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(999),
),
child: const Text(
'You reached the end',
style: TextStyle(color: Colors.white),
),
),
onReachLimit: () {
debugPrint('Reached the list end');
},
onReachLimitHold: (pullDistance) {
debugPrint('Holding at distance: $pullDistance');
},
),
),
);
}
}
Notes #
Haptics #
Haptic feedback depends on the device, platform, and system settings.
For best testing, use a real device instead of a simulator or emulator. Some devices may ignore repeated vibration requests or make them feel weaker than expected.
Android vibration permission #
If your custom haptic implementation uses a vibration plugin, you may need to add the Android vibration permission:
<uses-permission android:name="android.permission.VIBRATE"/>
Flutter's built-in HapticFeedback does not require extra Dart setup.
Horizontal lists #
When using isHorizontal: true, provide a fixed height.
OverscrollFeedback.strings(
isHorizontal: true,
height: 160,
messages: const ['One', 'Two', 'Three'],
reachLimitWidget: const Text('End'),
)
Example app #
A runnable example app is included in the example folder.
cd example
flutter run
API overview #
OverscrollFeedback<T> #
Generic widget for custom item types.
OverscrollFeedback<T>({
required List<T> items,
required ReachLimitItemBuilder<T> itemBuilder,
required Widget reachLimitWidget,
})
OverscrollFeedback.strings #
Convenience constructor for simple string lists.
OverscrollFeedback.strings({
required List<String> messages,
required Widget reachLimitWidget,
})
License #
MIT