tap_on_scroll 0.0.3
tap_on_scroll: ^0.0.3 copied to clipboard
Flutter package for reliable tap events on scrollable widgets even during scroll motion. Perfect for lists, grids, and slivers.
tap_on_scroll #
A Flutter package that solves the common issue of missed tap events on items while scrolling. tap_on_scroll
intercepts tap events during scroll interactions and reliably dispatches them to registered tappable areas in both regular and sliver-based layouts.
๐ฑ Demo #
Here's how tap_on_scroll solves the problem of unresponsive taps during scrolling:

Demonstration of tap_on_scroll functionality showing how it ensures reliable tap detection even during active scrolling.
๐ Features #
- Reliable Tap Detection: Prevents missed tap events while scrolling
- Tappable Areas: Easily designate areas that should respond to tap events using
TappableArea
- Seamless Integration: Works with existing scrollable widgets
- Simple API: Minimal setup to enhance tap responsiveness in your Flutter apps
- Zero Dependencies: Only depends on Flutter core libraries
- Perfect for Pinned Headers: Makes PinnedHeaderSlivers consistently tappable even during scroll
๐ Installation #
Add this to your package's pubspec.yaml
file:
dependencies:
tap_on_scroll: ^0.0.3
Or install it directly from the command line:
flutter pub add tap_on_scroll
๐ฏ Usage #
Wrap your scrollable widget with TapInterceptor
and wrap your tappable items with TappableArea
:
Basic Example #
import 'package:flutter/material.dart';
import 'package:tap_on_scroll/tap_on_scroll.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final ScrollController _scrollController = ScrollController();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'tap_on_scroll Demo',
home: Scaffold(
appBar: AppBar(
title: const Text('tap_on_scroll Demo'),
),
body: TapInterceptor(
child: ListView.builder(
controller: _scrollController,
itemCount: 20,
itemBuilder: (context, index) {
return TappableArea(
onTap: () => print('Tapped item $index'),
child: ListTile(
title: Text('Item $index'),
),
);
},
),
),
),
);
}
}
Using with GridView #
TapInterceptor(
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemCount: 20,
itemBuilder: (context, index) {
return TappableArea(
onTap: () => print('Tapped grid item $index'),
child: Card(
child: Center(
child: Text('Grid Item $index'),
),
),
);
},
),
)
Using with Pinned Headers (SliverAppBar) #
This is an especially useful application of tap_on_scroll, as pinned headers often lose tap responsiveness during scroll:
TapInterceptor(
child: CustomScrollView(
slivers: [
// Pinned SliverAppBar with TappableArea for title and actions
SliverAppBar(
pinned: true,
expandedHeight: 150.0,
// Make the title tappable
flexibleSpace: FlexibleSpaceBar(
title: TappableArea(
onTap: () => print('Pinned header title tapped!'),
child: Text('Pinned Header'),
),
background: Image.network(
'https://example.com/image.jpg',
fit: BoxFit.cover,
),
),
// CRUCIAL USE CASE: Make action buttons tappable during scroll
actions: [
TappableArea(
onTap: () => print('Search button tapped during scroll!'),
child: IconButton(
icon: Icon(Icons.search),
onPressed: () {}, // This might not work reliably during scroll
),
),
TappableArea(
onTap: () => print('Favorite button tapped during scroll!'),
child: IconButton(
icon: Icon(Icons.favorite),
onPressed: () {}, // This might not work reliably during scroll
),
),
TappableArea(
onTap: () => print('Menu button tapped during scroll!'),
child: IconButton(
icon: Icon(Icons.more_vert),
onPressed: () {}, // This might not work reliably during scroll
),
),
],
),
// Section header that remains tappable
SliverPersistentHeader(
pinned: true,
delegate: YourHeaderDelegate(
child: TappableArea(
onTap: () => print('Section header tapped!'),
child: Container(
color: Colors.teal,
child: Center(
child: Text('Always Tappable Section Header'),
),
),
),
),
),
// Regular list items
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return TappableArea(
onTap: () => print('Item $index tapped'),
child: ListTile(title: Text('Item $index')),
);
},
childCount: 50,
),
),
],
),
)
๐ How It Works #
TapInterceptor
intercepts tap events and checks if a tap falls within any registered tappable areas. When a tap is detected:
- It identifies which
TappableArea
was tapped - Dispatches the tap event to the appropriate area
- Triggers the
onTap
callback you provided
This approach ensures that taps are reliably detected even during scroll momentum, solving a common issue with pinned headers and other interactive elements during scrolling.
Implementation Details #
The package is designed with simplicity in mind:
TapInterceptor
doesn't require a scroll controller - it works automatically with any scrollable widgetTappableArea
registers itself with the nearestTapInterceptor
ancestor- The tap detection works by computing the global position of each tappable area and checking if tap events fall within that area
- Perfect for pinned headers because it resolves the conflict between scroll gestures and tap interactions that often causes unresponsiveness
๐งช Example #
Check out the example app for a complete implementation showing how to use tap_on_scroll
in a real-world scenario, including a dedicated example of making pinned headers tappable during scrolling.
๐ค Contributing #
Contributions are welcome! If you encounter issues or have suggestions for improvements, please:
- Open an issue describing the bug or feature request
- Submit a pull request with your changes
- Ensure your code follows the project's coding standards
๐ License #
This project is licensed under the MIT License - see the LICENSE file for details.
๐ฑ Compatibility #
- Dart SDK: ^3.7.0
- Flutter: ">=1.17.0"
๐ฅ Authors #
- Adnan Aljafarey - Initial work - GitHub
If you find this package helpful, please consider giving it a star โญ on GitHub!