overscroll_feedback 0.0.2 copy "overscroll_feedback: ^0.0.2" to clipboard
overscroll_feedback: ^0.0.2 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 the end of a list.

It is useful for chat screens, message lists, feed pages, horizontal carousels, and any scrollable UI where you want to show a small “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.
  • 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.2

Then run:

flutter pub get

Import it:

import 'package:overscroll_feedback/overscroll_feedback.dart';

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');
  },
)

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'),
      ),
    );
  },
)

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,
)

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

3
likes
0
points
306
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter ListView that reveals custom feedback when users pull beyond the scroll edge, with optional haptics and callbacks.

Repository (GitHub)
View/report issues

Topics

#flutter #listview #overscroll #scroll-effects #haptics

License

unknown (license)

Dependencies

flutter

More

Packages that depend on overscroll_feedback