简而言之,你可以通过在ScrollController中添加Listener来实现。
A.按滚动偏移量计算
在此解决方案中,您需要知道列表中的第一项偏移量和每个项的高度。
final double initialOffset = 300;
final double itemHeight = 50;
那么你可以在每次滚动ScrollController时改变索引:
...
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.计算孩子在屏幕上的位置
这要复杂得多。
它跟踪屏幕内列表中的所有子项,并从屏幕偏移量返回索引:
bool inTopArea(double dy) => dy <= 60 && dy >=0;
另外,它需要全局键来跟踪所有孩子并获取全局位置(来自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>[];
因为你使用builder来生成列表,所以child必须是一个有状态的widget,包含dispose方法,代表widget在屏幕外:
...
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();
}
}
最后,滚动时每个子dy位置的计算:
_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;
}
});