Skip to content

Commit

Permalink
Limit overscroll stretching (#99364)
Browse files Browse the repository at this point in the history
  • Loading branch information
Piinks authored Mar 2, 2022
1 parent 4df1c0c commit d211f3f
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 2 deletions.
14 changes: 12 additions & 2 deletions packages/flutter/lib/src/widgets/overscroll_indicator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,15 @@ class _StretchingOverscrollIndicatorState extends State<StretchingOverscrollIndi
} else {
assert(notification.overscroll != 0.0);
if (notification.dragDetails != null) {
_stretchController.pull(notification.overscroll.abs() / notification.metrics.viewportDimension);
// We clamp the overscroll amount relative to the length of the viewport,
// which is the furthest distance a single pointer could pull on the
// screen. This is because more than one pointer will multiply the
// amount of overscroll - https://github.com/flutter/flutter/issues/11884
final double viewportDimension = notification.metrics.viewportDimension;
final double distanceForPull =
(notification.overscroll.abs() / viewportDimension) + _stretchController.pullDistance;
final double clampedOverscroll = distanceForPull.clamp(0, 1.0);
_stretchController.pull(clampedOverscroll);
}
}
}
Expand Down Expand Up @@ -818,6 +826,8 @@ class _StretchController extends ChangeNotifier {
late final Animation<double> _stretchSize;
final Tween<double> _stretchSizeTween = Tween<double>(begin: 0.0, end: 0.0);
_StretchState _state = _StretchState.idle;

double get pullDistance => _pullDistance;
double _pullDistance = 0.0;

// Constants from Android.
Expand Down Expand Up @@ -848,7 +858,7 @@ class _StretchController extends ChangeNotifier {
/// in the main axis.
void pull(double normalizedOverscroll) {
assert(normalizedOverscroll >= 0.0);
_pullDistance = normalizedOverscroll + _pullDistance;
_pullDistance = normalizedOverscroll;
_stretchSizeTween.begin = _stretchSize.value;
final double linearIntensity =_stretchIntensity * _pullDistance;
final double exponentialIntensity = _stretchIntensity * (1 - math.exp(-_pullDistance * _exponentialScalar));
Expand Down
121 changes: 121 additions & 0 deletions packages/flutter/test/widgets/overscroll_stretch_indicator_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -451,4 +451,125 @@ void main() {
await gesture.up();
await tester.pumpAndSettle();
});

testWidgets('Stretch limit', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/99264
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: ScrollConfiguration(
behavior: const ScrollBehavior().copyWith(overscroll: false),
child: StretchingOverscrollIndicator(
axisDirection: AxisDirection.down,
child: SizedBox(
height: 300,
child: ListView.builder(
itemCount: 20,
itemBuilder: (BuildContext context, int index){
return Padding(
padding: const EdgeInsets.all(10.0),
child: Text('Index $index'),
);
},
),
),
),
),
)
)
);
const double maxStretchLocation = 52.63178407049861;

expect(find.text('Index 1'), findsOneWidget);
expect(tester.getCenter(find.text('Index 1')).dy, 51.0);

TestGesture pointer = await tester.startGesture(tester.getCenter(find.text('Index 1')));
// Overscroll beyond the limit (the viewport is 600.0).
await pointer.moveBy(const Offset(0.0, 610.0));
await tester.pumpAndSettle();
expect(find.text('Index 1'), findsOneWidget);
expect(tester.getCenter(find.text('Index 1')).dy, maxStretchLocation);

pointer = await tester.startGesture(tester.getCenter(find.text('Index 1')));
// Overscroll way way beyond the limit
await pointer.moveBy(const Offset(0.0, 1000.0));
await tester.pumpAndSettle();
expect(find.text('Index 1'), findsOneWidget);
expect(tester.getCenter(find.text('Index 1')).dy, maxStretchLocation);

await pointer.up();
await tester.pumpAndSettle();
});

testWidgets('Multiple pointers wll not exceed stretch limit', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/99264
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: ScrollConfiguration(
behavior: const ScrollBehavior().copyWith(overscroll: false),
child: StretchingOverscrollIndicator(
axisDirection: AxisDirection.down,
child: SizedBox(
height: 300,
child: ListView.builder(
itemCount: 20,
itemBuilder: (BuildContext context, int index){
return Padding(
padding: const EdgeInsets.all(10.0),
child: Text('Index $index'),
);
},
),
),
),
),
)
)
);
expect(find.text('Index 1'), findsOneWidget);
expect(tester.getCenter(find.text('Index 1')).dy, 51.0);

final TestGesture pointer1 = await tester.startGesture(tester.getCenter(find.text('Index 1')));
// Overscroll the start.
await pointer1.moveBy(const Offset(0.0, 210.0));
await tester.pumpAndSettle();
expect(find.text('Index 1'), findsOneWidget);
double lastStretchedLocation = tester.getCenter(find.text('Index 1')).dy;
expect(lastStretchedLocation, greaterThan(51.0));

final TestGesture pointer2 = await tester.startGesture(tester.getCenter(find.text('Index 1')));
// Add overscroll from an additional pointer
await pointer2.moveBy(const Offset(0.0, 210.0));
await tester.pumpAndSettle();
expect(find.text('Index 1'), findsOneWidget);
expect(tester.getCenter(find.text('Index 1')).dy, greaterThan(lastStretchedLocation));
lastStretchedLocation = tester.getCenter(find.text('Index 1')).dy;

final TestGesture pointer3 = await tester.startGesture(tester.getCenter(find.text('Index 1')));
// Add overscroll from an additional pointer, exceeding the max stretch (600)
await pointer3.moveBy(const Offset(0.0, 210.0));
await tester.pumpAndSettle();
expect(find.text('Index 1'), findsOneWidget);
expect(tester.getCenter(find.text('Index 1')).dy, greaterThan(lastStretchedLocation));
lastStretchedLocation = tester.getCenter(find.text('Index 1')).dy;

final TestGesture pointer4 = await tester.startGesture(tester.getCenter(find.text('Index 1')));
// Since we have maxed out the overscroll, it should not have stretched
// further, regardless of the number of pointers.
await pointer4.moveBy(const Offset(0.0, 210.0));
await tester.pumpAndSettle();
expect(find.text('Index 1'), findsOneWidget);
expect(tester.getCenter(find.text('Index 1')).dy, lastStretchedLocation);

await pointer1.up();
await pointer2.up();
await pointer3.up();
await pointer4.up();
await tester.pumpAndSettle();
});
}

0 comments on commit d211f3f

Please sign in to comment.