In short, you can achieve it by adding Listener
to ScrollController
.
A. Calculate by scroll offset

In this solution, you need to know the first item offset in the list and each item height.
final double initialOffset = 300;
final double itemHeight = 50;
Then you can change the index every time ScrollController is scrolled:
...
final ScrollController _scrollController = ScrollController();
...
@override
Widget build(BuildContext context) {
final index = ValueNotifier<int>(-1);
_scrollController.addListener(() {
index.value = ((_scrollController.offset-initialOffset)/itemHeight).round()-1;
});
return CustomScrollView(
controller: _scrollController,
slivers: [
SliverToBoxAdapter(
child: Container(height: 100, color: Colors.blue),
),
SliverToBoxAdapter(
child: Container(height: 100, color: Colors.blue),
),
ValueListenableBuilder(
valueListenable: index,
builder: (_,value,__){
return SliverPersistentHeader(
pinned: true,
delegate: PersistentHeader(value),
);
},
),
SliverList(
delegate: SliverChildBuilderDelegate(
...
B. Calculate children location on the screen

This is much more complicated.
It keeps track of all children in the list inside the screen and returns the index from the screen offset:
bool inTopArea(double dy) => dy <= 60 && dy >=0;
Also, it needs Global keys to keep track of all children and gets the global location (from here)
extension GlobalKeyExtension on GlobalKey {
Rect get globalPaintBounds {
final renderObject = currentContext?.findRenderObject();
var translation = renderObject?.getTransformTo(null)?.getTranslation();
if (translation != null && renderObject.paintBounds != null) {
return renderObject.paintBounds
.shift(Offset(translation.x, translation.y));
} else {
return null;
}
}
}
final keys = <GlobalKey>[];
Because you use builder to generate the list, the child must be a stateful widget that contains dispose
method, which represents the widget is out of the screen:
...
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final uniqueKey = GlobalKey();
keys.add(uniqueKey);
return MyChildWidget(
index: index,
dispose: (){keys.remove(uniqueKey);},
key: uniqueKey,
);
},
),
),
...
class MyChildWidget extends StatefulWidget {
const MyChildWidget({this.index,this.dispose,Key key}):super(key: key);
final int index;
final VoidCallback dispose;
@override
_MyChildWidgetState createState() => _MyChildWidgetState();
}
class _MyChildWidgetState extends State<MyChildWidget> {
@override
Widget build(BuildContext context) {
return ListTile(
title: Text('bron ${widget.index}'),
);
}
@override
void dispose() {
widget.dispose();
super.dispose();
}
}
Finally, the calculation of every child dy location when scrolling:
_scrollController.addListener(() {
final tops = keys.where((aKey) {
return inTopArea(aKey.globalPaintBounds.topLeft.dy);
});
if(tops.isNotEmpty){
index.value = (tops.first.currentState as _MyChildWidgetState).widget.index;
}else{
index.value = -1;
}
});