Skip to content

Commit caebdaf

Browse files
authored
fix issue 30526: rounding error (flutter#30979)
1 parent 4230e96 commit caebdaf

File tree

2 files changed

+116
-2
lines changed

2 files changed

+116
-2
lines changed

packages/flutter/lib/src/rendering/sliver_fixed_extent_list.dart

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,15 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
6565
/// order, without gaps, starting from layout offset zero.
6666
@protected
6767
int getMinChildIndexForScrollOffset(double scrollOffset, double itemExtent) {
68-
return itemExtent > 0.0 ? math.max(0, scrollOffset ~/ itemExtent) : 0;
68+
if (itemExtent > 0.0) {
69+
final double actual = scrollOffset / itemExtent;
70+
final int round = actual.round();
71+
if ((actual - round).abs() < precisionErrorTolerance) {
72+
return round;
73+
}
74+
return actual.floor();
75+
}
76+
return 0;
6977
}
7078

7179
/// The maximum child index that is visible at the given scroll offset.
@@ -224,7 +232,7 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
224232
final double leadingScrollOffset = indexToLayoutOffset(itemExtent, firstIndex);
225233
final double trailingScrollOffset = indexToLayoutOffset(itemExtent, lastIndex + 1);
226234

227-
assert(firstIndex == 0 || childScrollOffset(firstChild) <= scrollOffset);
235+
assert(firstIndex == 0 || childScrollOffset(firstChild) - scrollOffset <= precisionErrorTolerance);
228236
assert(debugAssertChildListIsNonEmptyAndContiguous());
229237
assert(indexOf(firstChild) == firstIndex);
230238
assert(targetLastIndex == null || lastIndex <= targetLastIndex);
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright 2019 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/foundation.dart';
6+
import 'package:flutter/rendering.dart';
7+
import '../flutter_test_alternative.dart';
8+
9+
import 'rendering_tester.dart';
10+
11+
void main() {
12+
test('RenderSliverFixedExtentList layout test - rounding error', () {
13+
final List<RenderBox> children = <RenderBox>[
14+
RenderSizedBox(const Size(400.0, 100.0)),
15+
RenderSizedBox(const Size(400.0, 100.0)),
16+
RenderSizedBox(const Size(400.0, 100.0))
17+
];
18+
final TestRenderSliverBoxChildManager childManager = TestRenderSliverBoxChildManager(
19+
children: children,
20+
);
21+
final RenderViewport root = RenderViewport(
22+
axisDirection: AxisDirection.down,
23+
crossAxisDirection: AxisDirection.right,
24+
offset: ViewportOffset.zero(),
25+
cacheExtent: 0,
26+
children: <RenderSliver>[
27+
childManager.createRenderSliverFillViewport(),
28+
],
29+
);
30+
layout(root);
31+
expect(children[0].attached, true);
32+
expect(children[1].attached, false);
33+
34+
root.offset = ViewportOffset.fixed(600);
35+
pumpFrame();
36+
expect(children[0].attached, false);
37+
expect(children[1].attached, true);
38+
39+
// Simulate double precision error.
40+
root.offset = ViewportOffset.fixed(1199.999999999998);
41+
pumpFrame();
42+
expect(children[1].attached, false);
43+
expect(children[2].attached, true);
44+
});
45+
}
46+
47+
class TestRenderSliverBoxChildManager extends RenderSliverBoxChildManager {
48+
TestRenderSliverBoxChildManager({
49+
this.children,
50+
});
51+
52+
RenderSliverMultiBoxAdaptor _renderObject;
53+
List<RenderBox> children;
54+
55+
RenderSliverFillViewport createRenderSliverFillViewport() {
56+
assert(_renderObject == null);
57+
_renderObject = RenderSliverFillViewport(
58+
childManager: this,
59+
);
60+
return _renderObject;
61+
}
62+
63+
int _currentlyUpdatingChildIndex;
64+
65+
@override
66+
void createChild(int index, { @required RenderBox after }) {
67+
if (index < 0 || index >= children.length)
68+
return;
69+
try {
70+
_currentlyUpdatingChildIndex = index;
71+
_renderObject.insert(children[index], after: after);
72+
} finally {
73+
_currentlyUpdatingChildIndex = null;
74+
}
75+
}
76+
77+
@override
78+
void removeChild(RenderBox child) {
79+
_renderObject.remove(child);
80+
}
81+
82+
@override
83+
double estimateMaxScrollOffset(
84+
SliverConstraints constraints, {
85+
int firstIndex,
86+
int lastIndex,
87+
double leadingScrollOffset,
88+
double trailingScrollOffset,
89+
}) {
90+
assert(lastIndex >= firstIndex);
91+
return children.length * (trailingScrollOffset - leadingScrollOffset) / (lastIndex - firstIndex + 1);
92+
}
93+
94+
@override
95+
int get childCount => children.length;
96+
97+
@override
98+
void didAdoptChild(RenderBox child) {
99+
assert(_currentlyUpdatingChildIndex != null);
100+
final SliverMultiBoxAdaptorParentData childParentData = child.parentData;
101+
childParentData.index = _currentlyUpdatingChildIndex;
102+
}
103+
104+
@override
105+
void setDidUnderflow(bool value) { }
106+
}

0 commit comments

Comments
 (0)