Unverified Commit 7984f6e0 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Implicit a11y scrolling for iOS (and caching in Viewports) (#17021)

parent be09a200
...@@ -140,11 +140,11 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda ...@@ -140,11 +140,11 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
final double itemExtent = this.itemExtent; final double itemExtent = this.itemExtent;
final double scrollOffset = constraints.scrollOffset; final double scrollOffset = constraints.scrollOffset + constraints.cacheOrigin;
assert(scrollOffset >= 0.0); assert(scrollOffset >= 0.0);
final double remainingPaintExtent = constraints.remainingPaintExtent; final double remainingExtent = constraints.remainingCacheExtent;
assert(remainingPaintExtent >= 0.0); assert(remainingExtent >= 0.0);
final double targetEndScrollOffset = scrollOffset + remainingPaintExtent; final double targetEndScrollOffset = scrollOffset + remainingExtent;
final BoxConstraints childConstraints = constraints.asBoxConstraints( final BoxConstraints childConstraints = constraints.asBoxConstraints(
minExtent: itemExtent, minExtent: itemExtent,
...@@ -242,9 +242,16 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda ...@@ -242,9 +242,16 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
to: trailingScrollOffset, to: trailingScrollOffset,
); );
final double cacheExtent = calculateCacheOffset(
constraints,
from: leadingScrollOffset,
to: trailingScrollOffset,
);
geometry = new SliverGeometry( geometry = new SliverGeometry(
scrollExtent: estimatedMaxScrollOffset, scrollExtent: estimatedMaxScrollOffset,
paintExtent: paintExtent, paintExtent: paintExtent,
cacheExtent: cacheExtent,
maxPaintExtent: estimatedMaxScrollOffset, maxPaintExtent: estimatedMaxScrollOffset,
// Conservative to avoid flickering away the clip during scroll. // Conservative to avoid flickering away the clip during scroll.
hasVisualOverflow: (targetLastIndex != null && lastIndex >= targetLastIndex) hasVisualOverflow: (targetLastIndex != null && lastIndex >= targetLastIndex)
......
...@@ -513,11 +513,11 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor { ...@@ -513,11 +513,11 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor {
childManager.didStartLayout(); childManager.didStartLayout();
childManager.setDidUnderflow(false); childManager.setDidUnderflow(false);
final double scrollOffset = constraints.scrollOffset; final double scrollOffset = constraints.scrollOffset + constraints.cacheOrigin;
assert(scrollOffset >= 0.0); assert(scrollOffset >= 0.0);
final double remainingPaintExtent = constraints.remainingPaintExtent; final double remainingExtent = constraints.remainingCacheExtent;
assert(remainingPaintExtent >= 0.0); assert(remainingExtent >= 0.0);
final double targetEndScrollOffset = scrollOffset + remainingPaintExtent; final double targetEndScrollOffset = scrollOffset + remainingExtent;
final SliverGridLayout layout = _gridDelegate.getLayout(constraints); final SliverGridLayout layout = _gridDelegate.getLayout(constraints);
...@@ -617,11 +617,17 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor { ...@@ -617,11 +617,17 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor {
from: leadingScrollOffset, from: leadingScrollOffset,
to: trailingScrollOffset, to: trailingScrollOffset,
); );
final double cacheExtent = calculateCacheOffset(
constraints,
from: leadingScrollOffset,
to: trailingScrollOffset,
);
geometry = new SliverGeometry( geometry = new SliverGeometry(
scrollExtent: estimatedTotalExtent, scrollExtent: estimatedTotalExtent,
paintExtent: paintExtent, paintExtent: paintExtent,
maxPaintExtent: estimatedTotalExtent, maxPaintExtent: estimatedTotalExtent,
cacheExtent: cacheExtent,
// Conservative to avoid complexity. // Conservative to avoid complexity.
hasVisualOverflow: true, hasVisualOverflow: true,
); );
......
...@@ -47,11 +47,11 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor { ...@@ -47,11 +47,11 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor {
childManager.didStartLayout(); childManager.didStartLayout();
childManager.setDidUnderflow(false); childManager.setDidUnderflow(false);
final double scrollOffset = constraints.scrollOffset; final double scrollOffset = constraints.scrollOffset + constraints.cacheOrigin;
assert(scrollOffset >= 0.0); assert(scrollOffset >= 0.0);
final double remainingPaintExtent = constraints.remainingPaintExtent; final double remainingExtent = constraints.remainingCacheExtent;
assert(remainingPaintExtent >= 0.0); assert(remainingExtent >= 0.0);
final double targetEndScrollOffset = scrollOffset + remainingPaintExtent; final double targetEndScrollOffset = scrollOffset + remainingExtent;
final BoxConstraints childConstraints = constraints.asBoxConstraints(); final BoxConstraints childConstraints = constraints.asBoxConstraints();
int leadingGarbage = 0; int leadingGarbage = 0;
int trailingGarbage = 0; int trailingGarbage = 0;
...@@ -269,9 +269,15 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor { ...@@ -269,9 +269,15 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor {
from: childScrollOffset(firstChild), from: childScrollOffset(firstChild),
to: endScrollOffset, to: endScrollOffset,
); );
final double cacheExtent = calculateCacheOffset(
constraints,
from: childScrollOffset(firstChild),
to: endScrollOffset,
);
geometry = new SliverGeometry( geometry = new SliverGeometry(
scrollExtent: estimatedMaxScrollOffset, scrollExtent: estimatedMaxScrollOffset,
paintExtent: paintExtent, paintExtent: paintExtent,
cacheExtent: cacheExtent,
maxPaintExtent: estimatedMaxScrollOffset, maxPaintExtent: estimatedMaxScrollOffset,
// Conservative to avoid flickering away the clip during scroll. // Conservative to avoid flickering away the clip during scroll.
hasVisualOverflow: endScrollOffset > targetEndScrollOffset || constraints.scrollOffset > 0.0, hasVisualOverflow: endScrollOffset > targetEndScrollOffset || constraints.scrollOffset > 0.0,
......
...@@ -298,36 +298,6 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver ...@@ -298,36 +298,6 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
_keepAliveBucket.values.forEach(visitor); _keepAliveBucket.values.forEach(visitor);
} }
@override
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
switch (constraints.normalizedGrowthDirection) {
case GrowthDirection.forward:
super.visitChildrenForSemantics((RenderObject child) {
// The sliver is overlapped at the leading edge; check if trailing edge is visible.
final Offset bottomRightInViewport = MatrixUtils.transformPoint(
child.getTransformTo(parent), child.semanticBounds.bottomRight
);
final double endOverlap = constraints.overlap;
if ((constraints.axis == Axis.vertical && bottomRightInViewport.dy > endOverlap) ||
(constraints.axis == Axis.horizontal && bottomRightInViewport.dx > endOverlap))
visitor(child);
});
break;
case GrowthDirection.reverse:
super.visitChildrenForSemantics((RenderObject child) {
// The sliver is overlapped at the trailing edge; check if leading edge is visible.
final Offset topLeftInViewport = MatrixUtils.transformPoint(
child.getTransformTo(parent), child.semanticBounds.topLeft
);
final double startOverlap = constraints.remainingPaintExtent - constraints.overlap;
if ((constraints.axis == Axis.vertical && topLeftInViewport.dy < startOverlap) ||
(constraints.axis == Axis.horizontal && topLeftInViewport.dx < startOverlap))
visitor(child);
});
break;
}
}
/// Called during layout to create and add the child with the given index and /// Called during layout to create and add the child with the given index and
/// scroll offset. /// scroll offset.
/// ///
......
...@@ -182,8 +182,10 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R ...@@ -182,8 +182,10 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
child.layout( child.layout(
constraints.copyWith( constraints.copyWith(
scrollOffset: math.max(0.0, constraints.scrollOffset - beforePadding), scrollOffset: math.max(0.0, constraints.scrollOffset - beforePadding),
cacheOrigin: math.min(0.0, constraints.cacheOrigin + beforePadding),
overlap: 0.0, overlap: 0.0,
remainingPaintExtent: constraints.remainingPaintExtent - calculatePaintOffset(constraints, from: 0.0, to: beforePadding), remainingPaintExtent: constraints.remainingPaintExtent - calculatePaintOffset(constraints, from: 0.0, to: beforePadding),
remainingCacheExtent: constraints.remainingCacheExtent - calculateCacheOffset(constraints, from: 0.0, to: beforePadding),
crossAxisExtent: math.max(0.0, constraints.crossAxisExtent - crossAxisPadding), crossAxisExtent: math.max(0.0, constraints.crossAxisExtent - crossAxisPadding),
), ),
parentUsesSize: true, parentUsesSize: true,
...@@ -206,6 +208,17 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R ...@@ -206,6 +208,17 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
to: mainAxisPadding + childLayoutGeometry.scrollExtent, to: mainAxisPadding + childLayoutGeometry.scrollExtent,
); );
final double mainAxisPaddingPaintExtent = beforePaddingPaintExtent + afterPaddingPaintExtent; final double mainAxisPaddingPaintExtent = beforePaddingPaintExtent + afterPaddingPaintExtent;
final double beforePaddingCacheExtent = calculateCacheOffset(
constraints,
from: 0.0,
to: beforePadding,
);
final double afterPaddingCacheExtent = calculateCacheOffset(
constraints,
from: beforePadding + childLayoutGeometry.scrollExtent,
to: mainAxisPadding + childLayoutGeometry.scrollExtent,
);
final double mainAxisPaddingCacheExtent = afterPaddingCacheExtent + beforePaddingCacheExtent;
final double paintExtent = math.min( final double paintExtent = math.min(
beforePaddingPaintExtent + math.max(childLayoutGeometry.paintExtent, childLayoutGeometry.layoutExtent + afterPaddingPaintExtent), beforePaddingPaintExtent + math.max(childLayoutGeometry.paintExtent, childLayoutGeometry.layoutExtent + afterPaddingPaintExtent),
constraints.remainingPaintExtent, constraints.remainingPaintExtent,
...@@ -214,6 +227,7 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R ...@@ -214,6 +227,7 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
scrollExtent: mainAxisPadding + childLayoutGeometry.scrollExtent, scrollExtent: mainAxisPadding + childLayoutGeometry.scrollExtent,
paintExtent: paintExtent, paintExtent: paintExtent,
layoutExtent: math.min(mainAxisPaddingPaintExtent + childLayoutGeometry.layoutExtent, paintExtent), layoutExtent: math.min(mainAxisPaddingPaintExtent + childLayoutGeometry.layoutExtent, paintExtent),
cacheExtent: math.min(mainAxisPaddingCacheExtent + childLayoutGeometry.cacheExtent, constraints.remainingCacheExtent),
maxPaintExtent: mainAxisPadding + childLayoutGeometry.maxPaintExtent, maxPaintExtent: mainAxisPadding + childLayoutGeometry.maxPaintExtent,
hitTestExtent: math.max( hitTestExtent: math.max(
mainAxisPaddingPaintExtent + childLayoutGeometry.paintExtent, mainAxisPaddingPaintExtent + childLayoutGeometry.paintExtent,
......
...@@ -292,13 +292,15 @@ abstract class RenderSliverPinnedPersistentHeader extends RenderSliverPersistent ...@@ -292,13 +292,15 @@ abstract class RenderSliverPinnedPersistentHeader extends RenderSliverPersistent
final bool overlapsContent = constraints.overlap > 0.0; final bool overlapsContent = constraints.overlap > 0.0;
excludeFromSemanticsScrolling = overlapsContent || (constraints.scrollOffset > maxExtent - minExtent); excludeFromSemanticsScrolling = overlapsContent || (constraints.scrollOffset > maxExtent - minExtent);
layoutChild(constraints.scrollOffset, maxExtent, overlapsContent: overlapsContent); layoutChild(constraints.scrollOffset, maxExtent, overlapsContent: overlapsContent);
final double layoutExtent = (maxExtent - constraints.scrollOffset).clamp(0.0, constraints.remainingPaintExtent);
geometry = new SliverGeometry( geometry = new SliverGeometry(
scrollExtent: maxExtent, scrollExtent: maxExtent,
paintOrigin: constraints.overlap, paintOrigin: constraints.overlap,
paintExtent: math.min(childExtent, constraints.remainingPaintExtent), paintExtent: math.min(childExtent, constraints.remainingPaintExtent),
layoutExtent: (maxExtent - constraints.scrollOffset).clamp(0.0, constraints.remainingPaintExtent), layoutExtent: layoutExtent,
maxPaintExtent: maxExtent, maxPaintExtent: maxExtent,
maxScrollObstructionExtent: minExtent, maxScrollObstructionExtent: minExtent,
cacheExtent: layoutExtent > 0.0 ? -constraints.cacheOrigin + layoutExtent : layoutExtent,
hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity. hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity.
); );
} }
......
...@@ -794,11 +794,39 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { ...@@ -794,11 +794,39 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
} }
} }
/// The clip rect from an ancestor that was applied to this node. /// The semantic clip from an ancestor that was applied to this node.
/// ///
/// Expressed in the coordinate system of the node. May be null if no clip has /// Expressed in the coordinate system of the node. May be null if no clip has
/// been applied. /// been applied.
Rect parentClipRect; ///
/// Descendant [SemanticsNode]s that are positioned outside of this rect will
/// be excluded from the semantics tree. Descendant [SemanticsNode]s that are
/// overlapping with this rect, but are outside of [parentPaintClipRect] will
/// be included in the tree, but they will be marked as hidden because they
/// are assumed to be not visible on screen.
///
/// If this rect is null, all descendant [SemanticsNode]s outside of
/// [parentPaintClipRect] will be excluded from the tree.
///
/// If this rect is non-null it has to completely enclose
/// [parentPaintClipRect]. If [parentPaintClipRect] is null this property is
/// also null.
Rect parentSemanticsClipRect;
/// The paint clip from an ancestor that was applied to this node.
///
/// Expressed in the coordinate system of the node. May be null if no clip has
/// been applied.
///
/// Descendant [SemanticsNode]s that are positioned outside of this rect will
/// either be excluded from the semantics tree (if they have no overlap with
/// [parentSemanticsClipRect]) or they will be included and marked as hidden
/// (if they are overlapping with [parentSemanticsClipRect]).
///
/// This rect is completely enclosed by [parentSemanticsClipRect].
///
/// If this rect is null [parentSemanticsClipRect] also has to be null.
Rect parentPaintClipRect;
/// Whether the node is invisible. /// Whether the node is invisible.
/// ///
......
...@@ -564,6 +564,7 @@ class _PageViewState extends State<PageView> { ...@@ -564,6 +564,7 @@ class _PageViewState extends State<PageView> {
physics: physics, physics: physics,
viewportBuilder: (BuildContext context, ViewportOffset position) { viewportBuilder: (BuildContext context, ViewportOffset position) {
return new Viewport( return new Viewport(
cacheExtent: 0.0,
axisDirection: axisDirection, axisDirection: axisDirection,
offset: position, offset: position,
slivers: <Widget>[ slivers: <Widget>[
......
...@@ -56,6 +56,7 @@ abstract class ScrollView extends StatelessWidget { ...@@ -56,6 +56,7 @@ abstract class ScrollView extends StatelessWidget {
bool primary, bool primary,
ScrollPhysics physics, ScrollPhysics physics,
this.shrinkWrap: false, this.shrinkWrap: false,
this.cacheExtent,
}) : assert(reverse != null), }) : assert(reverse != null),
assert(shrinkWrap != null), assert(shrinkWrap != null),
assert(!(controller != null && primary == true), assert(!(controller != null && primary == true),
...@@ -165,6 +166,9 @@ abstract class ScrollView extends StatelessWidget { ...@@ -165,6 +166,9 @@ abstract class ScrollView extends StatelessWidget {
/// Defaults to false. /// Defaults to false.
final bool shrinkWrap; final bool shrinkWrap;
/// {@macro flutter.rendering.viewport.cacheExtent}
final double cacheExtent;
/// Returns the [AxisDirection] in which the scroll view scrolls. /// Returns the [AxisDirection] in which the scroll view scrolls.
/// ///
/// Combines the [scrollDirection] with the [reverse] boolean to obtain the /// Combines the [scrollDirection] with the [reverse] boolean to obtain the
...@@ -211,6 +215,7 @@ abstract class ScrollView extends StatelessWidget { ...@@ -211,6 +215,7 @@ abstract class ScrollView extends StatelessWidget {
axisDirection: axisDirection, axisDirection: axisDirection,
offset: offset, offset: offset,
slivers: slivers, slivers: slivers,
cacheExtent: cacheExtent,
); );
} }
...@@ -333,6 +338,7 @@ class CustomScrollView extends ScrollView { ...@@ -333,6 +338,7 @@ class CustomScrollView extends ScrollView {
bool primary, bool primary,
ScrollPhysics physics, ScrollPhysics physics,
bool shrinkWrap: false, bool shrinkWrap: false,
double cacheExtent,
this.slivers: const <Widget>[], this.slivers: const <Widget>[],
}) : super( }) : super(
key: key, key: key,
...@@ -342,6 +348,7 @@ class CustomScrollView extends ScrollView { ...@@ -342,6 +348,7 @@ class CustomScrollView extends ScrollView {
primary: primary, primary: primary,
physics: physics, physics: physics,
shrinkWrap: shrinkWrap, shrinkWrap: shrinkWrap,
cacheExtent: cacheExtent,
); );
/// The slivers to place inside the viewport. /// The slivers to place inside the viewport.
...@@ -372,6 +379,7 @@ abstract class BoxScrollView extends ScrollView { ...@@ -372,6 +379,7 @@ abstract class BoxScrollView extends ScrollView {
ScrollPhysics physics, ScrollPhysics physics,
bool shrinkWrap: false, bool shrinkWrap: false,
this.padding, this.padding,
double cacheExtent,
}) : super( }) : super(
key: key, key: key,
scrollDirection: scrollDirection, scrollDirection: scrollDirection,
...@@ -380,6 +388,7 @@ abstract class BoxScrollView extends ScrollView { ...@@ -380,6 +388,7 @@ abstract class BoxScrollView extends ScrollView {
primary: primary, primary: primary,
physics: physics, physics: physics,
shrinkWrap: shrinkWrap, shrinkWrap: shrinkWrap,
cacheExtent: cacheExtent,
); );
/// The amount of space by which to inset the children. /// The amount of space by which to inset the children.
...@@ -594,6 +603,7 @@ class ListView extends BoxScrollView { ...@@ -594,6 +603,7 @@ class ListView extends BoxScrollView {
this.itemExtent, this.itemExtent,
bool addAutomaticKeepAlives: true, bool addAutomaticKeepAlives: true,
bool addRepaintBoundaries: true, bool addRepaintBoundaries: true,
double cacheExtent,
List<Widget> children: const <Widget>[], List<Widget> children: const <Widget>[],
}) : childrenDelegate = new SliverChildListDelegate( }) : childrenDelegate = new SliverChildListDelegate(
children, children,
...@@ -608,6 +618,7 @@ class ListView extends BoxScrollView { ...@@ -608,6 +618,7 @@ class ListView extends BoxScrollView {
physics: physics, physics: physics,
shrinkWrap: shrinkWrap, shrinkWrap: shrinkWrap,
padding: padding, padding: padding,
cacheExtent: cacheExtent,
); );
/// Creates a scrollable, linear array of widgets that are created on demand. /// Creates a scrollable, linear array of widgets that are created on demand.
...@@ -648,6 +659,7 @@ class ListView extends BoxScrollView { ...@@ -648,6 +659,7 @@ class ListView extends BoxScrollView {
int itemCount, int itemCount,
bool addAutomaticKeepAlives: true, bool addAutomaticKeepAlives: true,
bool addRepaintBoundaries: true, bool addRepaintBoundaries: true,
double cacheExtent,
}) : childrenDelegate = new SliverChildBuilderDelegate( }) : childrenDelegate = new SliverChildBuilderDelegate(
itemBuilder, itemBuilder,
childCount: itemCount, childCount: itemCount,
...@@ -662,6 +674,7 @@ class ListView extends BoxScrollView { ...@@ -662,6 +674,7 @@ class ListView extends BoxScrollView {
physics: physics, physics: physics,
shrinkWrap: shrinkWrap, shrinkWrap: shrinkWrap,
padding: padding, padding: padding,
cacheExtent: cacheExtent
); );
/// Creates a scrollable, linear array of widgets with a custom child model. /// Creates a scrollable, linear array of widgets with a custom child model.
...@@ -679,6 +692,7 @@ class ListView extends BoxScrollView { ...@@ -679,6 +692,7 @@ class ListView extends BoxScrollView {
EdgeInsetsGeometry padding, EdgeInsetsGeometry padding,
this.itemExtent, this.itemExtent,
@required this.childrenDelegate, @required this.childrenDelegate,
double cacheExtent,
}) : assert(childrenDelegate != null), }) : assert(childrenDelegate != null),
super( super(
key: key, key: key,
...@@ -689,6 +703,7 @@ class ListView extends BoxScrollView { ...@@ -689,6 +703,7 @@ class ListView extends BoxScrollView {
physics: physics, physics: physics,
shrinkWrap: shrinkWrap, shrinkWrap: shrinkWrap,
padding: padding, padding: padding,
cacheExtent: cacheExtent,
); );
/// If non-null, forces the children to have the given extent in the scroll /// If non-null, forces the children to have the given extent in the scroll
...@@ -878,6 +893,7 @@ class GridView extends BoxScrollView { ...@@ -878,6 +893,7 @@ class GridView extends BoxScrollView {
@required this.gridDelegate, @required this.gridDelegate,
bool addAutomaticKeepAlives: true, bool addAutomaticKeepAlives: true,
bool addRepaintBoundaries: true, bool addRepaintBoundaries: true,
double cacheExtent,
List<Widget> children: const <Widget>[], List<Widget> children: const <Widget>[],
}) : assert(gridDelegate != null), }) : assert(gridDelegate != null),
childrenDelegate = new SliverChildListDelegate( childrenDelegate = new SliverChildListDelegate(
...@@ -894,6 +910,7 @@ class GridView extends BoxScrollView { ...@@ -894,6 +910,7 @@ class GridView extends BoxScrollView {
physics: physics, physics: physics,
shrinkWrap: shrinkWrap, shrinkWrap: shrinkWrap,
padding: padding, padding: padding,
cacheExtent: cacheExtent,
); );
/// Creates a scrollable, 2D array of widgets that are created on demand. /// Creates a scrollable, 2D array of widgets that are created on demand.
...@@ -929,6 +946,7 @@ class GridView extends BoxScrollView { ...@@ -929,6 +946,7 @@ class GridView extends BoxScrollView {
int itemCount, int itemCount,
bool addAutomaticKeepAlives: true, bool addAutomaticKeepAlives: true,
bool addRepaintBoundaries: true, bool addRepaintBoundaries: true,
double cacheExtent,
}) : assert(gridDelegate != null), }) : assert(gridDelegate != null),
childrenDelegate = new SliverChildBuilderDelegate( childrenDelegate = new SliverChildBuilderDelegate(
itemBuilder, itemBuilder,
...@@ -945,6 +963,7 @@ class GridView extends BoxScrollView { ...@@ -945,6 +963,7 @@ class GridView extends BoxScrollView {
physics: physics, physics: physics,
shrinkWrap: shrinkWrap, shrinkWrap: shrinkWrap,
padding: padding, padding: padding,
cacheExtent: cacheExtent,
); );
/// Creates a scrollable, 2D array of widgets with both a custom /// Creates a scrollable, 2D array of widgets with both a custom
...@@ -965,6 +984,7 @@ class GridView extends BoxScrollView { ...@@ -965,6 +984,7 @@ class GridView extends BoxScrollView {
EdgeInsetsGeometry padding, EdgeInsetsGeometry padding,
@required this.gridDelegate, @required this.gridDelegate,
@required this.childrenDelegate, @required this.childrenDelegate,
double cacheExtent,
}) : assert(gridDelegate != null), }) : assert(gridDelegate != null),
assert(childrenDelegate != null), assert(childrenDelegate != null),
super( super(
...@@ -976,6 +996,7 @@ class GridView extends BoxScrollView { ...@@ -976,6 +996,7 @@ class GridView extends BoxScrollView {
physics: physics, physics: physics,
shrinkWrap: shrinkWrap, shrinkWrap: shrinkWrap,
padding: padding, padding: padding,
cacheExtent: cacheExtent,
); );
/// Creates a scrollable, 2D array of widgets with a fixed number of tiles in /// Creates a scrollable, 2D array of widgets with a fixed number of tiles in
...@@ -1007,6 +1028,7 @@ class GridView extends BoxScrollView { ...@@ -1007,6 +1028,7 @@ class GridView extends BoxScrollView {
double childAspectRatio: 1.0, double childAspectRatio: 1.0,
bool addAutomaticKeepAlives: true, bool addAutomaticKeepAlives: true,
bool addRepaintBoundaries: true, bool addRepaintBoundaries: true,
double cacheExtent,
List<Widget> children: const <Widget>[], List<Widget> children: const <Widget>[],
}) : gridDelegate = new SliverGridDelegateWithFixedCrossAxisCount( }) : gridDelegate = new SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount, crossAxisCount: crossAxisCount,
...@@ -1027,6 +1049,7 @@ class GridView extends BoxScrollView { ...@@ -1027,6 +1049,7 @@ class GridView extends BoxScrollView {
physics: physics, physics: physics,
shrinkWrap: shrinkWrap, shrinkWrap: shrinkWrap,
padding: padding, padding: padding,
cacheExtent: cacheExtent,
); );
/// Creates a scrollable, 2D array of widgets with tiles that each have a /// Creates a scrollable, 2D array of widgets with tiles that each have a
......
...@@ -323,11 +323,14 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix ...@@ -323,11 +323,14 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
_RenderSingleChildViewport({ _RenderSingleChildViewport({
AxisDirection axisDirection: AxisDirection.down, AxisDirection axisDirection: AxisDirection.down,
@required ViewportOffset offset, @required ViewportOffset offset,
double cacheExtent: RenderAbstractViewport.defaultCacheExtent,
RenderBox child, RenderBox child,
}) : assert(axisDirection != null), }) : assert(axisDirection != null),
assert(offset != null), assert(offset != null),
assert(cacheExtent != null),
_axisDirection = axisDirection, _axisDirection = axisDirection,
_offset = offset { _offset = offset,
_cacheExtent = cacheExtent {
this.child = child; this.child = child;
} }
...@@ -357,6 +360,17 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix ...@@ -357,6 +360,17 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
markNeedsLayout(); markNeedsLayout();
} }
/// {@macro flutter.rendering.viewport.cacheExtent}
double get cacheExtent => _cacheExtent;
double _cacheExtent;
set cacheExtent(double value) {
assert(value != null);
if (value == _cacheExtent)
return;
_cacheExtent = value;
markNeedsLayout();
}
void _hasScrolled() { void _hasScrolled() {
markNeedsPaint(); markNeedsPaint();
markNeedsSemanticsUpdate(); markNeedsSemanticsUpdate();
...@@ -588,4 +602,26 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix ...@@ -588,4 +602,26 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
// Make sure the viewport itself is on screen. // Make sure the viewport itself is on screen.
super.showOnScreen(); super.showOnScreen();
} }
@override
Rect describeSemanticsClip(RenderObject child) {
assert(axis != null);
switch (axis) {
case Axis.vertical:
return new Rect.fromLTRB(
semanticBounds.left,
semanticBounds.top - cacheExtent,
semanticBounds.right,
semanticBounds.bottom + cacheExtent,
);
case Axis.horizontal:
return new Rect.fromLTRB(
semanticBounds.left - cacheExtent,
semanticBounds.top,
semanticBounds.right + cacheExtent,
semanticBounds.bottom,
);
}
return null;
}
} }
...@@ -888,6 +888,25 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render ...@@ -888,6 +888,25 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render
assert(!_childElements.values.any((Element child) => child == null)); assert(!_childElements.values.any((Element child) => child == null));
_childElements.values.toList().forEach(visitor); _childElements.values.toList().forEach(visitor);
} }
@override
void debugVisitOnstageChildren(ElementVisitor visitor) {
_childElements.values.where((Element child) {
final SliverMultiBoxAdaptorParentData parentData = child.renderObject.parentData;
double itemExtent;
switch (renderObject.constraints.axis) {
case Axis.horizontal:
itemExtent = child.renderObject.paintBounds.width;
break;
case Axis.vertical:
itemExtent = child.renderObject.paintBounds.height;
break;
}
return parentData.layoutOffset < renderObject.constraints.scrollOffset + renderObject.constraints.remainingPaintExtent &&
parentData.layoutOffset + itemExtent > renderObject.constraints.scrollOffset;
}).forEach(visitor);
}
} }
/// A sliver that contains a single box child that fills the remaining space in /// A sliver that contains a single box child that fills the remaining space in
......
...@@ -56,6 +56,7 @@ class Viewport extends MultiChildRenderObjectWidget { ...@@ -56,6 +56,7 @@ class Viewport extends MultiChildRenderObjectWidget {
this.anchor: 0.0, this.anchor: 0.0,
@required this.offset, @required this.offset,
this.center, this.center,
this.cacheExtent,
List<Widget> slivers: const <Widget>[], List<Widget> slivers: const <Widget>[],
}) : assert(offset != null), }) : assert(offset != null),
assert(slivers != null), assert(slivers != null),
...@@ -108,6 +109,9 @@ class Viewport extends MultiChildRenderObjectWidget { ...@@ -108,6 +109,9 @@ class Viewport extends MultiChildRenderObjectWidget {
/// The [center] must be the key of a child of the viewport. /// The [center] must be the key of a child of the viewport.
final Key center; final Key center;
/// {@macro flutter.rendering.viewport.cacheExtent}
final double cacheExtent;
/// Given a [BuildContext] and an [AxisDirection], determine the correct cross /// Given a [BuildContext] and an [AxisDirection], determine the correct cross
/// axis direction. /// axis direction.
/// ///
...@@ -135,6 +139,7 @@ class Viewport extends MultiChildRenderObjectWidget { ...@@ -135,6 +139,7 @@ class Viewport extends MultiChildRenderObjectWidget {
crossAxisDirection: crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection), crossAxisDirection: crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection),
anchor: anchor, anchor: anchor,
offset: offset, offset: offset,
cacheExtent: cacheExtent,
); );
} }
...@@ -144,7 +149,8 @@ class Viewport extends MultiChildRenderObjectWidget { ...@@ -144,7 +149,8 @@ class Viewport extends MultiChildRenderObjectWidget {
..axisDirection = axisDirection ..axisDirection = axisDirection
..crossAxisDirection = crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection) ..crossAxisDirection = crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection)
..anchor = anchor ..anchor = anchor
..offset = offset; ..offset = offset
..cacheExtent = cacheExtent;
} }
@override @override
...@@ -199,6 +205,14 @@ class _ViewportElement extends MultiChildRenderObjectElement { ...@@ -199,6 +205,14 @@ class _ViewportElement extends MultiChildRenderObjectElement {
renderObject.center = null; renderObject.center = null;
} }
} }
@override
void debugVisitOnstageChildren(ElementVisitor visitor) {
children.where((Element e) {
final RenderSliver renderSliver = e.renderObject;
return renderSliver.geometry.visible;
}).forEach(visitor);
}
} }
/// A widget that is bigger on the inside and shrink wraps its children in the /// A widget that is bigger on the inside and shrink wraps its children in the
......
...@@ -430,11 +430,11 @@ void main() { ...@@ -430,11 +430,11 @@ void main() {
// Now the sliver is scrolled off screen. // Now the sliver is scrolled off screen.
expect( expect(
tester.getTopLeft(find.widgetWithText(Center, '-1')).dy, tester.getTopLeft(find.widgetWithText(Center, '-1', skipOffstage: false)).dy,
moreOrLessEquals(-175.38461538461536), moreOrLessEquals(-175.38461538461536),
); );
expect( expect(
tester.getBottomLeft(find.widgetWithText(Center, '-1')).dy, tester.getBottomLeft(find.widgetWithText(Center, '-1', skipOffstage: false)).dy,
moreOrLessEquals(-115.38461538461536), moreOrLessEquals(-115.38461538461536),
); );
expect( expect(
...@@ -720,11 +720,11 @@ void main() { ...@@ -720,11 +720,11 @@ void main() {
// Now the sliver is scrolled off screen. // Now the sliver is scrolled off screen.
expect( expect(
tester.getTopLeft(find.widgetWithText(Center, '-1')).dy, tester.getTopLeft(find.widgetWithText(Center, '-1', skipOffstage: false)).dy,
moreOrLessEquals(-175.38461538461536), moreOrLessEquals(-175.38461538461536),
); );
expect( expect(
tester.getBottomLeft(find.widgetWithText(Center, '-1')).dy, tester.getBottomLeft(find.widgetWithText(Center, '-1', skipOffstage: false)).dy,
moreOrLessEquals(-115.38461538461536), moreOrLessEquals(-115.38461538461536),
); );
...@@ -872,7 +872,7 @@ void main() { ...@@ -872,7 +872,7 @@ void main() {
); );
expect( expect(
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))), CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
RefreshIndicatorMode.inactive, RefreshIndicatorMode.inactive,
); );
...@@ -907,7 +907,7 @@ void main() { ...@@ -907,7 +907,7 @@ void main() {
await tester.pump(const Duration(seconds: 2)); await tester.pump(const Duration(seconds: 2));
expect( expect(
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))), CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
RefreshIndicatorMode.inactive, RefreshIndicatorMode.inactive,
); );
...@@ -1147,7 +1147,7 @@ void main() { ...@@ -1147,7 +1147,7 @@ void main() {
moreOrLessEquals(-145.0332383665717), moreOrLessEquals(-145.0332383665717),
); );
expect( expect(
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))), CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
RefreshIndicatorMode.refresh, RefreshIndicatorMode.refresh,
); );
...@@ -1155,7 +1155,7 @@ void main() { ...@@ -1155,7 +1155,7 @@ void main() {
// The sliver layout extent is removed on next frame. // The sliver layout extent is removed on next frame.
await tester.pump(); await tester.pump();
expect( expect(
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))), CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
RefreshIndicatorMode.inactive, RefreshIndicatorMode.inactive,
); );
// Nothing moved. // Nothing moved.
...@@ -1208,7 +1208,7 @@ void main() { ...@@ -1208,7 +1208,7 @@ void main() {
await tester.pump(const Duration(seconds: 5)); await tester.pump(const Duration(seconds: 5));
// In refresh mode but has no UI. // In refresh mode but has no UI.
expect( expect(
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))), CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
RefreshIndicatorMode.refresh, RefreshIndicatorMode.refresh,
); );
expect( expect(
...@@ -1221,7 +1221,7 @@ void main() { ...@@ -1221,7 +1221,7 @@ void main() {
await tester.pump(); await tester.pump();
// Goes to inactive right away since the sliver is already collapsed. // Goes to inactive right away since the sliver is already collapsed.
expect( expect(
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))), CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
RefreshIndicatorMode.inactive, RefreshIndicatorMode.inactive,
); );
......
...@@ -54,16 +54,11 @@ ScrollController primaryScrollController(WidgetTester tester) { ...@@ -54,16 +54,11 @@ ScrollController primaryScrollController(WidgetTester tester) {
return PrimaryScrollController.of(tester.element(find.byType(CustomScrollView))); return PrimaryScrollController.of(tester.element(find.byType(CustomScrollView)));
} }
bool appBarIsVisible(WidgetTester tester) { double appBarHeight(WidgetTester tester) => tester.getSize(find.byType(AppBar, skipOffstage: false)).height;
final RenderSliver sliver = tester.element(find.byType(SliverAppBar)).findRenderObject(); double appBarTop(WidgetTester tester) => tester.getTopLeft(find.byType(AppBar, skipOffstage: false)).dy;
return sliver.geometry.visible; double appBarBottom(WidgetTester tester) => tester.getBottomLeft(find.byType(AppBar, skipOffstage: false)).dy;
}
double appBarHeight(WidgetTester tester) => tester.getSize(find.byType(AppBar)).height;
double appBarTop(WidgetTester tester) => tester.getTopLeft(find.byType(AppBar)).dy;
double appBarBottom(WidgetTester tester) => tester.getBottomLeft(find.byType(AppBar)).dy;
double tabBarHeight(WidgetTester tester) => tester.getSize(find.byType(TabBar)).height; double tabBarHeight(WidgetTester tester) => tester.getSize(find.byType(TabBar, skipOffstage: false)).height;
void main() { void main() {
setUp(() { setUp(() {
...@@ -592,7 +587,7 @@ void main() { ...@@ -592,7 +587,7 @@ void main() {
final ScrollController controller = primaryScrollController(tester); final ScrollController controller = primaryScrollController(tester);
expect(controller.offset, 0.0); expect(controller.offset, 0.0);
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
final double initialAppBarHeight = appBarHeight(tester); final double initialAppBarHeight = appBarHeight(tester);
final double initialTabBarHeight = tabBarHeight(tester); final double initialTabBarHeight = tabBarHeight(tester);
...@@ -600,21 +595,21 @@ void main() { ...@@ -600,21 +595,21 @@ void main() {
// Scroll the not-pinned appbar partially out of view // Scroll the not-pinned appbar partially out of view
controller.jumpTo(50.0); controller.jumpTo(50.0);
await tester.pump(); await tester.pump();
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), initialAppBarHeight); expect(appBarHeight(tester), initialAppBarHeight);
expect(tabBarHeight(tester), initialTabBarHeight); expect(tabBarHeight(tester), initialTabBarHeight);
// Scroll the not-pinned appbar out of view // Scroll the not-pinned appbar out of view
controller.jumpTo(600.0); controller.jumpTo(600.0);
await tester.pump(); await tester.pump();
expect(appBarIsVisible(tester), false); expect(find.byType(SliverAppBar), findsNothing);
expect(appBarHeight(tester), initialAppBarHeight); expect(appBarHeight(tester), initialAppBarHeight);
expect(tabBarHeight(tester), initialTabBarHeight); expect(tabBarHeight(tester), initialTabBarHeight);
// Scroll the not-pinned appbar back into view // Scroll the not-pinned appbar back into view
controller.jumpTo(0.0); controller.jumpTo(0.0);
await tester.pump(); await tester.pump();
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), initialAppBarHeight); expect(appBarHeight(tester), initialAppBarHeight);
expect(tabBarHeight(tester), initialTabBarHeight); expect(tabBarHeight(tester), initialTabBarHeight);
}); });
...@@ -629,7 +624,7 @@ void main() { ...@@ -629,7 +624,7 @@ void main() {
final ScrollController controller = primaryScrollController(tester); final ScrollController controller = primaryScrollController(tester);
expect(controller.offset, 0.0); expect(controller.offset, 0.0);
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), 128.0); expect(appBarHeight(tester), 128.0);
const double initialAppBarHeight = 128.0; const double initialAppBarHeight = 128.0;
...@@ -639,7 +634,7 @@ void main() { ...@@ -639,7 +634,7 @@ void main() {
// point both the toolbar and the tabbar are visible. // point both the toolbar and the tabbar are visible.
controller.jumpTo(600.0); controller.jumpTo(600.0);
await tester.pump(); await tester.pump();
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
expect(tabBarHeight(tester), initialTabBarHeight); expect(tabBarHeight(tester), initialTabBarHeight);
expect(appBarHeight(tester), lessThan(initialAppBarHeight)); expect(appBarHeight(tester), lessThan(initialAppBarHeight));
expect(appBarHeight(tester), greaterThan(initialTabBarHeight)); expect(appBarHeight(tester), greaterThan(initialTabBarHeight));
...@@ -647,7 +642,7 @@ void main() { ...@@ -647,7 +642,7 @@ void main() {
// Scroll the not-pinned appbar back into view // Scroll the not-pinned appbar back into view
controller.jumpTo(0.0); controller.jumpTo(0.0);
await tester.pump(); await tester.pump();
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), initialAppBarHeight); expect(appBarHeight(tester), initialAppBarHeight);
expect(tabBarHeight(tester), initialTabBarHeight); expect(tabBarHeight(tester), initialTabBarHeight);
}); });
...@@ -662,7 +657,7 @@ void main() { ...@@ -662,7 +657,7 @@ void main() {
final ScrollController controller = primaryScrollController(tester); final ScrollController controller = primaryScrollController(tester);
expect(controller.offset, 0.0); expect(controller.offset, 0.0);
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), 128.0); expect(appBarHeight(tester), 128.0);
const double initialAppBarHeight = 128.0; const double initialAppBarHeight = 128.0;
...@@ -672,7 +667,7 @@ void main() { ...@@ -672,7 +667,7 @@ void main() {
// point only the tabBar is visible. // point only the tabBar is visible.
controller.jumpTo(600.0); controller.jumpTo(600.0);
await tester.pump(); await tester.pump();
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
expect(tabBarHeight(tester), initialTabBarHeight); expect(tabBarHeight(tester), initialTabBarHeight);
expect(appBarHeight(tester), lessThan(initialAppBarHeight)); expect(appBarHeight(tester), lessThan(initialAppBarHeight));
expect(appBarHeight(tester), initialTabBarHeight); expect(appBarHeight(tester), initialTabBarHeight);
...@@ -680,7 +675,7 @@ void main() { ...@@ -680,7 +675,7 @@ void main() {
// Scroll the floating-pinned appbar back into view // Scroll the floating-pinned appbar back into view
controller.jumpTo(0.0); controller.jumpTo(0.0);
await tester.pump(); await tester.pump();
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), initialAppBarHeight); expect(appBarHeight(tester), initialAppBarHeight);
expect(tabBarHeight(tester), initialTabBarHeight); expect(tabBarHeight(tester), initialTabBarHeight);
}); });
...@@ -692,7 +687,7 @@ void main() { ...@@ -692,7 +687,7 @@ void main() {
snap: true, snap: true,
expandedHeight: 128.0, expandedHeight: 128.0,
)); ));
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarTop(tester), 0.0); expect(appBarTop(tester), 0.0);
expect(appBarHeight(tester), 128.0); expect(appBarHeight(tester), 128.0);
expect(appBarBottom(tester), 128.0); expect(appBarBottom(tester), 128.0);
...@@ -701,7 +696,7 @@ void main() { ...@@ -701,7 +696,7 @@ void main() {
final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position; final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
position.jumpTo(256.00); position.jumpTo(256.00);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(appBarIsVisible(tester), false); expect(find.byType(SliverAppBar), findsNothing);
expect(appBarTop(tester), lessThanOrEqualTo(-128.0)); expect(appBarTop(tester), lessThanOrEqualTo(-128.0));
// Drag the scrollable up and down. The app bar should not snap open, its // Drag the scrollable up and down. The app bar should not snap open, its
...@@ -773,7 +768,7 @@ void main() { ...@@ -773,7 +768,7 @@ void main() {
snap: true, snap: true,
expandedHeight: 128.0, expandedHeight: 128.0,
)); ));
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarTop(tester), 0.0); expect(appBarTop(tester), 0.0);
expect(appBarHeight(tester), 128.0); expect(appBarHeight(tester), 128.0);
expect(appBarBottom(tester), 128.0); expect(appBarBottom(tester), 128.0);
...@@ -783,7 +778,7 @@ void main() { ...@@ -783,7 +778,7 @@ void main() {
final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position; final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
position.jumpTo(256.0); position.jumpTo(256.0);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarTop(tester), 0.0); expect(appBarTop(tester), 0.0);
expect(appBarHeight(tester), kTextTabBarHeight); expect(appBarHeight(tester), kTextTabBarHeight);
......
...@@ -289,7 +289,7 @@ void main() { ...@@ -289,7 +289,7 @@ void main() {
} }
StateMarkerState findStateMarkerState(String name) { StateMarkerState findStateMarkerState(String name) {
return tester.state(find.widgetWithText(StateMarker, name)); return tester.state(find.widgetWithText(StateMarker, name, skipOffstage: false));
} }
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
......
...@@ -162,7 +162,7 @@ void main() { ...@@ -162,7 +162,7 @@ void main() {
await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0));
await tester.pump(); await tester.pump();
expect(find.byType(TextField), findsOneWidget); expect(find.byType(TextField, skipOffstage: false), findsOneWidget);
expect(tester.testTextInput.isVisible, isTrue); expect(tester.testTextInput.isVisible, isTrue);
focusNode.unfocus(); focusNode.unfocus();
...@@ -201,10 +201,10 @@ void main() { ...@@ -201,10 +201,10 @@ void main() {
expect(find.byType(TextField), findsOneWidget); expect(find.byType(TextField), findsOneWidget);
await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0));
await tester.pump(); await tester.pump();
expect(find.byType(TextField), findsOneWidget); expect(find.byType(TextField, skipOffstage: false), findsOneWidget);
await tester.pumpWidget(makeTest('test')); await tester.pumpWidget(makeTest('test'));
await tester.pump(); // in case the AutomaticKeepAlive widget thinks it needs a cleanup frame await tester.pump(); // in case the AutomaticKeepAlive widget thinks it needs a cleanup frame
expect(find.byType(TextField), findsOneWidget); expect(find.byType(TextField, skipOffstage: false), findsOneWidget);
}); });
testWidgets('TextField with decoration:null', (WidgetTester tester) async { testWidgets('TextField with decoration:null', (WidgetTester tester) async {
......
This diff is collapsed.
...@@ -83,6 +83,7 @@ void main() { ...@@ -83,6 +83,7 @@ void main() {
axisDirection: AxisDirection.down, axisDirection: AxisDirection.down,
crossAxisDirection: AxisDirection.right, crossAxisDirection: AxisDirection.right,
offset: new ViewportOffset.zero(), offset: new ViewportOffset.zero(),
cacheExtent: 0.0,
children: <RenderSliver>[ children: <RenderSliver>[
inner = childManager.createRenderObject(), inner = childManager.createRenderObject(),
], ],
...@@ -161,6 +162,7 @@ void main() { ...@@ -161,6 +162,7 @@ void main() {
children: <RenderSliver>[ children: <RenderSliver>[
inner = childManager.createRenderObject(), inner = childManager.createRenderObject(),
], ],
cacheExtent: 0.0,
); );
layout(root); layout(root);
......
...@@ -27,6 +27,8 @@ void main() { ...@@ -27,6 +27,8 @@ void main() {
crossAxisExtent: 0.0, crossAxisExtent: 0.0,
crossAxisDirection: AxisDirection.right, crossAxisDirection: AxisDirection.right,
viewportMainAxisExtent: 0.0, viewportMainAxisExtent: 0.0,
cacheOrigin: 0.0,
remainingCacheExtent: 0.0,
); );
final SliverConstraints b = a.copyWith(); final SliverConstraints b = a.copyWith();
expect(a, equals(b)); expect(a, equals(b));
...@@ -55,6 +57,8 @@ void main() { ...@@ -55,6 +57,8 @@ void main() {
crossAxisExtent: 40.0, crossAxisExtent: 40.0,
crossAxisDirection: AxisDirection.right, crossAxisDirection: AxisDirection.right,
viewportMainAxisExtent: 30.0, viewportMainAxisExtent: 30.0,
cacheOrigin: 0.0,
remainingCacheExtent: 0.0,
); );
expect(c, equals(d)); expect(c, equals(d));
expect(c.normalizedGrowthDirection, equals(GrowthDirection.forward)); expect(c.normalizedGrowthDirection, equals(GrowthDirection.forward));
......
...@@ -60,9 +60,9 @@ void main() { ...@@ -60,9 +60,9 @@ void main() {
), ),
); );
double itemHeight(int index) => tester.getSize(find.byKey(new ValueKey<int>(index))).height; double itemHeight(int index) => tester.getSize(find.byKey(new ValueKey<int>(index), skipOffstage: false)).height;
double itemTop(int index) => tester.getTopLeft(find.byKey(new ValueKey<int>(index))).dy; double itemTop(int index) => tester.getTopLeft(find.byKey(new ValueKey<int>(index), skipOffstage: false)).dy;
double itemBottom(int index) => tester.getBottomLeft(find.byKey(new ValueKey<int>(index))).dy; double itemBottom(int index) => tester.getBottomLeft(find.byKey(new ValueKey<int>(index), skipOffstage: false)).dy;
listKey.currentState.insertItem(0, duration: const Duration(milliseconds: 100)); listKey.currentState.insertItem(0, duration: const Duration(milliseconds: 100));
await tester.pump(); await tester.pump();
......
...@@ -168,9 +168,17 @@ void main() { ...@@ -168,9 +168,17 @@ void main() {
0, 1, 2, // col 0 0, 1, 2, // col 0
3, 4, 5, // col 1 3, 4, 5, // col 1
6, 7, 8, // col 2 6, 7, 8, // col 2
9, 10, 11, // col 3 (in cached area)
])); ]));
log.clear(); log.clear();
for (int i = 0; i < 9; i++) {
expect(find.text('$i'), findsOneWidget);
}
for (int i = 9; i < 80; i++) {
expect(find.text('$i'), findsNothing);
}
final ScrollableState state = tester.state(find.byType(Scrollable)); final ScrollableState state = tester.state(find.byType(Scrollable));
final ScrollPosition position = state.position; final ScrollPosition position = state.position;
position.jumpTo(3025.0); position.jumpTo(3025.0);
...@@ -179,25 +187,49 @@ void main() { ...@@ -179,25 +187,49 @@ void main() {
await tester.pump(); await tester.pump();
expect(log, equals(<int>[ expect(log, equals(<int>[
30, 31, 32, // col 10 (in cached area)
33, 34, 35, // col 11 33, 34, 35, // col 11
36, 37, 38, // col 12 36, 37, 38, // col 12
39, 40, 41, // col 13 39, 40, 41, // col 13
42, 43, 44, // col 14 42, 43, 44, // col 14
45, 46, 47, // col 15 (in cached area)
])); ]));
log.clear(); log.clear();
for (int i = 0; i < 33; i++) {
expect(find.text('$i'), findsNothing);
}
for (int i = 33; i < 45; i++) {
expect(find.text('$i'), findsOneWidget);
}
for (int i = 45; i < 80; i++) {
expect(find.text('$i'), findsNothing);
}
position.jumpTo(975.0); position.jumpTo(975.0);
expect(log, isEmpty); expect(log, isEmpty);
await tester.pump(); await tester.pump();
expect(log, equals(<int>[ expect(log, equals(<int>[
6, 7, 8, // col2 (in cached area)
9, 10, 11, // col 3 9, 10, 11, // col 3
12, 13, 14, // col 4 12, 13, 14, // col 4
15, 16, 17, // col 5 15, 16, 17, // col 5
18, 19, 20, // col 6 18, 19, 20, // col 6
21, 22, 23, // col 7 (in cached area)
])); ]));
log.clear(); log.clear();
for (int i = 0; i < 9; i++) {
expect(find.text('$i'), findsNothing);
}
for (int i = 9; i < 21; i++) {
expect(find.text('$i'), findsOneWidget);
}
for (int i = 21; i < 80; i++) {
expect(find.text('$i'), findsNothing);
}
}); });
testWidgets('GridView - change crossAxisCount', (WidgetTester tester) async { testWidgets('GridView - change crossAxisCount', (WidgetTester tester) async {
...@@ -230,7 +262,15 @@ void main() { ...@@ -230,7 +262,15 @@ void main() {
0, 1, 2, 3, // row 0 0, 1, 2, 3, // row 0
4, 5, 6, 7, // row 1 4, 5, 6, 7, // row 1
8, 9, 10, 11, // row 2 8, 9, 10, 11, // row 2
12, 13, 14, 15, // row 3 (in cached area)
16, 17, 18, 19, // row 4 (in cached area)
])); ]));
for (int i = 0; i < 12; i++) {
expect(find.text('$i'), findsOneWidget);
}
for (int i = 12; i < 40; i++) {
expect(find.text('$i'), findsNothing);
}
log.clear(); log.clear();
await tester.pumpWidget( await tester.pumpWidget(
...@@ -258,6 +298,8 @@ void main() { ...@@ -258,6 +298,8 @@ void main() {
0, 1, 2, 3, // row 0 0, 1, 2, 3, // row 0
4, 5, 6, 7, // row 1 4, 5, 6, 7, // row 1
8, 9, 10, 11, // row 2 8, 9, 10, 11, // row 2
12, 13, 14, 15, // row 3 (in cached area)
16, 17, 18, 19, // row 4 (in cached area)
])); ]));
log.clear(); log.clear();
...@@ -295,7 +337,15 @@ void main() { ...@@ -295,7 +337,15 @@ void main() {
0, 1, 2, 3, // row 0 0, 1, 2, 3, // row 0
4, 5, 6, 7, // row 1 4, 5, 6, 7, // row 1
8, 9, 10, 11, // row 2 8, 9, 10, 11, // row 2
12, 13, 14, 15, // row 3 (in cached area)
16, 17, 18, 19, // row 4 (in cached area)
])); ]));
for (int i = 0; i < 12; i++) {
expect(find.text('$i'), findsOneWidget);
}
for (int i = 12; i < 40; i++) {
expect(find.text('$i'), findsNothing);
}
log.clear(); log.clear();
await tester.pumpWidget( await tester.pumpWidget(
...@@ -323,6 +373,8 @@ void main() { ...@@ -323,6 +373,8 @@ void main() {
0, 1, 2, 3, // row 0 0, 1, 2, 3, // row 0
4, 5, 6, 7, // row 1 4, 5, 6, 7, // row 1
8, 9, 10, 11, // row 2 8, 9, 10, 11, // row 2
12, 13, 14, 15, // row 3 (in cached area)
16, 17, 18, 19, // row 4 (in cached area)
])); ]));
log.clear(); log.clear();
...@@ -346,6 +398,7 @@ void main() { ...@@ -346,6 +398,7 @@ void main() {
child: new SizedBox( child: new SizedBox(
height: 200.0, height: 200.0,
child: new GridView.count( child: new GridView.count(
cacheExtent: 0.0,
crossAxisCount: 2, crossAxisCount: 2,
children: <Widget>[ container, container, container, container ], children: <Widget>[ container, container, container, container ],
), ),
...@@ -379,7 +432,7 @@ void main() { ...@@ -379,7 +432,7 @@ void main() {
), ),
); );
expect(find.text('0'), findsOneWidget); expect(find.text('0'), findsNothing);
expect(find.text('1'), findsNothing); expect(find.text('1'), findsNothing);
}); });
......
...@@ -781,6 +781,7 @@ void main() { ...@@ -781,6 +781,7 @@ void main() {
builder: (BuildContext context) { builder: (BuildContext context) {
return new Material( return new Material(
child: new ListView( child: new ListView(
cacheExtent: 0.0,
children: <Widget>[ children: <Widget>[
const SizedBox(height: 100.0), const SizedBox(height: 100.0),
// This container will appear at Y=100 // This container will appear at Y=100
......
...@@ -38,7 +38,11 @@ void main() { ...@@ -38,7 +38,11 @@ void main() {
final FlipWidgetState testWidget = tester.state(find.byType(FlipWidget)); final FlipWidgetState testWidget = tester.state(find.byType(FlipWidget));
expect(callbackTracker, equals(<int>[0, 1, 2, 3, 4, 5])); expect(callbackTracker, equals(<int>[
0, 1, 2, 3, 4, 5, // visible in viewport
6, 7, 8, // in caching area
]));
check(visible: <int>[0, 1, 2, 3, 4, 5], hidden: <int>[ 6, 7, 8]);
callbackTracker.clear(); callbackTracker.clear();
testWidget.flip(); testWidget.flip();
...@@ -50,7 +54,11 @@ void main() { ...@@ -50,7 +54,11 @@ void main() {
testWidget.flip(); testWidget.flip();
await tester.pump(); await tester.pump();
expect(callbackTracker, equals(<int>[0, 1, 2, 3, 4, 5])); expect(callbackTracker, equals(<int>[
0, 1, 2, 3, 4, 5,
6, 7, 8, // in caching area
]));
check(visible: <int>[0, 1, 2, 3, 4, 5], hidden: <int>[ 6, 7, 8]);
}); });
testWidgets('ListView.builder vertical', (WidgetTester tester) async { testWidgets('ListView.builder vertical', (WidgetTester tester) async {
...@@ -91,7 +99,12 @@ void main() { ...@@ -91,7 +99,12 @@ void main() {
await tester.pumpWidget(buildWidget()); await tester.pumpWidget(buildWidget());
expect(callbackTracker, equals(<int>[1, 2, 3, 4])); expect(callbackTracker, equals(<int>[
0, // in caching area
1, 2, 3, 4,
5, // in caching area
]));
check(visible: <int>[1, 2, 3, 4], hidden: <int>[0, 5]);
callbackTracker.clear(); callbackTracker.clear();
jumpTo(400.0); jumpTo(400.0);
...@@ -99,12 +112,12 @@ void main() { ...@@ -99,12 +112,12 @@ void main() {
await tester.pumpWidget(buildWidget()); await tester.pumpWidget(buildWidget());
expect(callbackTracker, equals(<int>[1, 2, 3, 4])); expect(callbackTracker, equals(<int>[
callbackTracker.clear(); 0, 1, // in caching area
2, 3, 4,
await tester.pumpWidget(buildWidget()); 5, 6, // in caching area
]));
expect(callbackTracker, equals(<int>[2, 3, 4])); check(visible: <int>[2, 3, 4], hidden: <int>[0, 1, 5, 6]);
callbackTracker.clear(); callbackTracker.clear();
jumpTo(500.0); jumpTo(500.0);
...@@ -112,7 +125,12 @@ void main() { ...@@ -112,7 +125,12 @@ void main() {
await tester.pumpWidget(buildWidget()); await tester.pumpWidget(buildWidget());
expect(callbackTracker, equals(<int>[2, 3, 4, 5])); expect(callbackTracker, equals(<int>[
0, 1, // in caching area
2, 3, 4, 5,
6, // in caching area
]));
check(visible: <int>[2, 3, 4, 5], hidden: <int>[0, 1, 6]);
callbackTracker.clear(); callbackTracker.clear();
}); });
...@@ -155,8 +173,12 @@ void main() { ...@@ -155,8 +173,12 @@ void main() {
await tester.pumpWidget(buildWidget()); await tester.pumpWidget(buildWidget());
expect(callbackTracker, equals(<int>[1, 2, 3, 4, 5])); expect(callbackTracker, equals(<int>[
0, // in caching area
1, 2, 3, 4, 5,
6, // in caching area
]));
check(visible: <int>[1, 2, 3, 4, 5], hidden: <int>[0, 6]);
callbackTracker.clear(); callbackTracker.clear();
jumpTo(400.0); jumpTo(400.0);
...@@ -164,12 +186,12 @@ void main() { ...@@ -164,12 +186,12 @@ void main() {
await tester.pumpWidget(buildWidget()); await tester.pumpWidget(buildWidget());
expect(callbackTracker, equals(<int>[1, 2, 3, 4, 5])); expect(callbackTracker, equals(<int>[
callbackTracker.clear(); 0, 1, // in caching area
2, 3, 4, 5,
await tester.pumpWidget(buildWidget()); 6, 7, // in caching area
]));
expect(callbackTracker, equals(<int>[2, 3, 4, 5])); check(visible: <int>[2, 3, 4, 5], hidden: <int>[0, 1, 6, 7]);
callbackTracker.clear(); callbackTracker.clear();
jumpTo(500.0); jumpTo(500.0);
...@@ -177,7 +199,12 @@ void main() { ...@@ -177,7 +199,12 @@ void main() {
await tester.pumpWidget(buildWidget()); await tester.pumpWidget(buildWidget());
expect(callbackTracker, equals(<int>[2, 3, 4, 5, 6])); expect(callbackTracker, equals(<int>[
0, 1, // in caching area
2, 3, 4, 5, 6,
7, // in caching area
]));
check(visible: <int>[2, 3, 4, 5, 6], hidden: <int>[0, 1, 7]);
callbackTracker.clear(); callbackTracker.clear();
}); });
...@@ -208,26 +235,39 @@ void main() { ...@@ -208,26 +235,39 @@ void main() {
} }
await tester.pumpWidget(testWidget); await tester.pumpWidget(testWidget);
expect(callbackTracker, equals(<int>[0, 1])); expect(callbackTracker, equals(<int>[0, 1, 2]));
check(visible: <int>[0, 1], hidden: <int>[2]);
callbackTracker.clear(); callbackTracker.clear();
jumpTo(150.0); jumpTo(150.0);
await tester.pump(); await tester.pump();
expect(callbackTracker, equals(<int>[2])); expect(callbackTracker, equals(<int>[3]));
check(visible: <int>[0, 1, 2], hidden: <int>[3]);
callbackTracker.clear(); callbackTracker.clear();
jumpTo(600.0); jumpTo(600.0);
await tester.pump(); await tester.pump();
expect(callbackTracker, equals(<int>[3])); expect(callbackTracker, equals(<int>[4]));
check(visible: <int>[2, 3], hidden: <int>[0, 1, 4]);
callbackTracker.clear(); callbackTracker.clear();
jumpTo(750.0); jumpTo(750.0);
await tester.pump(); await tester.pump();
expect(callbackTracker, equals(<int>[4])); expect(callbackTracker, equals(<int>[5]));
check(visible: <int>[2, 3, 4], hidden: <int>[0, 1, 5]);
callbackTracker.clear(); callbackTracker.clear();
}); });
} }
void check({List<int> visible: const <int>[], List<int> hidden: const <int>[]}) {
for (int i in visible) {
expect(find.text('$i'), findsOneWidget);
}
for (int i in hidden) {
expect(find.text('$i'), findsNothing);
}
}
...@@ -12,6 +12,7 @@ void main() { ...@@ -12,6 +12,7 @@ void main() {
new Directionality( new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new ListView( child: new ListView(
cacheExtent: 0.0,
controller: controller, controller: controller,
children: <Widget>[ children: <Widget>[
new Container(height: 400.0, child: const Text('1')), new Container(height: 400.0, child: const Text('1')),
...@@ -34,6 +35,7 @@ void main() { ...@@ -34,6 +35,7 @@ void main() {
new Directionality( new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new ListView( child: new ListView(
cacheExtent: 0.0,
controller: controller, controller: controller,
children: <Widget>[ children: <Widget>[
new Container(height: 200.0, child: const Text('1')), new Container(height: 200.0, child: const Text('1')),
...@@ -53,16 +55,73 @@ void main() { ...@@ -53,16 +55,73 @@ void main() {
controller.jumpTo(300.0); controller.jumpTo(300.0);
await tester.pump(); await tester.pump();
expect(controller.offset, equals(300.0));
expect(tester.getTopLeft(find.text('2')).dy, equals(100.0)); expect(tester.getTopLeft(find.text('2')).dy, equals(100.0));
controller.jumpTo(50.0); controller.jumpTo(50.0);
await tester.pump(); await tester.pump();
expect(controller.offset, equals(0.0)); expect(controller.offset, equals(0.0));
expect(tester.getTopLeft(find.text('2')).dy, equals(200.0)); expect(tester.getTopLeft(find.text('2')).dy, equals(200.0));
}); });
testWidgets('ListView can handle shrinking top elements with cache extent', (WidgetTester tester) async {
final ScrollController controller = new ScrollController();
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new ListView(
controller: controller,
children: <Widget>[
new Container(height: 400.0, child: const Text('1')),
new Container(height: 400.0, child: const Text('2')),
new Container(height: 400.0, child: const Text('3')),
new Container(height: 400.0, child: const Text('4')),
new Container(height: 400.0, child: const Text('5')),
new Container(height: 400.0, child: const Text('6')),
],
),
),
);
controller.jumpTo(1000.0);
await tester.pump();
expect(tester.getTopLeft(find.text('4')).dy, equals(200.0));
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new ListView(
controller: controller,
children: <Widget>[
new Container(height: 200.0, child: const Text('1')),
new Container(height: 400.0, child: const Text('2')),
new Container(height: 400.0, child: const Text('3')),
new Container(height: 400.0, child: const Text('4')),
new Container(height: 400.0, child: const Text('5')),
new Container(height: 400.0, child: const Text('6')),
],
),
),
);
expect(controller.offset, equals(1000.0));
expect(tester.getTopLeft(find.text('4')).dy, equals(200.0));
controller.jumpTo(300.0);
await tester.pump();
expect(controller.offset, equals(250.0));
expect(tester.getTopLeft(find.text('2')).dy, equals(-50.0));
controller.jumpTo(50.0);
await tester.pump();
expect(controller.offset, equals(50.0));
expect(tester.getTopLeft(find.text('2')).dy, equals(150.0));
});
testWidgets('ListView can handle inserts at 0', (WidgetTester tester) async { testWidgets('ListView can handle inserts at 0', (WidgetTester tester) async {
final ScrollController controller = new ScrollController(); final ScrollController controller = new ScrollController();
await tester.pumpWidget( await tester.pumpWidget(
......
...@@ -150,7 +150,7 @@ void main() { ...@@ -150,7 +150,7 @@ void main() {
), ),
); );
final SliverMultiBoxAdaptorElement element = tester.element(find.byType(SliverList)); final SliverMultiBoxAdaptorElement element = tester.element(find.byType(SliverList, skipOffstage: false));
final double maxScrollOffset = element.estimateMaxScrollOffset( final double maxScrollOffset = element.estimateMaxScrollOffset(
null, null,
......
...@@ -96,7 +96,7 @@ void main() { ...@@ -96,7 +96,7 @@ void main() {
), ),
); );
expect(log, equals(<int>[0, 1, 2])); expect(log, equals(<int>[0, 1, 2, 3, 4]));
log.clear(); log.clear();
final ScrollableState state = tester.state(find.byType(Scrollable)); final ScrollableState state = tester.state(find.byType(Scrollable));
...@@ -106,7 +106,7 @@ void main() { ...@@ -106,7 +106,7 @@ void main() {
expect(log, isEmpty); expect(log, isEmpty);
await tester.pump(); await tester.pump();
expect(log, equals(<int>[10, 11, 12, 13])); expect(log, equals(<int>[8, 9, 10, 11, 12, 13, 14]));
log.clear(); log.clear();
position.jumpTo(975.0); position.jumpTo(975.0);
...@@ -114,7 +114,7 @@ void main() { ...@@ -114,7 +114,7 @@ void main() {
expect(log, isEmpty); expect(log, isEmpty);
await tester.pump(); await tester.pump();
expect(log, equals(<int>[4, 5, 6, 7])); expect(log, equals(<int>[7, 6, 5, 4, 3]));
log.clear(); log.clear();
}); });
...@@ -196,7 +196,7 @@ void main() { ...@@ -196,7 +196,7 @@ void main() {
), ),
), ),
); );
expect(find.text('padded'), findsOneWidget); expect(find.text('padded', skipOffstage: false), findsOneWidget);
}); });
testWidgets('ListView with itemExtent in unbounded context', (WidgetTester tester) async { testWidgets('ListView with itemExtent in unbounded context', (WidgetTester tester) async {
...@@ -240,7 +240,7 @@ void main() { ...@@ -240,7 +240,7 @@ void main() {
), ),
); );
expect(delegate.log, equals(<String>['didFinishLayout firstIndex=0 lastIndex=5'])); expect(delegate.log, equals(<String>['didFinishLayout firstIndex=0 lastIndex=7']));
delegate.log.clear(); delegate.log.clear();
await tester.pumpWidget( await tester.pumpWidget(
...@@ -253,7 +253,7 @@ void main() { ...@@ -253,7 +253,7 @@ void main() {
), ),
); );
expect(delegate.log, equals(<String>['didFinishLayout firstIndex=0 lastIndex=2'])); expect(delegate.log, equals(<String>['didFinishLayout firstIndex=0 lastIndex=4']));
delegate.log.clear(); delegate.log.clear();
await tester.drag(find.byType(ListView), const Offset(0.0, -600.0)); await tester.drag(find.byType(ListView), const Offset(0.0, -600.0));
...@@ -262,7 +262,7 @@ void main() { ...@@ -262,7 +262,7 @@ void main() {
await tester.pump(); await tester.pump();
expect(delegate.log, equals(<String>['didFinishLayout firstIndex=2 lastIndex=5'])); expect(delegate.log, equals(<String>['didFinishLayout firstIndex=1 lastIndex=6']));
delegate.log.clear(); delegate.log.clear();
}); });
......
...@@ -38,7 +38,10 @@ void main() { ...@@ -38,7 +38,10 @@ void main() {
final FlipWidgetState testWidget = tester.state(find.byType(FlipWidget)); final FlipWidgetState testWidget = tester.state(find.byType(FlipWidget));
expect(callbackTracker, equals(<int>[0, 1, 2, 3, 4, 5])); expect(callbackTracker, equals(<int>[
0, 1, 2, 3, 4, 5, // visible
6, 7, 8 // in cached area
]));
callbackTracker.clear(); callbackTracker.clear();
testWidget.flip(); testWidget.flip();
...@@ -50,7 +53,10 @@ void main() { ...@@ -50,7 +53,10 @@ void main() {
testWidget.flip(); testWidget.flip();
await tester.pump(); await tester.pump();
expect(callbackTracker, equals(<int>[0, 1, 2, 3, 4, 5])); expect(callbackTracker, equals(<int>[
0, 1, 2, 3, 4, 5, // visible
6, 7, 8, // in cached area
]));
}); });
testWidgets('ListView vertical', (WidgetTester tester) async { testWidgets('ListView vertical', (WidgetTester tester) async {
...@@ -86,22 +92,34 @@ void main() { ...@@ -86,22 +92,34 @@ void main() {
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
// 0 is built to find its height // 0 is built to find its height
expect(callbackTracker, equals(<int>[0, 1, 2, 3, 4])); expect(callbackTracker, equals(<int>[
0, 1, 2, 3, 4,
5, // in cached area
]));
callbackTracker.clear(); callbackTracker.clear();
final ScrollableState scrollable = tester.state(find.byType(Scrollable)); final ScrollableState scrollable = tester.state(find.byType(Scrollable));
scrollable.position.jumpTo(400.0); // now only 3 should fit, numbered 2-4. scrollable.position.jumpTo(600.0); // now only 3 should fit, numbered 3-5.
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
// We build the visible children to find their new size. // We build the visible children to find their new size.
expect(callbackTracker, equals(<int>[1, 2, 3, 4])); expect(callbackTracker, equals(<int>[
0, 1, 2,
3, 4, 5, //visible
6, 7
]));
callbackTracker.clear(); callbackTracker.clear();
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
// 0 isn't built because they're not visible. // 0 isn't built because they're not visible.
expect(callbackTracker, equals(<int>[1, 2, 3, 4])); expect(callbackTracker, equals(<int>[
1, 2,
3, 4, 5, // visible
6, 7,
]
));
callbackTracker.clear(); callbackTracker.clear();
}); });
...@@ -128,7 +146,7 @@ void main() { ...@@ -128,7 +146,7 @@ void main() {
child: new FlipWidget( child: new FlipWidget(
left: new ListView.builder( left: new ListView.builder(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
controller: new ScrollController(initialScrollOffset: 300.0), controller: new ScrollController(initialScrollOffset: 500.0),
itemBuilder: itemBuilder, itemBuilder: itemBuilder,
), ),
right: const Text('Not Today'), right: const Text('Not Today'),
...@@ -139,23 +157,23 @@ void main() { ...@@ -139,23 +157,23 @@ void main() {
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
// 0 is built to find its width // 0 is built to find its width
expect(callbackTracker, equals(<int>[0, 1, 2, 3, 4, 5])); expect(callbackTracker, equals(<int>[0, 1, 2, 3, 4, 5, 6, 7]));
callbackTracker.clear(); callbackTracker.clear();
final ScrollableState scrollable = tester.state(find.byType(Scrollable)); final ScrollableState scrollable = tester.state(find.byType(Scrollable));
scrollable.position.jumpTo(400.0); // now only 4 should fit, numbered 2-5. scrollable.position.jumpTo(600.0); // now only 4 should fit, numbered 2-5.
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
// We build the visible children to find their new size. // We build the visible children to find their new size.
expect(callbackTracker, equals(<int>[1, 2, 3, 4, 5])); expect(callbackTracker, equals(<int>[1, 2, 3, 4, 5, 6, 7, 8]));
callbackTracker.clear(); callbackTracker.clear();
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
// 0 isn't built because they're not visible. // 0 isn't built because they're not visible.
expect(callbackTracker, equals(<int>[1, 2, 3, 4, 5])); expect(callbackTracker, equals(<int>[1, 2, 3, 4, 5, 6, 7, 8]));
callbackTracker.clear(); callbackTracker.clear();
}); });
...@@ -189,18 +207,24 @@ void main() { ...@@ -189,18 +207,24 @@ void main() {
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
expect(callbackTracker, equals(<int>[0, 1, 2])); expect(callbackTracker, equals(<int>[
0, 1, 2,
3, // in cached area
]));
callbackTracker.clear(); callbackTracker.clear();
tester.allWidgets.forEach(collectText); tester.allWidgets.forEach(collectText);
expect(text, equals(<String>['0', '1', '2'])); expect(text, equals(<String>['0', '1', '2', '3']));
text.clear(); text.clear();
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
expect(callbackTracker, equals(<int>[0, 1, 2])); expect(callbackTracker, equals(<int>[
0, 1, 2,
3, // in cached area
]));
callbackTracker.clear(); callbackTracker.clear();
tester.allWidgets.forEach(collectText); tester.allWidgets.forEach(collectText);
expect(text, equals(<String>['0', '1', '2'])); expect(text, equals(<String>['0', '1', '2', '3']));
text.clear(); text.clear();
}); });
...@@ -308,9 +332,10 @@ void main() { ...@@ -308,9 +332,10 @@ void main() {
' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n' ' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
' │ 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n' ' │ 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n'
' │ crossAxisDirection: AxisDirection.right,\n' ' │ crossAxisDirection: AxisDirection.right,\n'
' │ viewportMainAxisExtent: 600.0)\n' ' │ viewportMainAxisExtent: 600.0, remainingCacheExtent: 850.0\n'
' │ cacheOrigin: 0.0 )\n'
' │ geometry: SliverGeometry(scrollExtent: 300.0, paintExtent: 300.0,\n' ' │ geometry: SliverGeometry(scrollExtent: 300.0, paintExtent: 300.0,\n'
' │ maxPaintExtent: 300.0)\n' ' │ maxPaintExtent: 300.0, cacheExtent: 300.0)\n'
' │ currently live children: 0 to 2\n' ' │ currently live children: 0 to 2\n'
' │\n' ' │\n'
' ├─child with index 0: RenderRepaintBoundary#00000 relayoutBoundary=up2\n' ' ├─child with index 0: RenderRepaintBoundary#00000 relayoutBoundary=up2\n'
......
...@@ -339,7 +339,7 @@ void main() { ...@@ -339,7 +339,7 @@ void main() {
final Offset point1 = tester.getCenter(find.text('AA')); final Offset point1 = tester.getCenter(find.text('AA'));
await tester.dragFrom(point1, const Offset(0.0, 200.0)); await tester.dragFrom(point1, const Offset(0.0, 200.0));
await tester.pump(const Duration(milliseconds: 20)); await tester.pump(const Duration(milliseconds: 20));
final Offset point2 = tester.getCenter(find.text('AA')); final Offset point2 = tester.getCenter(find.text('AA', skipOffstage: false));
expect(point1.dy, greaterThan(point2.dy)); expect(point1.dy, greaterThan(point2.dy));
}); });
...@@ -646,4 +646,4 @@ class TestHeader extends SliverPersistentHeaderDelegate { ...@@ -646,4 +646,4 @@ class TestHeader extends SliverPersistentHeaderDelegate {
} }
@override @override
bool shouldRebuild(TestHeader oldDelegate) => false; bool shouldRebuild(TestHeader oldDelegate) => false;
} }
\ No newline at end of file
...@@ -257,7 +257,7 @@ void main() { ...@@ -257,7 +257,7 @@ void main() {
), ),
)); ));
expect(find.text('Alabama'), findsOneWidget); expect(find.text('Alabama', skipOffstage: false), findsOneWidget);
await tester.pumpWidget(new Directionality( await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
...@@ -362,6 +364,12 @@ void main() { ...@@ -362,6 +364,12 @@ void main() {
label: r'item 1', label: r'item 1',
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
), ),
new TestSemantics(
flags: <SemanticsFlag>[
SemanticsFlag.isHidden,
],
label: r'item 2',
),
], ],
), ),
], ],
......
...@@ -131,7 +131,7 @@ void main() { ...@@ -131,7 +131,7 @@ void main() {
); );
// Item 0 exists in the list and as the prototype item. // Item 0 exists in the list and as the prototype item.
expect(tester.widgetList(find.text('Item 0')).length, 2); expect(tester.widgetList(find.text('Item 0', skipOffstage: false)).length, 2);
for (int i = 1; i < 10; i += 1) for (int i = 1; i < 10; i += 1)
expect(find.text('Item $i'), findsOneWidget); expect(find.text('Item $i'), findsOneWidget);
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment