Let scrollOffsetCorrection on small lists not leave the list stuck in overscroll (#17599)
This commit is contained in:
parent
1808ac338c
commit
ef25052c51
@ -282,7 +282,8 @@ class CupertinoRefreshControl extends StatefulWidget {
|
||||
|
||||
/// The amount of overscroll the scrollable must be dragged to trigger a reload.
|
||||
///
|
||||
/// Must not be null, must be larger than 0.0 and larger than [refreshIndicatorExtent].
|
||||
/// Must not be null, must be larger than 0.0 and larger than
|
||||
/// [refreshIndicatorExtent]. Defaults to 100px when not specified.
|
||||
///
|
||||
/// When overscrolled past this distance, [onRefresh] will be called if not
|
||||
/// null and the [builder] will build in the [RefreshIndicatorMode.armed] state.
|
||||
@ -293,6 +294,7 @@ class CupertinoRefreshControl extends StatefulWidget {
|
||||
///
|
||||
/// Must not be null and must be positive, but can be 0.0, in which case the
|
||||
/// sliver will start retracting back to 0.0 as soon as the refresh is started.
|
||||
/// Defaults to 60px when not specified.
|
||||
///
|
||||
/// Must be smaller than [refreshTriggerPullDistance], since the sliver
|
||||
/// shouldn't grow further after triggering the refresh.
|
||||
|
@ -150,10 +150,20 @@ abstract class ViewportOffset extends ChangeNotifier {
|
||||
/// [RenderViewport], before [applyContentDimensions]. After this method is
|
||||
/// called, the layout will be recomputed and that may result in this method
|
||||
/// being called again, though this should be very rare.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [jumpTo], for also changing the scroll position when not in layout.
|
||||
/// [jumpTo] applies the change immediately and notifies its listeners.
|
||||
void correctBy(double correction);
|
||||
|
||||
/// Jumps the scroll position from its current value to the given value,
|
||||
/// without animation, and without checking if the new value is in range.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [correctBy], for changing the current offset in the middle of layout
|
||||
/// and that defers the notification of its listeners until after layout.
|
||||
void jumpTo(double pixels);
|
||||
|
||||
/// The direction in which the user is trying to change [pixels], relative to
|
||||
|
@ -250,13 +250,43 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
|
||||
/// To force the [pixels] to a particular value without honoring the normal
|
||||
/// conventions for changing the scroll offset, consider [forcePixels]. (But
|
||||
/// see the discussion there for why that might still be a bad idea.)
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * The method [correctBy], which is a method of [ViewportOffset] used
|
||||
/// by viewport render objects to correct the offset during layout
|
||||
/// without notifying its listeners.
|
||||
/// * The method [jumpTo], for making changes to position while not in the
|
||||
/// middle of layout and applying the new position immediately.
|
||||
/// * The method [animateTo], which is like [jumpTo] but animating to the
|
||||
/// distination offset.
|
||||
void correctPixels(double value) {
|
||||
_pixels = value;
|
||||
}
|
||||
|
||||
/// Apply a layout-time correction to the scroll offset.
|
||||
///
|
||||
/// This method should change the [pixels] value by `correction`, but without
|
||||
/// calling [notifyListeners]. It is called during layout by the
|
||||
/// [RenderViewport], before [applyContentDimensions]. After this method is
|
||||
/// called, the layout will be recomputed and that may result in this method
|
||||
/// being called again, though this should be very rare.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [jumpTo], for also changing the scroll position when not in layout.
|
||||
/// [jumpTo] applies the change immediately and notifies its listeners.
|
||||
/// * [correctPixels], which is used by the [ScrollPosition] itself to
|
||||
/// set the offset initially during construction or after
|
||||
/// [applyViewportDimension] or [applyContentDimensions] is called.
|
||||
@override
|
||||
void correctBy(double correction) {
|
||||
assert(
|
||||
_pixels != null,
|
||||
'An initial pixels value must exist by caling correctPixels on the ScrollPosition',
|
||||
);
|
||||
_pixels += correction;
|
||||
_didChangeViewportDimensionOrReceiveCorrection = true;
|
||||
}
|
||||
|
||||
/// Change the value of [pixels] to the new value, and notify any customers,
|
||||
@ -360,13 +390,13 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
|
||||
return result;
|
||||
}
|
||||
|
||||
bool _didChangeViewportDimension = true;
|
||||
bool _didChangeViewportDimensionOrReceiveCorrection = true;
|
||||
|
||||
@override
|
||||
bool applyViewportDimension(double viewportDimension) {
|
||||
if (_viewportDimension != viewportDimension) {
|
||||
_viewportDimension = viewportDimension;
|
||||
_didChangeViewportDimension = true;
|
||||
_didChangeViewportDimensionOrReceiveCorrection = true;
|
||||
// If this is called, you can rely on applyContentDimensions being called
|
||||
// soon afterwards in the same layout phase. So we put all the logic that
|
||||
// relies on both values being computed into applyContentDimensions.
|
||||
@ -419,12 +449,12 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
|
||||
bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) {
|
||||
if (!nearEqual(_minScrollExtent, minScrollExtent, Tolerance.defaultTolerance.distance) ||
|
||||
!nearEqual(_maxScrollExtent, maxScrollExtent, Tolerance.defaultTolerance.distance) ||
|
||||
_didChangeViewportDimension) {
|
||||
_didChangeViewportDimensionOrReceiveCorrection) {
|
||||
_minScrollExtent = minScrollExtent;
|
||||
_maxScrollExtent = maxScrollExtent;
|
||||
_haveDimensions = true;
|
||||
applyNewDimensions();
|
||||
_didChangeViewportDimension = false;
|
||||
_didChangeViewportDimensionOrReceiveCorrection = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -84,11 +84,6 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc
|
||||
return super.setPixels(newPixels);
|
||||
}
|
||||
|
||||
@override
|
||||
void correctBy(double correction) {
|
||||
correctPixels(pixels + correction);
|
||||
}
|
||||
|
||||
@override
|
||||
void absorb(ScrollPosition other) {
|
||||
super.absorb(other);
|
||||
|
@ -62,6 +62,7 @@ void main() {
|
||||
when(mockHelper.refreshTask()).thenAnswer((_) => refreshCompleter.future);
|
||||
});
|
||||
|
||||
int testListLength = 10;
|
||||
SliverList buildAListOfStuff() {
|
||||
return new SliverList(
|
||||
delegate: new SliverChildBuilderDelegate(
|
||||
@ -71,12 +72,12 @@ void main() {
|
||||
child: new Center(child: new Text(index.toString())),
|
||||
);
|
||||
},
|
||||
childCount: 10,
|
||||
childCount: testListLength,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
group('UI tests', () {
|
||||
final Function uiTestGroup = () {
|
||||
testWidgets("doesn't invoke anything without user interaction", (WidgetTester tester) async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
||||
|
||||
@ -682,6 +683,11 @@ void main() {
|
||||
testWidgets(
|
||||
'sliver scrolled away when task completes properly removes itself',
|
||||
(WidgetTester tester) async {
|
||||
if (testListLength < 4) {
|
||||
// This test only makes sense when the list is long enough that
|
||||
// the indicator can be scrolled away while refreshing.
|
||||
return;
|
||||
}
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
||||
|
||||
refreshIndicator = const Center(child: const Text('-1'));
|
||||
@ -849,11 +855,9 @@ void main() {
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// Test the internal state machine directly to make sure the UI aren't just
|
||||
// correct by coincidence.
|
||||
group('state machine test', () {
|
||||
final Function stateMachineTestGroup = () {
|
||||
testWidgets('starts in inactive state', (WidgetTester tester) async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
||||
|
||||
@ -1228,7 +1232,22 @@ void main() {
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
group('UI tests long list', uiTestGroup);
|
||||
|
||||
// Test the internal state machine directly to make sure the UI aren't just
|
||||
// correct by coincidence.
|
||||
group('state machine test long list', stateMachineTestGroup);
|
||||
|
||||
// Retest everything and make sure that it still works when the whole list
|
||||
// is smaller than the viewport size.
|
||||
testListLength = 2;
|
||||
group('UI tests short list', uiTestGroup);
|
||||
|
||||
// Test the internal state machine directly to make sure the UI aren't just
|
||||
// correct by coincidence.
|
||||
group('state machine test short list', stateMachineTestGroup);
|
||||
}
|
||||
|
||||
class MockHelper extends Mock {
|
||||
|
Loading…
x
Reference in New Issue
Block a user