mirror of
https://github.com/flutter/flutter
synced 2024-08-25 02:46:31 +00:00
RenderViewport max layout cycles should depend on number of slivers (#144104)
Fixes https://github.com/flutter/flutter/issues/144102 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/wiki/Tree-hygiene#overview [Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene [test-exempt]: https://github.com/flutter/flutter/wiki/Tree-hygiene#tests [Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo [Features we expect every widget to implement]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/wiki/Chat [Data Driven Fixes]: https://github.com/flutter/flutter/wiki/Data-driven-Fixes
This commit is contained in:
parent
fd73a9a0c6
commit
19087442ce
|
@ -1385,7 +1385,7 @@ class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentDat
|
||||||
return constraints.biggest;
|
return constraints.biggest;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const int _maxLayoutCycles = 10;
|
static const int _maxLayoutCyclesPerChild = 10;
|
||||||
|
|
||||||
// Out-of-band data computed during layout.
|
// Out-of-band data computed during layout.
|
||||||
late double _minScrollExtent;
|
late double _minScrollExtent;
|
||||||
|
@ -1419,6 +1419,7 @@ class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentDat
|
||||||
};
|
};
|
||||||
|
|
||||||
final double centerOffsetAdjustment = center!.centerOffsetAdjustment;
|
final double centerOffsetAdjustment = center!.centerOffsetAdjustment;
|
||||||
|
final int maxLayoutCycles = _maxLayoutCyclesPerChild * childCount;
|
||||||
|
|
||||||
double correction;
|
double correction;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
@ -1435,9 +1436,9 @@ class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentDat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
count += 1;
|
count += 1;
|
||||||
} while (count < _maxLayoutCycles);
|
} while (count < maxLayoutCycles);
|
||||||
assert(() {
|
assert(() {
|
||||||
if (count >= _maxLayoutCycles) {
|
if (count >= maxLayoutCycles) {
|
||||||
assert(count != 1);
|
assert(count != 1);
|
||||||
throw FlutterError(
|
throw FlutterError(
|
||||||
'A RenderViewport exceeded its maximum number of layout cycles.\n'
|
'A RenderViewport exceeded its maximum number of layout cycles.\n'
|
||||||
|
|
|
@ -2370,4 +2370,85 @@ void main() {
|
||||||
);
|
);
|
||||||
errors.clear();
|
errors.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('RenderViewport maxLayoutCycles depends on the number of children',
|
||||||
|
(WidgetTester tester) async {
|
||||||
|
Future<void> expectFlutterError({
|
||||||
|
required Widget widget,
|
||||||
|
required WidgetTester tester,
|
||||||
|
}) async {
|
||||||
|
final List<FlutterErrorDetails> errors = <FlutterErrorDetails>[];
|
||||||
|
final FlutterExceptionHandler? oldHandler = FlutterError.onError;
|
||||||
|
FlutterError.onError = (FlutterErrorDetails error) => errors.add(error);
|
||||||
|
try {
|
||||||
|
await tester.pumpWidget(widget);
|
||||||
|
} finally {
|
||||||
|
FlutterError.onError = oldHandler;
|
||||||
|
}
|
||||||
|
expect(errors, isNotEmpty);
|
||||||
|
expect(errors.first.exception, isFlutterError);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildWidget({required int sliverCount, required int correctionsCount}) {
|
||||||
|
return Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: CustomScrollView(
|
||||||
|
slivers: List<Widget>.generate(
|
||||||
|
sliverCount,
|
||||||
|
(_) => _ScrollOffsetCorrectionSliver(correctionsCount: correctionsCount)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5 correction per child will pass.
|
||||||
|
await tester.pumpWidget(buildWidget(sliverCount: 30, correctionsCount: 5));
|
||||||
|
|
||||||
|
// 15 correction per child will throw exception.
|
||||||
|
await expectFlutterError(
|
||||||
|
widget: buildWidget(sliverCount: 1, correctionsCount: 15),
|
||||||
|
tester: tester,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple sliver that applies N scroll offset corrections.
|
||||||
|
class _RenderScrollOffsetCorrectionSliver extends RenderSliver {
|
||||||
|
int _correctionCount = 0;
|
||||||
|
@override
|
||||||
|
void performLayout() {
|
||||||
|
if (_correctionCount > 0) {
|
||||||
|
--_correctionCount;
|
||||||
|
geometry = const SliverGeometry(scrollOffsetCorrection: 1.0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const double extent = 5;
|
||||||
|
final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: extent);
|
||||||
|
final double cacheExtent = calculateCacheOffset(constraints, from: 0.0, to: extent);
|
||||||
|
|
||||||
|
geometry = SliverGeometry(
|
||||||
|
scrollExtent: extent,
|
||||||
|
paintExtent: paintedChildSize,
|
||||||
|
maxPaintExtent: extent,
|
||||||
|
cacheExtent: cacheExtent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScrollOffsetCorrectionSliver extends SingleChildRenderObjectWidget {
|
||||||
|
const _ScrollOffsetCorrectionSliver({required this.correctionsCount});
|
||||||
|
final int correctionsCount;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_RenderScrollOffsetCorrectionSliver createRenderObject(BuildContext context) {
|
||||||
|
final _RenderScrollOffsetCorrectionSliver sliver = _RenderScrollOffsetCorrectionSliver();
|
||||||
|
sliver._correctionCount = correctionsCount;
|
||||||
|
return sliver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void updateRenderObject(BuildContext context, covariant _RenderScrollOffsetCorrectionSliver renderObject) {
|
||||||
|
super.updateRenderObject(context, renderObject);
|
||||||
|
renderObject.markNeedsLayout();
|
||||||
|
renderObject._correctionCount = correctionsCount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue