In short, you can achieve it by adding Listener to ScrollController.
A. Calculate by scroll offset
![enter image description here]()
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
![enter image description here]()
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;
}
});