mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
closes #40740
<p align="center">
<img
src="https://github.com/user-attachments/assets/8a48abd0-466b-4e06-90f3-dd05869bac27"
alt="NestedScrollController fix"
height="720px"
/>
</p>
<details> <summary><h4>Demo source code</h4> (click to expand)</summary>
(this is a slightly more concise version of the code sample from #40740)
```dart
import 'package:flutter/material.dart';
void main() {
runApp(
const MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(body: NewsScreen()),
),
);
}
class NewsScreen extends StatelessWidget {
const NewsScreen({super.key});
static const List<String> _tabs = <String>['Featured', 'Popular', 'Latest'];
static final List<Widget> _tabViews = <Widget>[
for (final String name in _tabs)
SafeArea(
top: false,
bottom: false,
child: Builder(builder: (BuildContext context) {
final handle = NestedScrollView.sliverOverlapAbsorberHandleFor(context);
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) => true,
child: CustomScrollView(
key: PageStorageKey<String>(name),
slivers: <Widget>[
SliverOverlapInjector(handle: handle),
SliverPadding(
padding: const EdgeInsets.all(8.0),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
childCount: 30,
(BuildContext context, int index) => Container(
margin: const EdgeInsets.only(bottom: 8),
width: double.infinity,
height: 150,
color: const Color(0xFFB0A4C8),
alignment: Alignment.center,
child: Text(
'$name $index',
style: const TextStyle(fontWeight: FontWeight.w600),
),
),
),
),
),
],
),
);
}),
),
];
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: _tabs.length,
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) => <Widget>[
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverSafeArea(
top: false,
sliver: SliverAppBar(
title: const Text('Tab Demo'),
floating: true,
pinned: true,
snap: true,
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
tabs: _tabs.map((String name) => Tab(text: name)).toList(),
),
),
),
),
],
body: TabBarView(children: _tabViews),
),
);
}
}
```
<br>
</details>
<br>
This bug can be traced to a return statement inside `_NestedScrollPosition`:
```dart
double applyClampedDragUpdate(double delta) {
// ...
return delta + offset;
}
```
Thanks to some quirks of floating-point arithmetic, `applyClampedDragUpdate` would sometimes return a tiny non-zero value, which ends up ruining one of the scroll coordinator's equality checks.
8990ed6538/packages/flutter/lib/src/widgets/nested_scroll_view.dart (L658-L664)