mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Match non-linear overscroll spring to iOS (#11065)
* Make the drag resistance non-linear * Let the easing of overscroll have a spring effect too * Add tests and prevent possible drift by having a slightly smaller resistance when easing the overscroll * lint
This commit is contained in:
parent
e6bafd0bdb
commit
47c4d64f01
@ -226,33 +226,51 @@ class BouncingScrollPhysics extends ScrollPhysics {
|
||||
|
||||
/// The multiple applied to overscroll to make it appear that scrolling past
|
||||
/// the edge of the scrollable contents is harder than scrolling the list.
|
||||
/// This is done by reducing the ratio of the scroll effect output vs the
|
||||
/// scroll gesture input.
|
||||
///
|
||||
/// By default this is 0.5, meaning that overscroll is twice as hard as normal
|
||||
/// scroll.
|
||||
double get frictionFactor => 0.5;
|
||||
/// This factor starts at 0.52 and progressively becomes harder to overscroll
|
||||
/// as more of the area past the edge is dragged in (represented by a reducing
|
||||
/// `inViewFraction` which starts at 1 when there is no overscroll).
|
||||
double frictionFactor(double inViewFraction) => 0.52 * math.pow(inViewFraction.abs(), 2);
|
||||
|
||||
@override
|
||||
double applyPhysicsToUserOffset(ScrollMetrics position, double offset) {
|
||||
assert(offset != 0.0);
|
||||
assert(position.minScrollExtent <= position.maxScrollExtent);
|
||||
if (offset > 0.0)
|
||||
return _applyFriction(position.pixels, position.minScrollExtent, position.maxScrollExtent, offset, frictionFactor);
|
||||
return -_applyFriction(-position.pixels, -position.maxScrollExtent, -position.minScrollExtent, -offset, frictionFactor);
|
||||
|
||||
if (!position.outOfRange)
|
||||
return offset;
|
||||
|
||||
final double overscrollPastStart = math.max(position.minScrollExtent - position.pixels, 0.0);
|
||||
final double overscrollPastEnd = math.max(position.pixels - position.maxScrollExtent, 0.0);
|
||||
final bool easing = (overscrollPastStart > 0.0 && offset < 0.0)
|
||||
|| (overscrollPastEnd > 0.0 && offset > 0.0);
|
||||
|
||||
final double friction = easing
|
||||
// Apply less resistance when easing the overscroll vs tensioning.
|
||||
? frictionFactor(math.min((position.extentInside + offset.abs()) / position.viewportDimension, 1.0))
|
||||
: frictionFactor(position.extentInside / position.viewportDimension);
|
||||
final double direction = offset.sign;
|
||||
|
||||
return direction * _applyFriction(
|
||||
math.max(overscrollPastStart, overscrollPastEnd),
|
||||
offset.abs(),
|
||||
friction,
|
||||
);
|
||||
}
|
||||
|
||||
static double _applyFriction(double start, double lowLimit, double highLimit, double delta, double gamma) {
|
||||
assert(lowLimit <= highLimit);
|
||||
assert(delta > 0.0);
|
||||
static double _applyFriction(double extentOutside, double absDelta, double gamma) {
|
||||
assert(absDelta > 0);
|
||||
double total = 0.0;
|
||||
if (start < lowLimit) {
|
||||
final double distanceToLimit = lowLimit - start;
|
||||
final double deltaToLimit = distanceToLimit / gamma;
|
||||
if (delta < deltaToLimit)
|
||||
return total + delta * gamma;
|
||||
total += distanceToLimit;
|
||||
delta -= deltaToLimit;
|
||||
if (extentOutside > 0) {
|
||||
final double deltaToLimit = extentOutside / gamma;
|
||||
if (absDelta < deltaToLimit)
|
||||
return absDelta * gamma;
|
||||
total += extentOutside;
|
||||
absDelta -= deltaToLimit;
|
||||
}
|
||||
return total + delta;
|
||||
return total + absDelta;
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -93,16 +93,18 @@ void main() {
|
||||
expect(leftOf(0), equals(0.0));
|
||||
expect(sizeOf(0), equals(const Size(800.0, 600.0)));
|
||||
|
||||
// Going into overscroll.
|
||||
await tester.drag(find.byType(PageView), const Offset(100.0, 0.0));
|
||||
await tester.pump();
|
||||
|
||||
expect(leftOf(0), equals(100.0));
|
||||
expect(leftOf(0), greaterThan(0.0));
|
||||
expect(sizeOf(0), equals(const Size(800.0, 600.0)));
|
||||
|
||||
// Easing overscroll past overscroll limit.
|
||||
await tester.drag(find.byType(PageView), const Offset(-200.0, 0.0));
|
||||
await tester.pump();
|
||||
|
||||
expect(leftOf(0), equals(-100.0));
|
||||
expect(leftOf(0), lessThan(0.0));
|
||||
expect(sizeOf(0), equals(const Size(800.0, 600.0)));
|
||||
});
|
||||
|
||||
|
||||
@ -75,4 +75,91 @@ void main() {
|
||||
expect(types(page.applyTo(bounce.applyTo(clamp.applyTo(never.applyTo(always))))),
|
||||
'PageScrollPhysics BouncingScrollPhysics ClampingScrollPhysics NeverScrollableScrollPhysics AlwaysScrollableScrollPhysics');
|
||||
});
|
||||
|
||||
group('BouncingScrollPhysics test', () {
|
||||
BouncingScrollPhysics physicsUnderTest;
|
||||
|
||||
setUp(() {
|
||||
physicsUnderTest = const BouncingScrollPhysics();
|
||||
});
|
||||
|
||||
test('overscroll is progressively harder', () {
|
||||
final ScrollMetrics lessOverscrolledPosition = new FixedScrollMetrics(
|
||||
minScrollExtent: 0.0,
|
||||
maxScrollExtent: 1000.0,
|
||||
pixels: -20.0,
|
||||
viewportDimension: 100.0,
|
||||
axisDirection: AxisDirection.down,
|
||||
);
|
||||
|
||||
final ScrollMetrics moreOverscrolledPosition = new FixedScrollMetrics(
|
||||
minScrollExtent: 0.0,
|
||||
maxScrollExtent: 1000.0,
|
||||
pixels: -40.0,
|
||||
viewportDimension: 100.0,
|
||||
axisDirection: AxisDirection.down,
|
||||
);
|
||||
|
||||
final double lessOverscrollApplied =
|
||||
physicsUnderTest.applyPhysicsToUserOffset(lessOverscrolledPosition, 10.0);
|
||||
|
||||
final double moreOverscrollApplied =
|
||||
physicsUnderTest.applyPhysicsToUserOffset(moreOverscrolledPosition, 10.0);
|
||||
|
||||
expect(lessOverscrollApplied, greaterThan(1.0));
|
||||
expect(lessOverscrollApplied, lessThan(20.0));
|
||||
|
||||
expect(moreOverscrollApplied, greaterThan(1.0));
|
||||
expect(moreOverscrollApplied, lessThan(20.0));
|
||||
|
||||
// Scrolling from a more overscrolled position meets more resistance.
|
||||
expect(lessOverscrollApplied.abs(), greaterThan(moreOverscrollApplied.abs()));
|
||||
});
|
||||
|
||||
test('easing an overscroll still has resistance', () {
|
||||
final ScrollMetrics overscrolledPosition = new FixedScrollMetrics(
|
||||
minScrollExtent: 0.0,
|
||||
maxScrollExtent: 1000.0,
|
||||
pixels: -20.0,
|
||||
viewportDimension: 100.0,
|
||||
axisDirection: AxisDirection.down,
|
||||
);
|
||||
|
||||
final double easingApplied =
|
||||
physicsUnderTest.applyPhysicsToUserOffset(overscrolledPosition, -10.0);
|
||||
|
||||
expect(easingApplied, lessThan(-1.0));
|
||||
expect(easingApplied, greaterThan(-10.0));
|
||||
});
|
||||
|
||||
test('no resistance when not overscrolled', () {
|
||||
final ScrollMetrics scrollPosition = new FixedScrollMetrics(
|
||||
minScrollExtent: 0.0,
|
||||
maxScrollExtent: 1000.0,
|
||||
pixels: 300.0,
|
||||
viewportDimension: 100.0,
|
||||
axisDirection: AxisDirection.down,
|
||||
);
|
||||
|
||||
expect(physicsUnderTest.applyPhysicsToUserOffset(scrollPosition, 10.0), 10.0);
|
||||
expect(physicsUnderTest.applyPhysicsToUserOffset(scrollPosition, -10.0), -10.0);
|
||||
});
|
||||
|
||||
test('easing an overscroll meets less resistance than tensioning', () {
|
||||
final ScrollMetrics overscrolledPosition = new FixedScrollMetrics(
|
||||
minScrollExtent: 0.0,
|
||||
maxScrollExtent: 1000.0,
|
||||
pixels: -20.0,
|
||||
viewportDimension: 100.0,
|
||||
axisDirection: AxisDirection.down,
|
||||
);
|
||||
|
||||
final double easingApplied =
|
||||
physicsUnderTest.applyPhysicsToUserOffset(overscrolledPosition, -10.0);
|
||||
final double tensioningApplied =
|
||||
physicsUnderTest.applyPhysicsToUserOffset(overscrolledPosition, 10.0);
|
||||
|
||||
expect(easingApplied.abs(), greaterThan(tensioningApplied.abs()));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user