mirror of
https://github.com/flutter/flutter
synced 2024-09-13 05:11:45 +00:00
Set cacheExtent for SliverFillRemaining widget (#143612)
When a Sliver with items is outside of the Viewport, but within the Viewport's `cacheExtent`, the framework should create SemanticNodes for the items even though they are out of view. However, for this to work, the Sliver's geometry must have a `cacheExtent` (how much space the sliver took up of the Viewport's `cacheExtent`) greater than 0, otherwise it is [excluded](f01ce9f4cb/packages/flutter/lib/src/rendering/viewport.dart (L311-L315)
).
`SliverFillRemaining` widgets that fall outside the viewport did not have this set and therefore were being excluded when SemanticNodes were created, even if they were within the Viewport's `cacheExtent`. This PR sets the `cacheExtent` for `SliverFillRemaining` widgets.
In addition, `RenderSliverFillRemainingWithScrollable` would get dropped from the semantic tree because it's child had a size of 0 when outside the remaining paint extent. To fix, we give the child a `maxExtent` of the sliver's `cacheExtent` if it's outside the remaining paint extent but within the viewport's cacheExtent.
Fixes https://github.com/flutter/flutter/issues/142065.
Definitions:
* `RenderViewport.cacheExtent`:
```dart
/// The viewport has an area before and after the visible area to cache items
/// that are about to become visible when the user scrolls.
///
/// Items that fall in this cache area are laid out even though they are not
/// (yet) visible on screen. The [cacheExtent] describes how many pixels
/// the cache area extends before the leading edge and after the trailing edge
/// of the viewport.
///
/// The total extent, which the viewport will try to cover with children, is
/// [cacheExtent] before the leading edge + extent of the main axis +
/// [cacheExtent] after the trailing edge.
///
/// The cache area is also used to implement implicit accessibility scrolling
/// on iOS: When the accessibility focus moves from an item in the visible
/// viewport to an invisible item in the cache area, the framework will bring
/// that item into view with an (implicit) scroll action.
```
* `SliverGeometry.cacheExtent`:
```dart
/// How many pixels the sliver has consumed in the
/// [SliverConstraints.remainingCacheExtent].
```
* `SliverContraints.remainingCacheExtent`:
```dart
/// Describes how much content the sliver should provide starting from the
/// [cacheOrigin].
///
/// Not all content in the [remainingCacheExtent] will be visible as some
/// of it might fall into the cache area of the viewport.
///
/// Each sliver should start laying out content at the [cacheOrigin] and
/// try to provide as much content as the [remainingCacheExtent] allows.
```
This commit is contained in:
parent
1ccad1a2a7
commit
825e901e00
|
@ -83,21 +83,36 @@ class RenderSliverFillRemainingWithScrollable extends RenderSliverSingleBoxAdapt
|
||||||
final SliverConstraints constraints = this.constraints;
|
final SliverConstraints constraints = this.constraints;
|
||||||
final double extent = constraints.remainingPaintExtent - math.min(constraints.overlap, 0.0);
|
final double extent = constraints.remainingPaintExtent - math.min(constraints.overlap, 0.0);
|
||||||
|
|
||||||
|
final double cacheExtent = calculateCacheOffset(
|
||||||
|
constraints,
|
||||||
|
from: 0.0,
|
||||||
|
to: constraints.viewportMainAxisExtent,
|
||||||
|
);
|
||||||
if (child != null) {
|
if (child != null) {
|
||||||
|
double maxExtent = extent;
|
||||||
|
|
||||||
|
// If sliver has no extent, but is within viewport's cacheExtent, use the
|
||||||
|
// sliver's cacheExtent as the maxExtent so that it does not get dropped
|
||||||
|
// from the semantic tree.
|
||||||
|
if (extent == 0 && cacheExtent > 0) {
|
||||||
|
maxExtent = cacheExtent;
|
||||||
|
}
|
||||||
child!.layout(constraints.asBoxConstraints(
|
child!.layout(constraints.asBoxConstraints(
|
||||||
minExtent: extent,
|
minExtent: extent,
|
||||||
maxExtent: extent,
|
maxExtent: maxExtent,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: extent);
|
final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: extent);
|
||||||
assert(paintedChildSize.isFinite);
|
assert(paintedChildSize.isFinite);
|
||||||
assert(paintedChildSize >= 0.0);
|
assert(paintedChildSize >= 0.0);
|
||||||
|
|
||||||
geometry = SliverGeometry(
|
geometry = SliverGeometry(
|
||||||
scrollExtent: constraints.viewportMainAxisExtent,
|
scrollExtent: constraints.viewportMainAxisExtent,
|
||||||
paintExtent: paintedChildSize,
|
paintExtent: paintedChildSize,
|
||||||
maxPaintExtent: paintedChildSize,
|
maxPaintExtent: paintedChildSize,
|
||||||
hasVisualOverflow: extent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
|
hasVisualOverflow: extent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
|
||||||
|
cacheExtent: cacheExtent,
|
||||||
);
|
);
|
||||||
if (child != null) {
|
if (child != null) {
|
||||||
setChildParentData(child!, constraints, geometry!);
|
setChildParentData(child!, constraints, geometry!);
|
||||||
|
@ -162,11 +177,14 @@ class RenderSliverFillRemaining extends RenderSliverSingleBoxAdapter {
|
||||||
final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: extent);
|
final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: extent);
|
||||||
assert(paintedChildSize.isFinite);
|
assert(paintedChildSize.isFinite);
|
||||||
assert(paintedChildSize >= 0.0);
|
assert(paintedChildSize >= 0.0);
|
||||||
|
|
||||||
|
final double cacheExtent = calculateCacheOffset(constraints, from: 0.0, to: extent);
|
||||||
geometry = SliverGeometry(
|
geometry = SliverGeometry(
|
||||||
scrollExtent: extent,
|
scrollExtent: extent,
|
||||||
paintExtent: paintedChildSize,
|
paintExtent: paintedChildSize,
|
||||||
maxPaintExtent: paintedChildSize,
|
maxPaintExtent: paintedChildSize,
|
||||||
hasVisualOverflow: extent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
|
hasVisualOverflow: extent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
|
||||||
|
cacheExtent: cacheExtent,
|
||||||
);
|
);
|
||||||
if (child != null) {
|
if (child != null) {
|
||||||
setChildParentData(child!, constraints, geometry!);
|
setChildParentData(child!, constraints, geometry!);
|
||||||
|
@ -235,11 +253,14 @@ class RenderSliverFillRemainingAndOverscroll extends RenderSliverSingleBoxAdapte
|
||||||
final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: extent);
|
final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: extent);
|
||||||
assert(paintedChildSize.isFinite);
|
assert(paintedChildSize.isFinite);
|
||||||
assert(paintedChildSize >= 0.0);
|
assert(paintedChildSize >= 0.0);
|
||||||
|
|
||||||
|
final double cacheExtent = calculateCacheOffset(constraints, from: 0.0, to: extent);
|
||||||
geometry = SliverGeometry(
|
geometry = SliverGeometry(
|
||||||
scrollExtent: extent,
|
scrollExtent: extent,
|
||||||
paintExtent: math.min(maxExtent, constraints.remainingPaintExtent),
|
paintExtent: math.min(maxExtent, constraints.remainingPaintExtent),
|
||||||
maxPaintExtent: maxExtent,
|
maxPaintExtent: maxExtent,
|
||||||
hasVisualOverflow: extent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
|
hasVisualOverflow: extent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
|
||||||
|
cacheExtent: cacheExtent,
|
||||||
);
|
);
|
||||||
if (child != null) {
|
if (child != null) {
|
||||||
setChildParentData(child!, constraints, geometry!);
|
setChildParentData(child!, constraints, geometry!);
|
||||||
|
|
|
@ -872,6 +872,914 @@ void main() {
|
||||||
visible: true,
|
visible: true,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('RenderSliverFillRemaining calculates correct geometry', () {
|
||||||
|
test('when initially in view', () {
|
||||||
|
// Viewport is 800x600
|
||||||
|
const double viewportHeight = 600;
|
||||||
|
const double viewportWidth = 800;
|
||||||
|
const double cacheExtent = 250.0;
|
||||||
|
const double beginningViewportCacheExtent = viewportHeight + cacheExtent;
|
||||||
|
const double firstSliverHeight = 400;
|
||||||
|
const double sliverFillRemainingChildHeight = 100.0;
|
||||||
|
|
||||||
|
final List<RenderSliver> slivers = <RenderSliver>[
|
||||||
|
RenderSliverToBoxAdapter(
|
||||||
|
child: RenderSizedBox(const Size(400.0, firstSliverHeight)),
|
||||||
|
),
|
||||||
|
RenderSliverFillRemaining(
|
||||||
|
child: RenderSizedBox(const Size(100.0, sliverFillRemainingChildHeight)),
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
final RenderViewport root = RenderViewport(
|
||||||
|
crossAxisDirection: AxisDirection.right,
|
||||||
|
offset: ViewportOffset.zero(),
|
||||||
|
cacheExtent: cacheExtent,
|
||||||
|
children: slivers,
|
||||||
|
);
|
||||||
|
layout(root);
|
||||||
|
|
||||||
|
final RenderSliver firstVisibleSliver = slivers[0];
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: firstVisibleSliver,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: viewportHeight,
|
||||||
|
remainingCacheExtent: beginningViewportCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: firstVisibleSliver,
|
||||||
|
paintExtent: firstSliverHeight,
|
||||||
|
cacheExtent: firstSliverHeight,
|
||||||
|
visible: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// With RenderSliverFillRemaining:
|
||||||
|
// * The child has a minExtent and maxExtent of the remaining space of the
|
||||||
|
// viewportMainAxisExtent or the height of the child - whichever is larger.
|
||||||
|
// * The sliver has a paintExtent of the child's minExtent/maxExtent or the
|
||||||
|
// remainingPaintExtent - whichever is smaller.
|
||||||
|
// * The sliver has a cacheExtent of the child's minExtent/maxExtent or the
|
||||||
|
// remainingCacheExtent - whichever is smaller.
|
||||||
|
final RenderSliverSingleBoxAdapter sliverFillRemaining = slivers[1] as RenderSliverSingleBoxAdapter;
|
||||||
|
const double extentOfChild = viewportHeight - firstSliverHeight;
|
||||||
|
double remainingPaintExtent = viewportHeight - firstSliverHeight;
|
||||||
|
double remainingCacheExtent = beginningViewportCacheExtent - firstSliverHeight;
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: remainingPaintExtent,
|
||||||
|
remainingCacheExtent: remainingCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverChildConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
maxHeight: extentOfChild,
|
||||||
|
minHeight: extentOfChild,
|
||||||
|
maxWidth: viewportWidth,
|
||||||
|
minWidth: viewportWidth,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
paintExtent: extentOfChild,
|
||||||
|
cacheExtent: extentOfChild,
|
||||||
|
visible: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Overscroll
|
||||||
|
const double scrollOffset = 50;
|
||||||
|
root.offset = ViewportOffset.fixed(scrollOffset);
|
||||||
|
pumpFrame();
|
||||||
|
remainingPaintExtent = viewportHeight - firstSliverHeight + scrollOffset;
|
||||||
|
remainingCacheExtent = beginningViewportCacheExtent - firstSliverHeight + scrollOffset;
|
||||||
|
|
||||||
|
// With RenderSliverFillRemaining, when you overscroll, the extent of the
|
||||||
|
// child does not change and therefore neither does paintExtent or cacheExtent.
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: remainingPaintExtent,
|
||||||
|
remainingCacheExtent: remainingCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverChildConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
maxHeight: extentOfChild,
|
||||||
|
minHeight: extentOfChild,
|
||||||
|
maxWidth: viewportWidth,
|
||||||
|
minWidth: viewportWidth,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
paintExtent: extentOfChild,
|
||||||
|
cacheExtent: extentOfChild,
|
||||||
|
visible: true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('when scrolled into view', () {
|
||||||
|
// Viewport is 800x600
|
||||||
|
const double viewportHeight = 600;
|
||||||
|
const double viewportWidth = 800;
|
||||||
|
const double cacheExtent = 250.0;
|
||||||
|
const double beginningViewportCacheExtent = viewportHeight + cacheExtent;
|
||||||
|
const double firstSliverHeight = beginningViewportCacheExtent;
|
||||||
|
const double sliverFillRemainingChildHeight = 100.0;
|
||||||
|
|
||||||
|
final List<RenderSliver> slivers = <RenderSliver>[
|
||||||
|
RenderSliverToBoxAdapter(
|
||||||
|
child: RenderSizedBox(const Size(400.0, firstSliverHeight)),
|
||||||
|
),
|
||||||
|
RenderSliverFillRemaining(
|
||||||
|
child: RenderSizedBox(const Size(100.0, sliverFillRemainingChildHeight)),
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
final RenderViewport root = RenderViewport(
|
||||||
|
crossAxisDirection: AxisDirection.right,
|
||||||
|
offset: ViewportOffset.zero(),
|
||||||
|
cacheExtent: cacheExtent,
|
||||||
|
children: slivers,
|
||||||
|
);
|
||||||
|
layout(root);
|
||||||
|
|
||||||
|
final RenderSliver firstVisibleSliver = slivers[0];
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: firstVisibleSliver,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: viewportHeight,
|
||||||
|
remainingCacheExtent: beginningViewportCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: firstVisibleSliver,
|
||||||
|
paintExtent: viewportHeight,
|
||||||
|
cacheExtent: firstSliverHeight,
|
||||||
|
visible: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// With RenderSliverFillRemaining:
|
||||||
|
// * The child has a minExtent and maxExtent of the remaining space of the
|
||||||
|
// viewportMainAxisExtent or the height of the child - whichever is larger.
|
||||||
|
// * The sliver has a paintExtent of the child's minExtent/maxExtent or the
|
||||||
|
// remainingPaintExtent - whichever is smaller.
|
||||||
|
// * The sliver has a cacheExtent of the child's minExtent/maxExtent or the
|
||||||
|
// remainingCacheExtent - whichever is smaller.
|
||||||
|
final RenderSliverSingleBoxAdapter sliverFillRemaining = slivers[1] as RenderSliverSingleBoxAdapter;
|
||||||
|
const double extentOfChild = sliverFillRemainingChildHeight;
|
||||||
|
double remainingPaintExtent = 0;
|
||||||
|
double remainingCacheExtent = 0;
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: remainingPaintExtent,
|
||||||
|
remainingCacheExtent: remainingCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverChildConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
maxHeight: extentOfChild,
|
||||||
|
minHeight: extentOfChild,
|
||||||
|
maxWidth: viewportWidth,
|
||||||
|
minWidth: viewportWidth,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
paintExtent: remainingPaintExtent,
|
||||||
|
cacheExtent: remainingCacheExtent,
|
||||||
|
visible: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Scroll so RenderSliverFillRemaining is not within viewport, but is
|
||||||
|
// within remainingCacheExtent.
|
||||||
|
root.offset = ViewportOffset.fixed(cacheExtent);
|
||||||
|
pumpFrame();
|
||||||
|
remainingPaintExtent = 0;
|
||||||
|
remainingCacheExtent = cacheExtent;
|
||||||
|
|
||||||
|
// When within the remainingCacheExtent, the sliver will have a cacheExtent
|
||||||
|
// of the child's extent.
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: remainingPaintExtent,
|
||||||
|
remainingCacheExtent: remainingCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverChildConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
maxHeight: extentOfChild,
|
||||||
|
minHeight: extentOfChild,
|
||||||
|
maxWidth: viewportWidth,
|
||||||
|
minWidth: viewportWidth,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
paintExtent: remainingPaintExtent,
|
||||||
|
cacheExtent: extentOfChild,
|
||||||
|
visible: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Scroll so RenderSliverFillRemaining is partially within viewport.
|
||||||
|
root.offset = ViewportOffset.fixed(cacheExtent + 50);
|
||||||
|
pumpFrame();
|
||||||
|
remainingPaintExtent = 50;
|
||||||
|
remainingCacheExtent = cacheExtent + 50;
|
||||||
|
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: remainingPaintExtent,
|
||||||
|
remainingCacheExtent: remainingCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverChildConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
maxHeight: extentOfChild,
|
||||||
|
minHeight: extentOfChild,
|
||||||
|
maxWidth: viewportWidth,
|
||||||
|
minWidth: viewportWidth,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
paintExtent: remainingPaintExtent,
|
||||||
|
cacheExtent: extentOfChild,
|
||||||
|
visible: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Scroll so RenderSliverFillRemaining is completely within viewport.
|
||||||
|
root.offset = ViewportOffset.fixed(cacheExtent + sliverFillRemainingChildHeight);
|
||||||
|
pumpFrame();
|
||||||
|
remainingPaintExtent = sliverFillRemainingChildHeight;
|
||||||
|
remainingCacheExtent = cacheExtent + sliverFillRemainingChildHeight;
|
||||||
|
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: remainingPaintExtent,
|
||||||
|
remainingCacheExtent: remainingCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverChildConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
maxHeight: extentOfChild,
|
||||||
|
minHeight: extentOfChild,
|
||||||
|
maxWidth: viewportWidth,
|
||||||
|
minWidth: viewportWidth,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
paintExtent: extentOfChild,
|
||||||
|
cacheExtent: extentOfChild,
|
||||||
|
visible: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Overscroll
|
||||||
|
root.offset = ViewportOffset.fixed(cacheExtent + sliverFillRemainingChildHeight + 50);
|
||||||
|
pumpFrame();
|
||||||
|
remainingPaintExtent = sliverFillRemainingChildHeight + 50;
|
||||||
|
remainingCacheExtent = cacheExtent + sliverFillRemainingChildHeight + 50;
|
||||||
|
|
||||||
|
// With RenderSliverFillRemaining, when you overscroll, the extent of the
|
||||||
|
// child does not change and therefore neither does paintExtent or cacheExtent.
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: remainingPaintExtent,
|
||||||
|
remainingCacheExtent: remainingCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverChildConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
maxHeight: extentOfChild,
|
||||||
|
minHeight: extentOfChild,
|
||||||
|
maxWidth: viewportWidth,
|
||||||
|
minWidth: viewportWidth,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
paintExtent: extentOfChild,
|
||||||
|
cacheExtent: extentOfChild,
|
||||||
|
visible: true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('RenderSliverFillRemainingAndOverscroll calculates correct geometry', () {
|
||||||
|
test('when initially in view', () {
|
||||||
|
// Viewport is 800x600
|
||||||
|
const double viewportHeight = 600;
|
||||||
|
const double viewportWidth = 800;
|
||||||
|
const double cacheExtent = 250.0;
|
||||||
|
const double beginningViewportCacheExtent = viewportHeight + cacheExtent;
|
||||||
|
const double firstSliverHeight = 400;
|
||||||
|
const double sliverFillRemainingChildHeight = 100.0;
|
||||||
|
|
||||||
|
final List<RenderSliver> slivers = <RenderSliver>[
|
||||||
|
RenderSliverToBoxAdapter(
|
||||||
|
child: RenderSizedBox(const Size(400.0, firstSliverHeight)),
|
||||||
|
),
|
||||||
|
RenderSliverFillRemainingAndOverscroll(
|
||||||
|
child: RenderSizedBox(const Size(100.0, sliverFillRemainingChildHeight)),
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
final RenderViewport root = RenderViewport(
|
||||||
|
crossAxisDirection: AxisDirection.right,
|
||||||
|
offset: ViewportOffset.zero(),
|
||||||
|
cacheExtent: cacheExtent,
|
||||||
|
children: slivers,
|
||||||
|
);
|
||||||
|
layout(root);
|
||||||
|
|
||||||
|
final RenderSliver firstVisibleSliver = slivers[0];
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: firstVisibleSliver,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: viewportHeight,
|
||||||
|
remainingCacheExtent: beginningViewportCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: firstVisibleSliver,
|
||||||
|
paintExtent: firstSliverHeight,
|
||||||
|
cacheExtent: firstSliverHeight,
|
||||||
|
visible: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// With RenderSliverFillRemainingAndOverscroll:
|
||||||
|
// * The child has a minExtent of the remaining space of the viewportMainAxisExtent
|
||||||
|
// or the height of the child - whichever is larger.
|
||||||
|
// * The child has a maxExtent of the remaining space of the viewportMainAxisExtent,
|
||||||
|
// the height of the child, or the remainingPaintExtent - whichever is larger.
|
||||||
|
// * The sliver has a paintExtent of the child's maxExtent or the
|
||||||
|
// remainingPaintExtent - whichever is smaller.
|
||||||
|
// * The sliver has a cacheExtent of the child's minExtent or the
|
||||||
|
// remainingCacheExtent - whichever is smaller.
|
||||||
|
final RenderSliverSingleBoxAdapter sliverFillRemaining = slivers[1] as RenderSliverSingleBoxAdapter;
|
||||||
|
const double minExtentOfChild = viewportHeight - firstSliverHeight;
|
||||||
|
double maxExtentOfChild = viewportHeight - firstSliverHeight;
|
||||||
|
double remainingPaintExtent = viewportHeight - firstSliverHeight;
|
||||||
|
double remainingCacheExtent = beginningViewportCacheExtent - firstSliverHeight;
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: remainingPaintExtent,
|
||||||
|
remainingCacheExtent: remainingCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverChildConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
maxHeight: maxExtentOfChild,
|
||||||
|
minHeight: minExtentOfChild,
|
||||||
|
maxWidth: viewportWidth,
|
||||||
|
minWidth: viewportWidth,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
paintExtent: maxExtentOfChild,
|
||||||
|
cacheExtent: minExtentOfChild,
|
||||||
|
visible: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Overscroll
|
||||||
|
const double scrollOffset = 50;
|
||||||
|
root.offset = ViewportOffset.fixed(scrollOffset);
|
||||||
|
pumpFrame();
|
||||||
|
remainingPaintExtent = viewportHeight - firstSliverHeight + scrollOffset;
|
||||||
|
remainingCacheExtent = beginningViewportCacheExtent - firstSliverHeight + scrollOffset;
|
||||||
|
|
||||||
|
// When you overscroll, the child's maxExtent is the
|
||||||
|
// remainingPaintExtent, since it's the higher value.
|
||||||
|
maxExtentOfChild = remainingPaintExtent;
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: remainingPaintExtent,
|
||||||
|
remainingCacheExtent: remainingCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverChildConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
maxHeight: maxExtentOfChild,
|
||||||
|
minHeight: minExtentOfChild,
|
||||||
|
maxWidth: viewportWidth,
|
||||||
|
minWidth: viewportWidth,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
paintExtent: maxExtentOfChild,
|
||||||
|
cacheExtent: minExtentOfChild,
|
||||||
|
visible: true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('when scrolled into view', () {
|
||||||
|
// Viewport is 800x600
|
||||||
|
const double viewportHeight = 600;
|
||||||
|
const double viewportWidth = 800;
|
||||||
|
const double cacheExtent = 250.0;
|
||||||
|
const double beginningViewportCacheExtent = viewportHeight + cacheExtent;
|
||||||
|
const double firstSliverHeight = beginningViewportCacheExtent;
|
||||||
|
const double sliverFillRemainingChildHeight = 100.0;
|
||||||
|
|
||||||
|
final List<RenderSliver> slivers = <RenderSliver>[
|
||||||
|
RenderSliverToBoxAdapter(
|
||||||
|
child: RenderSizedBox(const Size(400.0, firstSliverHeight)),
|
||||||
|
),
|
||||||
|
RenderSliverFillRemainingAndOverscroll(
|
||||||
|
child: RenderSizedBox(const Size(100.0, sliverFillRemainingChildHeight)),
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
final RenderViewport root = RenderViewport(
|
||||||
|
crossAxisDirection: AxisDirection.right,
|
||||||
|
offset: ViewportOffset.zero(),
|
||||||
|
cacheExtent: cacheExtent,
|
||||||
|
children: slivers,
|
||||||
|
);
|
||||||
|
layout(root);
|
||||||
|
|
||||||
|
final RenderSliver firstVisibleSliver = slivers[0];
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: firstVisibleSliver,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: viewportHeight,
|
||||||
|
remainingCacheExtent: beginningViewportCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: firstVisibleSliver,
|
||||||
|
paintExtent: viewportHeight,
|
||||||
|
cacheExtent: firstSliverHeight,
|
||||||
|
visible: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// With RenderSliverFillRemainingAndOverscroll:
|
||||||
|
// * The child has a minExtent of the remaining space of the viewportMainAxisExtent
|
||||||
|
// or the height of the child - whichever is larger.
|
||||||
|
// * The child has a maxExtent of the remaining space of the viewportMainAxisExtent,
|
||||||
|
// the height of the child, or the remainingPaintExtent - whichever is larger.
|
||||||
|
// * The sliver has a paintExtent of the child's maxExtent or the
|
||||||
|
// remainingPaintExtent - whichever is smaller.
|
||||||
|
// * The sliver has a cacheExtent of the child's minExtent or the
|
||||||
|
// remainingCacheExtent - whichever is smaller.
|
||||||
|
final RenderSliverSingleBoxAdapter sliverFillRemaining = slivers[1] as RenderSliverSingleBoxAdapter;
|
||||||
|
const double minExtentOfChild = sliverFillRemainingChildHeight;
|
||||||
|
double maxExtentOfChild = sliverFillRemainingChildHeight;
|
||||||
|
double remainingPaintExtent = 0;
|
||||||
|
double remainingCacheExtent = 0;
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: remainingPaintExtent,
|
||||||
|
remainingCacheExtent: remainingCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverChildConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
maxHeight: maxExtentOfChild,
|
||||||
|
minHeight: minExtentOfChild,
|
||||||
|
maxWidth: viewportWidth,
|
||||||
|
minWidth: viewportWidth,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
paintExtent: remainingPaintExtent,
|
||||||
|
cacheExtent: remainingCacheExtent,
|
||||||
|
visible: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Scroll so RenderSliverFillRemainingAndOverscroll is not within viewport,
|
||||||
|
// but is within remainingCacheExtent.
|
||||||
|
root.offset = ViewportOffset.fixed(cacheExtent);
|
||||||
|
pumpFrame();
|
||||||
|
remainingPaintExtent = 0;
|
||||||
|
remainingCacheExtent = cacheExtent;
|
||||||
|
|
||||||
|
// When within the remainingCacheExtent, the sliver will have a cacheExtent
|
||||||
|
// of the child's minExtent.
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: remainingPaintExtent,
|
||||||
|
remainingCacheExtent: remainingCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverChildConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
maxHeight: maxExtentOfChild,
|
||||||
|
minHeight: minExtentOfChild,
|
||||||
|
maxWidth: viewportWidth,
|
||||||
|
minWidth: viewportWidth,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
paintExtent: remainingPaintExtent,
|
||||||
|
cacheExtent: minExtentOfChild,
|
||||||
|
visible: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Scroll so RenderSliverFillRemainingAndOverscroll is partially within
|
||||||
|
// the viewport.
|
||||||
|
root.offset = ViewportOffset.fixed(cacheExtent + 50);
|
||||||
|
pumpFrame();
|
||||||
|
remainingPaintExtent = 50;
|
||||||
|
remainingCacheExtent = cacheExtent + 50;
|
||||||
|
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: remainingPaintExtent,
|
||||||
|
remainingCacheExtent: remainingCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverChildConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
maxHeight: maxExtentOfChild,
|
||||||
|
minHeight: minExtentOfChild,
|
||||||
|
maxWidth: viewportWidth,
|
||||||
|
minWidth: viewportWidth,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
paintExtent: remainingPaintExtent,
|
||||||
|
cacheExtent: minExtentOfChild,
|
||||||
|
visible: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Scroll so RenderSliverFillRemainingAndOverscroll is completely within
|
||||||
|
// the viewport.
|
||||||
|
root.offset = ViewportOffset.fixed(cacheExtent + sliverFillRemainingChildHeight);
|
||||||
|
pumpFrame();
|
||||||
|
remainingPaintExtent = sliverFillRemainingChildHeight;
|
||||||
|
remainingCacheExtent = cacheExtent + sliverFillRemainingChildHeight;
|
||||||
|
|
||||||
|
// When completely in view, the slivers's paintExtent is the child's
|
||||||
|
// maxExtentOfChild, since it's the smaller value.
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: remainingPaintExtent,
|
||||||
|
remainingCacheExtent: remainingCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverChildConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
maxHeight: maxExtentOfChild,
|
||||||
|
minHeight: minExtentOfChild,
|
||||||
|
maxWidth: viewportWidth,
|
||||||
|
minWidth: viewportWidth,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
paintExtent: maxExtentOfChild,
|
||||||
|
cacheExtent: minExtentOfChild,
|
||||||
|
visible: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Overscroll
|
||||||
|
root.offset = ViewportOffset.fixed(cacheExtent + sliverFillRemainingChildHeight + 50);
|
||||||
|
pumpFrame();
|
||||||
|
remainingPaintExtent = sliverFillRemainingChildHeight + 50;
|
||||||
|
remainingCacheExtent = cacheExtent + sliverFillRemainingChildHeight + 50;
|
||||||
|
|
||||||
|
// When you overscroll, the child's maxExtent is the remainingPaintExtent,
|
||||||
|
// since it's the higher value.
|
||||||
|
maxExtentOfChild = remainingPaintExtent;
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: remainingPaintExtent,
|
||||||
|
remainingCacheExtent: remainingCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverChildConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
maxHeight: maxExtentOfChild,
|
||||||
|
minHeight: minExtentOfChild,
|
||||||
|
maxWidth: viewportWidth,
|
||||||
|
minWidth: viewportWidth,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
paintExtent: maxExtentOfChild,
|
||||||
|
cacheExtent: minExtentOfChild,
|
||||||
|
visible: true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('RenderSliverFillRemainingWithScrollable calculates correct geometry', () {
|
||||||
|
test('when initially in view', () {
|
||||||
|
// Viewport is 800x600
|
||||||
|
const double viewportHeight = 600;
|
||||||
|
const double viewportWidth = 800;
|
||||||
|
const double cacheExtent = 250.0;
|
||||||
|
const double beginningViewportCacheExtent = viewportHeight + cacheExtent;
|
||||||
|
const double firstSliverHeight = 400;
|
||||||
|
const double sliverFillRemainingChildHeight = 100.0;
|
||||||
|
|
||||||
|
final List<RenderSliver> slivers = <RenderSliver>[
|
||||||
|
RenderSliverToBoxAdapter(
|
||||||
|
child: RenderSizedBox(const Size(400.0, firstSliverHeight)),
|
||||||
|
),
|
||||||
|
RenderSliverFillRemainingWithScrollable(
|
||||||
|
child: RenderSizedBox(const Size(100.0, sliverFillRemainingChildHeight)),
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
final RenderViewport root = RenderViewport(
|
||||||
|
crossAxisDirection: AxisDirection.right,
|
||||||
|
offset: ViewportOffset.zero(),
|
||||||
|
cacheExtent: cacheExtent,
|
||||||
|
children: slivers,
|
||||||
|
);
|
||||||
|
layout(root);
|
||||||
|
|
||||||
|
final RenderSliver firstVisibleSliver = slivers[0];
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: firstVisibleSliver,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: viewportHeight,
|
||||||
|
remainingCacheExtent: beginningViewportCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: firstVisibleSliver,
|
||||||
|
paintExtent: firstSliverHeight,
|
||||||
|
cacheExtent: firstSliverHeight,
|
||||||
|
visible: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// With RenderSliverFillRemainingWithScrollable:
|
||||||
|
// * The child has a minExtent of the remainingPaintExtent.
|
||||||
|
// * If not within the viewport but within the cacheExtent, the child has
|
||||||
|
// a maxExtent of the sliver's cacheExtent. Otherwise, the child has a
|
||||||
|
// maxExtent of the remainingPaintExtent
|
||||||
|
// * The sliver has a paintExtent of the child's minExtent or the
|
||||||
|
// remainingPaintExtent - whichever is smaller.
|
||||||
|
// * The sliver has a cacheExtent of either the viewportMainAxisExtent or
|
||||||
|
// the remainingCacheExtent - whichever is smaller.
|
||||||
|
final RenderSliverSingleBoxAdapter sliverFillRemaining = slivers[1] as RenderSliverSingleBoxAdapter;
|
||||||
|
double remainingPaintExtent = viewportHeight - firstSliverHeight;
|
||||||
|
double remainingCacheExtent = beginningViewportCacheExtent - firstSliverHeight;
|
||||||
|
double minExtentOfChild = remainingPaintExtent;
|
||||||
|
double maxExtentOfChild = remainingPaintExtent;
|
||||||
|
|
||||||
|
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: remainingPaintExtent,
|
||||||
|
remainingCacheExtent: remainingCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverChildConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
maxHeight: maxExtentOfChild,
|
||||||
|
minHeight: minExtentOfChild,
|
||||||
|
maxWidth: viewportWidth,
|
||||||
|
minWidth: viewportWidth,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
paintExtent: minExtentOfChild,
|
||||||
|
cacheExtent: remainingCacheExtent,
|
||||||
|
visible: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Overscroll so first sliver is partially out of view.
|
||||||
|
root.offset = ViewportOffset.fixed(50);
|
||||||
|
pumpFrame();
|
||||||
|
remainingPaintExtent = viewportHeight - firstSliverHeight + 50;
|
||||||
|
remainingCacheExtent = beginningViewportCacheExtent - firstSliverHeight + 50;
|
||||||
|
minExtentOfChild = remainingPaintExtent;
|
||||||
|
maxExtentOfChild = remainingPaintExtent;
|
||||||
|
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: remainingPaintExtent,
|
||||||
|
remainingCacheExtent: remainingCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverChildConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
maxHeight: maxExtentOfChild,
|
||||||
|
minHeight: minExtentOfChild,
|
||||||
|
maxWidth: viewportWidth,
|
||||||
|
minWidth: viewportWidth,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
paintExtent: minExtentOfChild,
|
||||||
|
cacheExtent: remainingCacheExtent,
|
||||||
|
visible: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Overscroll so only RenderSliverFillRemainingWithScrollable is visible.
|
||||||
|
root.offset = ViewportOffset.fixed(firstSliverHeight);
|
||||||
|
pumpFrame();
|
||||||
|
remainingPaintExtent = viewportHeight;
|
||||||
|
remainingCacheExtent = beginningViewportCacheExtent;
|
||||||
|
minExtentOfChild = remainingPaintExtent;
|
||||||
|
maxExtentOfChild = remainingPaintExtent;
|
||||||
|
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: remainingPaintExtent,
|
||||||
|
remainingCacheExtent: remainingCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverChildConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
maxHeight: maxExtentOfChild,
|
||||||
|
minHeight: minExtentOfChild,
|
||||||
|
maxWidth: viewportWidth,
|
||||||
|
minWidth: viewportWidth,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
paintExtent: minExtentOfChild,
|
||||||
|
cacheExtent: viewportHeight,
|
||||||
|
visible: true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('when scrolled into view', () {
|
||||||
|
// Viewport is 800x600
|
||||||
|
const double viewportHeight = 600;
|
||||||
|
const double viewportWidth = 800;
|
||||||
|
const double cacheExtent = 250.0;
|
||||||
|
const double beginningViewportCacheExtent = viewportHeight + cacheExtent;
|
||||||
|
const double firstSliverHeight = beginningViewportCacheExtent;
|
||||||
|
const double sliverFillRemainingChildHeight = 100.0;
|
||||||
|
|
||||||
|
final List<RenderSliver> slivers = <RenderSliver>[
|
||||||
|
RenderSliverToBoxAdapter(
|
||||||
|
child: RenderSizedBox(const Size(400.0, firstSliverHeight)),
|
||||||
|
),
|
||||||
|
RenderSliverFillRemainingWithScrollable(
|
||||||
|
child: RenderSizedBox(const Size(100.0, sliverFillRemainingChildHeight)),
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
final RenderViewport root = RenderViewport(
|
||||||
|
crossAxisDirection: AxisDirection.right,
|
||||||
|
offset: ViewportOffset.zero(),
|
||||||
|
cacheExtent: cacheExtent,
|
||||||
|
children: slivers,
|
||||||
|
);
|
||||||
|
layout(root);
|
||||||
|
|
||||||
|
final RenderSliver firstVisibleSliver = slivers[0];
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: firstVisibleSliver,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: viewportHeight,
|
||||||
|
remainingCacheExtent: beginningViewportCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: firstVisibleSliver,
|
||||||
|
paintExtent: viewportHeight,
|
||||||
|
cacheExtent: firstSliverHeight,
|
||||||
|
visible: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// With RenderSliverFillRemainingWithScrollable:
|
||||||
|
// * The child has a minExtent of the remainingPaintExtent.
|
||||||
|
// * If not within the viewport but within the cacheExtent, the child has
|
||||||
|
// a maxExtent of the sliver's cacheExtent. Otherwise, the child has a
|
||||||
|
// maxExtent of the remainingPaintExtent
|
||||||
|
// * The sliver has a paintExtent of the child's minExtent or the
|
||||||
|
// remainingPaintExtent - whichever is smaller.
|
||||||
|
// * The sliver has a cacheExtent of either the viewportMainAxisExtent or
|
||||||
|
// the remainingCacheExtent - whichever is smaller.
|
||||||
|
final RenderSliverSingleBoxAdapter sliverFillRemaining = slivers[1] as RenderSliverSingleBoxAdapter;
|
||||||
|
double remainingPaintExtent = 0;
|
||||||
|
double remainingCacheExtent = 0;
|
||||||
|
double minExtentOfChild = remainingPaintExtent;
|
||||||
|
double maxExtentOfChild = remainingPaintExtent;
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: remainingPaintExtent,
|
||||||
|
remainingCacheExtent: remainingCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverChildConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
maxHeight: maxExtentOfChild,
|
||||||
|
minHeight: minExtentOfChild,
|
||||||
|
maxWidth: viewportWidth,
|
||||||
|
minWidth: viewportWidth,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
paintExtent: minExtentOfChild,
|
||||||
|
cacheExtent: remainingCacheExtent,
|
||||||
|
visible: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Scroll so RenderSliverFillRemainingWithScrollable is not within
|
||||||
|
// viewport, but is within remainingCacheExtent.
|
||||||
|
root.offset = ViewportOffset.fixed(cacheExtent);
|
||||||
|
pumpFrame();
|
||||||
|
remainingPaintExtent = 0;
|
||||||
|
remainingCacheExtent = cacheExtent;
|
||||||
|
minExtentOfChild = remainingPaintExtent;
|
||||||
|
// When RenderSliverFillRemainingWithScrollable is completely outside the
|
||||||
|
// viewport, but is within the remainingCacheExtent, the child has a
|
||||||
|
// maxExtent of the slivers's cacheExtent.
|
||||||
|
maxExtentOfChild = remainingCacheExtent;
|
||||||
|
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: remainingPaintExtent,
|
||||||
|
remainingCacheExtent: remainingCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverChildConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
maxHeight: maxExtentOfChild,
|
||||||
|
minHeight: minExtentOfChild,
|
||||||
|
maxWidth: viewportWidth,
|
||||||
|
minWidth: viewportWidth,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
paintExtent: minExtentOfChild,
|
||||||
|
cacheExtent: remainingCacheExtent,
|
||||||
|
visible: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Scroll so RenderSliverFillRemainingWithScrollable is partially within
|
||||||
|
// viewport.
|
||||||
|
root.offset = ViewportOffset.fixed(cacheExtent + 50);
|
||||||
|
pumpFrame();
|
||||||
|
remainingPaintExtent = 50;
|
||||||
|
remainingCacheExtent = cacheExtent + 50;
|
||||||
|
minExtentOfChild = remainingPaintExtent;
|
||||||
|
maxExtentOfChild = remainingPaintExtent;
|
||||||
|
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: remainingPaintExtent,
|
||||||
|
remainingCacheExtent: remainingCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverChildConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
maxHeight: maxExtentOfChild,
|
||||||
|
minHeight: minExtentOfChild,
|
||||||
|
maxWidth: viewportWidth,
|
||||||
|
minWidth: viewportWidth,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
paintExtent: minExtentOfChild,
|
||||||
|
cacheExtent: remainingCacheExtent,
|
||||||
|
visible: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Scroll so RenderSliverFillRemainingWithScrollable takes the entire
|
||||||
|
// viewport.
|
||||||
|
root.offset = ViewportOffset.fixed(firstSliverHeight);
|
||||||
|
pumpFrame();
|
||||||
|
remainingPaintExtent = viewportHeight;
|
||||||
|
remainingCacheExtent = beginningViewportCacheExtent;
|
||||||
|
minExtentOfChild = remainingPaintExtent;
|
||||||
|
maxExtentOfChild = remainingPaintExtent;
|
||||||
|
|
||||||
|
expectSliverConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
cacheOrigin: 0.0,
|
||||||
|
remainingPaintExtent: remainingPaintExtent,
|
||||||
|
remainingCacheExtent: remainingCacheExtent,
|
||||||
|
scrollOffset: 0.0,
|
||||||
|
);
|
||||||
|
expectSliverChildConstraints(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
maxHeight: maxExtentOfChild,
|
||||||
|
minHeight: minExtentOfChild,
|
||||||
|
maxWidth: viewportWidth,
|
||||||
|
minWidth: viewportWidth,
|
||||||
|
);
|
||||||
|
expectSliverGeometry(
|
||||||
|
sliver: sliverFillRemaining,
|
||||||
|
paintExtent: minExtentOfChild,
|
||||||
|
cacheExtent: viewportHeight,
|
||||||
|
visible: true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void expectSliverConstraints({
|
void expectSliverConstraints({
|
||||||
|
@ -898,6 +1806,19 @@ void expectSliverGeometry({
|
||||||
expect(sliver.geometry!.visible, visible, reason: 'visible');
|
expect(sliver.geometry!.visible, visible, reason: 'visible');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void expectSliverChildConstraints({
|
||||||
|
required RenderSliverSingleBoxAdapter sliver,
|
||||||
|
required double maxWidth,
|
||||||
|
required double maxHeight,
|
||||||
|
required double minWidth,
|
||||||
|
required double minHeight,
|
||||||
|
}) {
|
||||||
|
expect(sliver.child!.constraints.maxWidth, maxWidth, reason: 'maxWidth');
|
||||||
|
expect(sliver.child!.constraints.maxHeight, maxHeight, reason: 'maxHeight');
|
||||||
|
expect(sliver.child!.constraints.minWidth, minWidth, reason: 'minWidth');
|
||||||
|
expect(sliver.child!.constraints.minHeight, minHeight, reason: 'minHeight');
|
||||||
|
}
|
||||||
|
|
||||||
class TestRenderSliverBoxChildManager extends RenderSliverBoxChildManager {
|
class TestRenderSliverBoxChildManager extends RenderSliverBoxChildManager {
|
||||||
TestRenderSliverBoxChildManager({
|
TestRenderSliverBoxChildManager({
|
||||||
required this.children,
|
required this.children,
|
||||||
|
|
|
@ -139,6 +139,132 @@ void main() {
|
||||||
expect(controller.offset, 150.0);
|
expect(controller.offset, 150.0);
|
||||||
expect(find.byType(Container), findsOneWidget);
|
expect(find.byType(Container), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('has correct semantics when', () {
|
||||||
|
testWidgets('within viewport', (WidgetTester tester) async {
|
||||||
|
// Viewport is 800x600
|
||||||
|
const double viewportHeight = 600;
|
||||||
|
const double cacheExtent = 250;
|
||||||
|
final SemanticsHandle handle = tester.ensureSemantics();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: CustomScrollView(
|
||||||
|
cacheExtent: cacheExtent,
|
||||||
|
slivers: <Widget>[
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.amber,
|
||||||
|
height: viewportHeight - 100,
|
||||||
|
width: 150,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// This sliver is within viewport
|
||||||
|
const SliverFillRemaining(
|
||||||
|
child: SizedBox(
|
||||||
|
height: 100,
|
||||||
|
child: Text('Text in SliverFillRemaining'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// When SliverFillRemaining is within the viewport, semantic nodes for
|
||||||
|
// it are created.
|
||||||
|
final SemanticsFinder textInSliverFillRemaining = find.semantics.byLabel(
|
||||||
|
'Text in SliverFillRemaining',
|
||||||
|
);
|
||||||
|
expect(textInSliverFillRemaining, findsOne);
|
||||||
|
expect(textInSliverFillRemaining, containsSemantics(isHidden: false));
|
||||||
|
handle.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('outside of viewport but within cache extent', (WidgetTester tester) async {
|
||||||
|
// Viewport is 800x600
|
||||||
|
const double viewportHeight = 600;
|
||||||
|
const double cacheExtent = 250;
|
||||||
|
final SemanticsHandle handle = tester.ensureSemantics();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: CustomScrollView(
|
||||||
|
cacheExtent: cacheExtent,
|
||||||
|
slivers: <Widget>[
|
||||||
|
// This sliver takes up entire viewport and leaves 250 remaining cacheExtent
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.amber,
|
||||||
|
height: viewportHeight,
|
||||||
|
width: 150,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// This sliver is not within viewport but is within remaining cacheExtent
|
||||||
|
const SliverFillRemaining(
|
||||||
|
child: SizedBox(
|
||||||
|
height: 100,
|
||||||
|
child: Text('Text in SliverFillRemaining'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// When SliverFillRemaining is not in viewport, but is within
|
||||||
|
// cacheExtent, hidden semantic nodes for it are created.
|
||||||
|
final SemanticsFinder textInSliverFillRemaining = find.semantics.byLabel(
|
||||||
|
'Text in SliverFillRemaining',
|
||||||
|
);
|
||||||
|
expect(textInSliverFillRemaining, findsOne);
|
||||||
|
expect(textInSliverFillRemaining, containsSemantics(isHidden: true));
|
||||||
|
handle.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('outside of viewport and not within cache extent', (WidgetTester tester) async {
|
||||||
|
// Viewport is 800x600
|
||||||
|
const double viewportHeight = 600;
|
||||||
|
const double cacheExtent = 250;
|
||||||
|
final SemanticsHandle handle = tester.ensureSemantics();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: CustomScrollView(
|
||||||
|
cacheExtent: cacheExtent,
|
||||||
|
slivers: <Widget>[
|
||||||
|
// This sliver takes up entire viewport and leaves 0 remaining cacheExtent
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.amber,
|
||||||
|
height: viewportHeight + cacheExtent,
|
||||||
|
width: 150,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// This sliver is not within viewport and not within remaining cacheExtent
|
||||||
|
const SliverFillRemaining(
|
||||||
|
child: SizedBox(
|
||||||
|
height: 100,
|
||||||
|
child: Text('Text in SliverFillRemaining'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// When SliverFillRemaining is not in viewport and not within
|
||||||
|
// cacheExtent, semantic nodes are not created.
|
||||||
|
final SemanticsFinder textInSliverFillRemaining = find.semantics.byLabel(
|
||||||
|
'Text in SliverFillRemaining',
|
||||||
|
);
|
||||||
|
expect(textInSliverFillRemaining, findsNothing);
|
||||||
|
handle.dispose();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
group('hasScrollBody: false', () {
|
group('hasScrollBody: false', () {
|
||||||
|
@ -357,6 +483,135 @@ void main() {
|
||||||
expect(tester.getCenter(button).dx, equals(400.0));
|
expect(tester.getCenter(button).dx, equals(400.0));
|
||||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
||||||
|
|
||||||
|
group('has correct semantics when', () {
|
||||||
|
testWidgets('within viewport', (WidgetTester tester) async {
|
||||||
|
// Viewport is 800x600
|
||||||
|
const double viewportHeight = 600;
|
||||||
|
const double cacheExtent = 250;
|
||||||
|
final SemanticsHandle handle = tester.ensureSemantics();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: CustomScrollView(
|
||||||
|
cacheExtent: cacheExtent,
|
||||||
|
slivers: <Widget>[
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.amber,
|
||||||
|
height: viewportHeight - 100,
|
||||||
|
width: 150,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// This sliver is within viewport
|
||||||
|
const SliverFillRemaining(
|
||||||
|
hasScrollBody: false,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 100,
|
||||||
|
child: Text('Text in SliverFillRemaining'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// When SliverFillRemaining is within the viewport, semantic nodes for
|
||||||
|
// it are created.
|
||||||
|
final SemanticsFinder textInSliverFillRemaining = find.semantics.byLabel(
|
||||||
|
'Text in SliverFillRemaining',
|
||||||
|
);
|
||||||
|
expect(textInSliverFillRemaining, findsOne);
|
||||||
|
expect(textInSliverFillRemaining, containsSemantics(isHidden: false));
|
||||||
|
handle.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('outside of viewport but within cache extent', (WidgetTester tester) async {
|
||||||
|
// Viewport is 800x600
|
||||||
|
const double viewportHeight = 600;
|
||||||
|
const double cacheExtent = 250;
|
||||||
|
final SemanticsHandle handle = tester.ensureSemantics();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: CustomScrollView(
|
||||||
|
cacheExtent: cacheExtent,
|
||||||
|
slivers: <Widget>[
|
||||||
|
// This sliver takes up entire viewport and leaves 250 remaining cacheExtent
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.amber,
|
||||||
|
height: viewportHeight,
|
||||||
|
width: 150,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// This sliver is not within viewport but is within remaining cacheExtent
|
||||||
|
const SliverFillRemaining(
|
||||||
|
hasScrollBody: false,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 100,
|
||||||
|
child: Text('Text in SliverFillRemaining'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// When SliverFillRemaining is not in viewport, but is within
|
||||||
|
// cacheExtent, hidden semantic nodes for it are created.
|
||||||
|
final SemanticsFinder textInSliverFillRemaining = find.semantics.byLabel(
|
||||||
|
'Text in SliverFillRemaining',
|
||||||
|
);
|
||||||
|
expect(textInSliverFillRemaining, findsOne);
|
||||||
|
expect(textInSliverFillRemaining, containsSemantics(isHidden: true));
|
||||||
|
handle.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('outside of viewport and not within cache extent', (WidgetTester tester) async {
|
||||||
|
// Viewport is 800x600
|
||||||
|
const double viewportHeight = 600;
|
||||||
|
const double cacheExtent = 250;
|
||||||
|
final SemanticsHandle handle = tester.ensureSemantics();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: CustomScrollView(
|
||||||
|
cacheExtent: cacheExtent,
|
||||||
|
slivers: <Widget>[
|
||||||
|
// This sliver takes up entire viewport and leaves 0 remaining cacheExtent
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.amber,
|
||||||
|
height: viewportHeight + cacheExtent,
|
||||||
|
width: 150,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// This sliver is not within viewport and not within remaining cacheExtent
|
||||||
|
const SliverFillRemaining(
|
||||||
|
hasScrollBody: false,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 100,
|
||||||
|
child: Text('Text in SliverFillRemaining'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// When SliverFillRemaining is not in viewport and not within
|
||||||
|
// cacheExtent, semantic nodes are not created.
|
||||||
|
final SemanticsFinder textInSliverFillRemaining = find.semantics.byLabel(
|
||||||
|
'Text in SliverFillRemaining',
|
||||||
|
);
|
||||||
|
expect(textInSliverFillRemaining, findsNothing);
|
||||||
|
handle.dispose();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
group('fillOverscroll: true, relevant platforms', () {
|
group('fillOverscroll: true, relevant platforms', () {
|
||||||
testWidgets('child without size is sized by extent and overscroll', (WidgetTester tester) async {
|
testWidgets('child without size is sized by extent and overscroll', (WidgetTester tester) async {
|
||||||
final List<Widget> slivers = <Widget>[
|
final List<Widget> slivers = <Widget>[
|
||||||
|
@ -650,6 +905,137 @@ void main() {
|
||||||
expect(tester.getBottomLeft(button).dy, equals(600.0));
|
expect(tester.getBottomLeft(button).dy, equals(600.0));
|
||||||
expect(tester.getCenter(button).dx, equals(400.0));
|
expect(tester.getCenter(button).dx, equals(400.0));
|
||||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
||||||
|
|
||||||
|
group('has correct semantics when', () {
|
||||||
|
testWidgets('within viewport', (WidgetTester tester) async {
|
||||||
|
// Viewport is 800x600
|
||||||
|
const double viewportHeight = 600;
|
||||||
|
const double cacheExtent = 250;
|
||||||
|
final SemanticsHandle handle = tester.ensureSemantics();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: CustomScrollView(
|
||||||
|
cacheExtent: cacheExtent,
|
||||||
|
slivers: <Widget>[
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.amber,
|
||||||
|
height: viewportHeight - 100,
|
||||||
|
width: 150,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// This sliver is within viewport
|
||||||
|
const SliverFillRemaining(
|
||||||
|
hasScrollBody: false,
|
||||||
|
fillOverscroll: true,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 100,
|
||||||
|
child: Text('Text in SliverFillRemaining'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// When SliverFillRemaining is within the viewport, semantic nodes for
|
||||||
|
// it are created.
|
||||||
|
final SemanticsFinder textInSliverFillRemaining = find.semantics.byLabel(
|
||||||
|
'Text in SliverFillRemaining',
|
||||||
|
);
|
||||||
|
expect(textInSliverFillRemaining, findsOne);
|
||||||
|
expect(textInSliverFillRemaining, containsSemantics(isHidden: false));
|
||||||
|
handle.dispose();
|
||||||
|
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
||||||
|
|
||||||
|
testWidgets('outside of viewport but within cache extent', (WidgetTester tester) async {
|
||||||
|
// Viewport is 800x600
|
||||||
|
const double viewportHeight = 600;
|
||||||
|
const double cacheExtent = 250;
|
||||||
|
final SemanticsHandle handle = tester.ensureSemantics();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: CustomScrollView(
|
||||||
|
cacheExtent: cacheExtent,
|
||||||
|
slivers: <Widget>[
|
||||||
|
// This sliver takes up entire viewport and leaves 250 remaining cacheExtent
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.amber,
|
||||||
|
height: viewportHeight,
|
||||||
|
width: 150,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// This sliver is not within viewport but is within remaining cacheExtent
|
||||||
|
const SliverFillRemaining(
|
||||||
|
hasScrollBody: false,
|
||||||
|
fillOverscroll: true,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 100,
|
||||||
|
child: Text('Text in SliverFillRemaining'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// When SliverFillRemaining is not in viewport, but is within
|
||||||
|
// cacheExtent, hidden semantic nodes for it are created.
|
||||||
|
final SemanticsFinder textInSliverFillRemaining = find.semantics.byLabel(
|
||||||
|
'Text in SliverFillRemaining',
|
||||||
|
);
|
||||||
|
expect(textInSliverFillRemaining, findsOne);
|
||||||
|
expect(textInSliverFillRemaining, containsSemantics(isHidden: true));
|
||||||
|
handle.dispose();
|
||||||
|
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
||||||
|
|
||||||
|
testWidgets('outside of viewport and not within cache extent', (WidgetTester tester) async {
|
||||||
|
// Viewport is 800x600
|
||||||
|
const double viewportHeight = 600;
|
||||||
|
const double cacheExtent = 250;
|
||||||
|
final SemanticsHandle handle = tester.ensureSemantics();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: CustomScrollView(
|
||||||
|
cacheExtent: cacheExtent,
|
||||||
|
slivers: <Widget>[
|
||||||
|
// This sliver takes up entire viewport and leaves 0 remaining cacheExtent
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.amber,
|
||||||
|
height: viewportHeight + cacheExtent,
|
||||||
|
width: 150,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// This sliver is not within viewport and not within remaining cacheExtent
|
||||||
|
const SliverFillRemaining(
|
||||||
|
hasScrollBody: false,
|
||||||
|
fillOverscroll: true,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 100,
|
||||||
|
child: Text('Text in SliverFillRemaining'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// When SliverFillRemaining is not in viewport and not within
|
||||||
|
// cacheExtent, semantic nodes are not created.
|
||||||
|
final SemanticsFinder textInSliverFillRemaining = find.semantics.byLabel(
|
||||||
|
'Text in SliverFillRemaining',
|
||||||
|
);
|
||||||
|
expect(textInSliverFillRemaining, findsNothing);
|
||||||
|
handle.dispose();
|
||||||
|
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('fillOverscroll: true, is ignored on irrelevant platforms', () {
|
group('fillOverscroll: true, is ignored on irrelevant platforms', () {
|
||||||
|
|
Loading…
Reference in a new issue