mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Fix frozen StretchingOverscrollIndicator animation (#147195)
`StretchingOverscrollIndicator`'s controller does not have a minimum value for its animation duration. When the `OverscrollNotification`'s `velocity` is small enough (< `25`) the controller's `absorbImpact` method sets this animation duration to 0ms, making the animation appear frozen to the user. This PR sets a minimum animation duration of 50ms. Fixes #146277 | Before | After | | --- | --- | | <video src="https://github.com/flutter/flutter/assets/82336674/8761f14e-d5a5-4a39-b8e7-9e77433ce2c6" width=250px />| <video src="https://github.com/flutter/flutter/assets/82336674/57b38448-29fb-41ad-a947-d7cf1c160ca3" width=250px /> |
This commit is contained in:
parent
ff5e2d5922
commit
dd647b0909
@ -841,15 +841,23 @@ class _StretchController extends ChangeNotifier {
|
||||
|
||||
double get value => _stretchSize.value;
|
||||
|
||||
// Constants for absorbImpact.
|
||||
static const double _kMinVelocity = 1;
|
||||
static const double _kMaxVelocity = 10000;
|
||||
static const Duration _kMinStretchDuration = Duration(milliseconds: 50);
|
||||
|
||||
/// Handle a fling to the edge of the viewport at a particular velocity.
|
||||
///
|
||||
/// The velocity must be positive.
|
||||
void absorbImpact(double velocity, double totalOverscroll) {
|
||||
assert(velocity >= 0.0);
|
||||
velocity = clampDouble(velocity, 1, 10000);
|
||||
velocity = clampDouble(velocity, _kMinVelocity, _kMaxVelocity);
|
||||
_stretchSizeTween.begin = _stretchSize.value;
|
||||
_stretchSizeTween.end = math.min(_stretchIntensity + (_flingFriction / velocity), 1.0);
|
||||
_stretchController.duration = Duration(milliseconds: (velocity * 0.02).round());
|
||||
_stretchController.duration = Duration(
|
||||
milliseconds:
|
||||
math.max(velocity * 0.02, _kMinStretchDuration.inMilliseconds).round(),
|
||||
);
|
||||
_stretchController.forward(from: 0.0);
|
||||
_state = _StretchState.absorb;
|
||||
_stretchDirection = totalOverscroll > 0 ? _StretchDirection.trailing : _StretchDirection.leading;
|
||||
|
||||
@ -1177,4 +1177,69 @@ void main() {
|
||||
|
||||
expect(tester.layers, contains(isA<ImageFilterLayer>()));
|
||||
});
|
||||
|
||||
testWidgets('Stretching animation completes after fling under scroll physics with high friction', (WidgetTester tester) async {
|
||||
// Regression test for https://github.com/flutter/flutter/issues/146277
|
||||
final GlobalKey box1Key = GlobalKey();
|
||||
final GlobalKey box2Key = GlobalKey();
|
||||
final GlobalKey box3Key = GlobalKey();
|
||||
late final OverscrollNotification overscrollNotification;
|
||||
final ScrollController controller = ScrollController();
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await tester.pumpWidget(NotificationListener<OverscrollNotification>(
|
||||
child: buildTest(
|
||||
box1Key,
|
||||
box2Key,
|
||||
box3Key,
|
||||
controller,
|
||||
physics: const _HighFrictionClampingScrollPhysics(),
|
||||
),
|
||||
onNotification: (OverscrollNotification notification) {
|
||||
overscrollNotification = notification;
|
||||
return false;
|
||||
},
|
||||
));
|
||||
|
||||
expect(find.byType(StretchingOverscrollIndicator), findsOneWidget);
|
||||
expect(find.byType(GlowingOverscrollIndicator), findsNothing);
|
||||
final RenderBox box1 = tester.renderObject(find.byKey(box1Key));
|
||||
final RenderBox box2 = tester.renderObject(find.byKey(box2Key));
|
||||
final RenderBox box3 = tester.renderObject(find.byKey(box3Key));
|
||||
|
||||
expect(controller.offset, 0.0);
|
||||
expect(box1.localToGlobal(Offset.zero), Offset.zero);
|
||||
expect(box2.localToGlobal(Offset.zero), const Offset(0.0, 250.0));
|
||||
expect(box3.localToGlobal(Offset.zero), const Offset(0.0, 500.0));
|
||||
|
||||
// We fling to the trailing edge and let it settle.
|
||||
await tester.fling(find.byType(CustomScrollView), const Offset(0.0, -50.0), 10000.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// We are now at the trailing edge
|
||||
expect(overscrollNotification.velocity, lessThan(25));
|
||||
expect(controller.offset, 150.0);
|
||||
expect(box1.localToGlobal(Offset.zero).dy, -150.0);
|
||||
expect(box2.localToGlobal(Offset.zero).dy, 100.0);
|
||||
expect(box3.localToGlobal(Offset.zero).dy, 350.0);
|
||||
});
|
||||
}
|
||||
|
||||
final class _HighFrictionClampingScrollPhysics extends ScrollPhysics {
|
||||
const _HighFrictionClampingScrollPhysics({super.parent});
|
||||
|
||||
@override
|
||||
ScrollPhysics applyTo(ScrollPhysics? ancestor) {
|
||||
return _HighFrictionClampingScrollPhysics(parent: buildParent(ancestor));
|
||||
}
|
||||
|
||||
@override
|
||||
Simulation? createBallisticSimulation(ScrollMetrics position, double velocity) {
|
||||
return ClampingScrollSimulation(
|
||||
position: position.pixels,
|
||||
velocity: velocity,
|
||||
friction: 0.94,
|
||||
tolerance: tolerance,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user