To detect which Elements in a ListView are currently Visible in Flutter, you can use the VisibilityDetector package or the ScrollablePositionedList package. Here’s how to do it with both approaches:
✅ Option 1: Using VisibilityDetector (More General Purpose)
- Add the dependency:
dependencies:
visibility_detector: ^0.4.0
- Wrap each item in the list with
VisibilityDetector:
import 'package:flutter/material.dart';
import 'package:visibility_detector/visibility_detector.dart';
class MyListView extends StatelessWidget {
final List<String> items = List.generate(100, (index) => 'Item $index');
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return VisibilityDetector(
key: Key('item-$index'),
onVisibilityChanged: (info) {
if (info.visibleFraction > 0) {
print('Item $index is visible with ${info.visibleFraction * 100}%');
}
},
child: ListTile(title: Text(items[index])),
);
},
);
}
}
✅ Option 2: Using ScrollablePositionedList (Efficient for Large Lists)
- Add the dependency:
dependencies:
scrollable_positioned_list: ^0.3.5
- Use it like this:
import 'package:flutter/material.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
class MyListView extends StatefulWidget {
@override
_MyListViewState createState() => _MyListViewState();
}
class _MyListViewState extends State<MyListView> {
final ItemScrollController itemScrollController = ItemScrollController();
final ItemPositionsListener itemPositionsListener = ItemPositionsListener.create();
final List<String> items = List.generate(100, (index) => 'Item $index');
@override
void initState() {
super.initState();
itemPositionsListener.itemPositions.addListener(() {
final visibleItems = itemPositionsListener.itemPositions.value
.where((position) => position.itemLeadingEdge >= 0 && position.itemTrailingEdge <= 1)
.map((e) => e.index)
.toList();
print('Visible items: $visibleItems');
});
}
@override
Widget build(BuildContext context) {
return ScrollablePositionedList.builder(
itemCount: items.length,
itemBuilder: (context, index) => ListTile(title: Text(items[index])),
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener,
);
}
}
📝 Notes
VisibilityDetectoris more flexible and works with any scrollable.ScrollablePositionedListis more efficient for tracking visible items and gives you better control for things like programmatically scrolling to an item. Here is also how to implement lazy loading: https://programtom.com/dev/2025/04/23/lazy-loading-with-scrollcontroller-in-flutter/
