mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Remove the inner SizeObserver from ScrollableWidgetList.
Adds a HomogeneousViewport class that works like MixedViewport but handles only children that have all the same height. Converts ScrollableWidgetList to use that, so that we don't waste a frame looking at the size of the contents each time we change size. This allows a number of seemingly pointless double-pumps in the tests to be removed. Other changes that were necessary to support the above: - RenderBlock now supports minExtent (think 'min-height' in CSS) - RenderBlock now supports itemExtent (forces the height of each child to be the same, so that the itemExtent passed to the fixed- height scrollables are all authoritative instead of a source of bugs when they don't match) - RenderBlockViewport now supports horizontal scrolling - improved the style of the isInfinite assert in box.dart - fixed the position of a comment in mixed_viewport.dart - added a test - made the logic for how many items to show be more precise
This commit is contained in:
parent
60119d0d4d
commit
c01bb264c7
@ -23,8 +23,10 @@ abstract class RenderBlockBase extends RenderBox with ContainerRenderObjectMixin
|
||||
|
||||
RenderBlockBase({
|
||||
List<RenderBox> children,
|
||||
BlockDirection direction: BlockDirection.vertical
|
||||
}) : _direction = direction {
|
||||
BlockDirection direction: BlockDirection.vertical,
|
||||
double itemExtent,
|
||||
double minExtent: 0.0
|
||||
}) : _direction = direction, _itemExtent = itemExtent, _minExtent = minExtent {
|
||||
addAll(children);
|
||||
}
|
||||
|
||||
@ -42,22 +44,42 @@ abstract class RenderBlockBase extends RenderBox with ContainerRenderObjectMixin
|
||||
}
|
||||
}
|
||||
|
||||
double _itemExtent;
|
||||
double get itemExtent => _itemExtent;
|
||||
void set itemExtent(double value) {
|
||||
if (value != _itemExtent) {
|
||||
_itemExtent = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
}
|
||||
|
||||
double _minExtent;
|
||||
double get minExtent => _minExtent;
|
||||
void set minExtent(double value) {
|
||||
if (value != _minExtent) {
|
||||
_minExtent = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
}
|
||||
|
||||
bool get isVertical => _direction == BlockDirection.vertical;
|
||||
|
||||
BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
|
||||
if (isVertical)
|
||||
return new BoxConstraints.tightFor(width: constraints.constrainWidth(constraints.maxWidth));
|
||||
return new BoxConstraints.tightFor(height: constraints.constrainHeight(constraints.maxHeight));
|
||||
return new BoxConstraints.tightFor(width: constraints.constrainWidth(constraints.maxWidth),
|
||||
height: itemExtent);
|
||||
return new BoxConstraints.tightFor(height: constraints.constrainHeight(constraints.maxHeight),
|
||||
width: itemExtent);
|
||||
}
|
||||
|
||||
double get _mainAxisExtent {
|
||||
RenderBox child = lastChild;
|
||||
if (child == null)
|
||||
return 0.0;
|
||||
return minExtent;
|
||||
BoxParentData parentData = child.parentData;
|
||||
return isVertical ?
|
||||
parentData.position.y + child.size.height :
|
||||
parentData.position.x + child.size.width;
|
||||
math.max(minExtent, parentData.position.y + child.size.height) :
|
||||
math.max(minExtent, parentData.position.x + child.size.width);
|
||||
}
|
||||
|
||||
void performLayout() {
|
||||
@ -84,8 +106,10 @@ class RenderBlock extends RenderBlockBase {
|
||||
|
||||
RenderBlock({
|
||||
List<RenderBox> children,
|
||||
BlockDirection direction: BlockDirection.vertical
|
||||
}) : super(children: children, direction: direction);
|
||||
BlockDirection direction: BlockDirection.vertical,
|
||||
double itemExtent,
|
||||
double minExtent: 0.0
|
||||
}) : super(children: children, direction: direction, itemExtent: itemExtent, minExtent: minExtent);
|
||||
|
||||
double _getIntrinsicCrossAxis(BoxConstraints constraints, _ChildSizingFunction childSize) {
|
||||
double extent = 0.0;
|
||||
@ -116,7 +140,7 @@ class RenderBlock extends RenderBlockBase {
|
||||
assert(child.parentData is BlockParentData);
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
return extent;
|
||||
return math.max(extent, minExtent);
|
||||
}
|
||||
|
||||
double getMinIntrinsicWidth(BoxConstraints constraints) {
|
||||
@ -185,15 +209,17 @@ class RenderBlockViewport extends RenderBlockBase {
|
||||
DimensionCallback totalExtentCallback,
|
||||
DimensionCallback maxCrossAxisDimensionCallback,
|
||||
DimensionCallback minCrossAxisDimensionCallback,
|
||||
BlockDirection direction: BlockDirection.vertical,
|
||||
double itemExtent,
|
||||
double minExtent: 0.0,
|
||||
double startOffset: 0.0,
|
||||
List<RenderBox> children,
|
||||
BlockDirection direction: BlockDirection.vertical
|
||||
List<RenderBox> children
|
||||
}) : _callback = callback,
|
||||
_totalExtentCallback = totalExtentCallback,
|
||||
_maxCrossAxisDimensionCallback = maxCrossAxisDimensionCallback,
|
||||
_minCrossAxisDimensionCallback = minCrossAxisDimensionCallback,
|
||||
_startOffset = startOffset,
|
||||
super(children: children, direction: direction);
|
||||
super(children: children, direction: direction, itemExtent: itemExtent, minExtent: minExtent);
|
||||
|
||||
bool _inCallback = false;
|
||||
|
||||
@ -250,11 +276,10 @@ class RenderBlockViewport extends RenderBlockBase {
|
||||
double _startOffset;
|
||||
double get startOffset => _startOffset;
|
||||
void set startOffset(double value) {
|
||||
if (value == _startOffset)
|
||||
return;
|
||||
_startOffset = value;
|
||||
if (!_inCallback)
|
||||
if (value != _startOffset) {
|
||||
_startOffset = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
}
|
||||
|
||||
double _getIntrinsicDimension(BoxConstraints constraints, DimensionCallback intrinsicCallback, _Constrainer constrainer) {
|
||||
@ -283,13 +308,13 @@ class RenderBlockViewport extends RenderBlockBase {
|
||||
double getMinIntrinsicWidth(BoxConstraints constraints) {
|
||||
if (isVertical)
|
||||
return _getIntrinsicDimension(constraints, minCrossAxisDimensionCallback, constraints.constrainWidth);
|
||||
return constraints.constrainWidth(0.0);
|
||||
return constraints.constrainWidth(minExtent);
|
||||
}
|
||||
|
||||
double getMaxIntrinsicWidth(BoxConstraints constraints) {
|
||||
if (isVertical)
|
||||
return _getIntrinsicDimension(constraints, maxCrossAxisDimensionCallback, constraints.constrainWidth);
|
||||
return _getIntrinsicDimension(constraints, totalExtentCallback, constraints.constrainWidth);
|
||||
return _getIntrinsicDimension(constraints, totalExtentCallback, new BoxConstraints(minWidth: minExtent).apply(constraints).constrainWidth);
|
||||
}
|
||||
|
||||
double getMinIntrinsicHeight(BoxConstraints constraints) {
|
||||
@ -301,7 +326,7 @@ class RenderBlockViewport extends RenderBlockBase {
|
||||
double getMaxIntrinsicHeight(BoxConstraints constraints) {
|
||||
if (!isVertical)
|
||||
return _getIntrinsicDimension(constraints, maxCrossAxisDimensionCallback, constraints.constrainHeight);
|
||||
return _getIntrinsicDimension(constraints, totalExtentCallback, constraints.constrainHeight);
|
||||
return _getIntrinsicDimension(constraints, totalExtentCallback, new BoxConstraints(minHeight: minExtent).apply(constraints).constrainHeight);
|
||||
}
|
||||
|
||||
// We don't override computeDistanceToActualBaseline(), because we
|
||||
@ -325,7 +350,10 @@ class RenderBlockViewport extends RenderBlockBase {
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
context.canvas.save();
|
||||
context.canvas.clipRect(offset & size);
|
||||
defaultPaint(context, offset.translate(0.0, startOffset));
|
||||
if (isVertical)
|
||||
defaultPaint(context, offset.translate(0.0, startOffset));
|
||||
else
|
||||
defaultPaint(context, offset.translate(startOffset, 0.0));
|
||||
context.canvas.restore();
|
||||
}
|
||||
|
||||
|
||||
@ -319,7 +319,10 @@ abstract class RenderBox extends RenderObject {
|
||||
bool debugDoesMeetConstraints() {
|
||||
assert(constraints != null);
|
||||
assert(_size != null);
|
||||
assert(!_size.isInfinite && 'See https://github.com/domokit/sky_engine/blob/master/sky/packages/sky/lib/widgets/sizing.md#user-content-unbounded-constraints' is String);
|
||||
assert(() {
|
||||
'See https://github.com/domokit/sky_engine/blob/master/sky/packages/sky/lib/widgets/sizing.md#user-content-unbounded-constraints';
|
||||
return !_size.isInfinite;
|
||||
});
|
||||
bool result = constraints.contains(_size);
|
||||
if (!result)
|
||||
print("${this.runtimeType} does not meet its constraints. Constraints: $constraints, size: $_size");
|
||||
|
||||
@ -24,6 +24,7 @@ export 'package:sky/widgets/floating_action_button.dart';
|
||||
export 'package:sky/widgets/focus.dart';
|
||||
export 'package:sky/widgets/framework.dart';
|
||||
export 'package:sky/widgets/gesture_detector.dart';
|
||||
export 'package:sky/widgets/homogeneous_viewport.dart';
|
||||
export 'package:sky/widgets/icon.dart';
|
||||
export 'package:sky/widgets/icon_button.dart';
|
||||
export 'package:sky/widgets/ink_well.dart';
|
||||
|
||||
179
sky/packages/sky/lib/widgets/homogeneous_viewport.dart
Normal file
179
sky/packages/sky/lib/widgets/homogeneous_viewport.dart
Normal file
@ -0,0 +1,179 @@
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:sky/rendering/block.dart';
|
||||
import 'package:sky/rendering/box.dart';
|
||||
import 'package:sky/rendering/object.dart';
|
||||
import 'package:sky/widgets/framework.dart';
|
||||
import 'package:sky/widgets/basic.dart';
|
||||
|
||||
typedef List<Widget> ListBuilder(int startIndex, int count);
|
||||
|
||||
class HomogeneousViewport extends RenderObjectWrapper {
|
||||
HomogeneousViewport({
|
||||
Key key,
|
||||
this.builder,
|
||||
this.itemExtent, // required
|
||||
this.itemCount, // optional, but you cannot shrink-wrap this class or otherwise use its intrinsic dimensions if you don't specify it
|
||||
this.direction: ScrollDirection.vertical,
|
||||
this.startOffset: 0.0
|
||||
}) : super(key: key) {
|
||||
assert(itemExtent != null);
|
||||
}
|
||||
|
||||
ListBuilder builder;
|
||||
double itemExtent;
|
||||
int itemCount;
|
||||
ScrollDirection direction;
|
||||
double startOffset;
|
||||
|
||||
bool _layoutDirty = true;
|
||||
List<Widget> _children;
|
||||
|
||||
RenderBlockViewport get renderObject => super.renderObject;
|
||||
|
||||
RenderBlockViewport createNode() {
|
||||
// we don't pass constructor arguments to the RenderBlockViewport() because until
|
||||
// we know our children, the constructor arguments we could give have no effect
|
||||
RenderBlockViewport result = new RenderBlockViewport();
|
||||
result.callback = layout;
|
||||
result.totalExtentCallback = getTotalExtent;
|
||||
result.minCrossAxisDimensionCallback = getMinCrossAxisDimension;
|
||||
result.maxCrossAxisDimensionCallback = getMaxCrossAxisDimension;
|
||||
return result;
|
||||
}
|
||||
|
||||
void remove() {
|
||||
renderObject.callback = null;
|
||||
renderObject.totalExtentCallback = null;
|
||||
renderObject.minCrossAxisDimensionCallback = null;
|
||||
renderObject.maxCrossAxisDimensionCallback = null;
|
||||
super.remove();
|
||||
_children.clear();
|
||||
_layoutDirty = true;
|
||||
}
|
||||
|
||||
void walkChildren(WidgetTreeWalker walker) {
|
||||
if (_children == null) return;
|
||||
for (Widget child in _children)
|
||||
walker(child);
|
||||
}
|
||||
|
||||
void insertChildRenderObject(RenderObjectWrapper child, Widget slot) {
|
||||
RenderObject nextSibling = slot != null ? slot.renderObject : null;
|
||||
renderObject.add(child.renderObject, before: nextSibling);
|
||||
}
|
||||
|
||||
void detachChildRenderObject(RenderObjectWrapper child) {
|
||||
renderObject.remove(child.renderObject);
|
||||
}
|
||||
|
||||
bool retainStatefulNodeIfPossible(HomogeneousViewport newNode) {
|
||||
retainStatefulRenderObjectWrapper(newNode);
|
||||
if (startOffset != newNode.startOffset) {
|
||||
_layoutDirty = true;
|
||||
startOffset = newNode.startOffset;
|
||||
}
|
||||
if (itemCount != newNode.itemCount) {
|
||||
_layoutDirty = true;
|
||||
itemCount = newNode.itemCount;
|
||||
}
|
||||
if (itemExtent != newNode.itemExtent) {
|
||||
_layoutDirty = true;
|
||||
itemExtent = newNode.itemExtent;
|
||||
}
|
||||
if (direction != newNode.direction) {
|
||||
_layoutDirty = true;
|
||||
direction = newNode.direction;
|
||||
}
|
||||
if (builder != newNode.builder) {
|
||||
_layoutDirty = true;
|
||||
builder = newNode.builder;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// This is called during the regular component build
|
||||
void syncRenderObject(HomogeneousViewport old) {
|
||||
super.syncRenderObject(old);
|
||||
if (_layoutDirty) {
|
||||
renderObject.markNeedsLayout();
|
||||
} else {
|
||||
assert(old != null); // if old was null, we'd be new, and therefore _layoutDirty would be true
|
||||
_updateChildren();
|
||||
}
|
||||
}
|
||||
|
||||
int _layoutFirstIndex;
|
||||
int _layoutItemCount;
|
||||
|
||||
void layout(BoxConstraints constraints) {
|
||||
LayoutCallbackBuilderHandle handle = enterLayoutCallbackBuilder();
|
||||
try {
|
||||
double mainAxisExtent = direction == ScrollDirection.vertical ? constraints.maxHeight : constraints.maxWidth;
|
||||
double offset;
|
||||
if (startOffset <= 0.0) {
|
||||
_layoutFirstIndex = 0;
|
||||
offset = -startOffset;
|
||||
} else {
|
||||
_layoutFirstIndex = startOffset ~/ itemExtent;
|
||||
offset = -(startOffset % itemExtent);
|
||||
}
|
||||
if (mainAxisExtent < double.INFINITY) {
|
||||
_layoutItemCount = ((mainAxisExtent - offset) / itemExtent).ceil();
|
||||
if (itemCount != null)
|
||||
_layoutItemCount = math.min(_layoutItemCount, itemCount - _layoutFirstIndex);
|
||||
} else {
|
||||
assert(() {
|
||||
'This HomogeneousViewport has no specified number of items (meaning it has infinite items), ' +
|
||||
'and has been placed in an unconstrained environment where all items can be rendered. ' +
|
||||
'It is most likely that you have placed your HomogeneousViewport (which is an internal ' +
|
||||
'component of several scrollable widgets) inside either another scrolling box, a flexible ' +
|
||||
'box (Row, Column), or a Stack, without giving it a specific size.';
|
||||
return itemCount != null;
|
||||
});
|
||||
_layoutItemCount = itemCount - _layoutFirstIndex;
|
||||
}
|
||||
_layoutItemCount = math.max(0, _layoutItemCount);
|
||||
_updateChildren();
|
||||
// Update the renderObject configuration
|
||||
renderObject.direction = direction == ScrollDirection.vertical ? BlockDirection.vertical : BlockDirection.horizontal;
|
||||
renderObject.itemExtent = itemExtent;
|
||||
renderObject.minExtent = getTotalExtent(null);
|
||||
renderObject.startOffset = offset;
|
||||
} finally {
|
||||
exitLayoutCallbackBuilder(handle);
|
||||
}
|
||||
}
|
||||
|
||||
void _updateChildren() {
|
||||
assert(_layoutFirstIndex != null);
|
||||
assert(_layoutItemCount != null);
|
||||
List<Widget> newChildren;
|
||||
if (_layoutItemCount > 0)
|
||||
newChildren = builder(_layoutFirstIndex, _layoutItemCount);
|
||||
else
|
||||
newChildren = <Widget>[];
|
||||
syncChildren(newChildren, _children == null ? <Widget>[] : _children);
|
||||
_children = newChildren;
|
||||
}
|
||||
|
||||
double getTotalExtent(BoxConstraints constraints) {
|
||||
// constraints is null when called by layout() above
|
||||
return itemCount != null ? itemCount * itemExtent : double.INFINITY;
|
||||
}
|
||||
|
||||
double getMinCrossAxisDimension(BoxConstraints constraints) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double getMaxCrossAxisDimension(BoxConstraints constraints) {
|
||||
if (direction == ScrollDirection.vertical)
|
||||
return constraints.maxWidth;
|
||||
return constraints.maxHeight;
|
||||
}
|
||||
|
||||
}
|
||||
@ -91,11 +91,11 @@ class MixedViewport extends RenderObjectWrapper {
|
||||
|
||||
Map<_Key, Widget> _childrenByKey = new Map<_Key, Widget>();
|
||||
|
||||
// we don't pass the direction or offset to the render object when we create it, because
|
||||
// the render object is empty so it will not matter
|
||||
RenderBlockViewport get renderObject => super.renderObject;
|
||||
|
||||
RenderBlockViewport createNode() {
|
||||
// we don't pass the direction or offset to the render object when we
|
||||
// create it, because the render object is empty so it will not matter
|
||||
RenderBlockViewport result = new RenderBlockViewport();
|
||||
result.callback = layout;
|
||||
result.totalExtentCallback = _noIntrinsicDimensions;
|
||||
|
||||
@ -18,6 +18,7 @@ import 'package:sky/rendering/viewport.dart';
|
||||
import 'package:sky/widgets/basic.dart';
|
||||
import 'package:sky/widgets/framework.dart';
|
||||
import 'package:sky/widgets/gesture_detector.dart';
|
||||
import 'package:sky/widgets/homogeneous_viewport.dart';
|
||||
import 'package:sky/widgets/mixed_viewport.dart';
|
||||
|
||||
export 'package:sky/widgets/mixed_viewport.dart' show MixedViewportLayoutState;
|
||||
@ -421,17 +422,11 @@ abstract class ScrollableWidgetList extends Scrollable {
|
||||
double contentExtent = itemExtent * itemCount;
|
||||
if (padding != null)
|
||||
contentExtent += _leadingPadding + _trailingPadding;
|
||||
|
||||
scrollTo(scrollBehavior.updateExtents(
|
||||
contentExtent: contentExtent,
|
||||
containerExtent: _containerExtent,
|
||||
scrollOffset: scrollOffset));
|
||||
}
|
||||
|
||||
Offset _toOffset(double value) {
|
||||
return scrollDirection == ScrollDirection.vertical
|
||||
? new Offset(0.0, value)
|
||||
: new Offset(value, 0.0);
|
||||
scrollOffset: scrollOffset
|
||||
));
|
||||
}
|
||||
|
||||
Widget buildContent() {
|
||||
@ -440,48 +435,27 @@ abstract class ScrollableWidgetList extends Scrollable {
|
||||
_updateScrollBehavior();
|
||||
}
|
||||
|
||||
double paddedScrollOffset = scrollOffset - _leadingPadding;
|
||||
int itemShowIndex = 0;
|
||||
int itemShowCount = 0;
|
||||
Offset viewportOffset = Offset.zero;
|
||||
|
||||
if (_containerExtent != null && _containerExtent > 0.0 && itemCount > 0) {
|
||||
if (paddedScrollOffset < scrollBehavior.minScrollOffset) {
|
||||
// Underscroll
|
||||
double visibleExtent = _containerExtent + paddedScrollOffset;
|
||||
itemShowCount = (visibleExtent / itemExtent).ceil();
|
||||
viewportOffset = _toOffset(paddedScrollOffset);
|
||||
} else {
|
||||
itemShowCount = (_containerExtent / itemExtent).ceil() + 1;
|
||||
itemShowIndex = (paddedScrollOffset / itemExtent).floor();
|
||||
viewportOffset = _toOffset(paddedScrollOffset - itemShowIndex * itemExtent);
|
||||
itemShowIndex %= itemCount; // Wrap index for when itemWrap is true.
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> items = buildItems(itemShowIndex, itemShowCount);
|
||||
assert(items.every((item) => item.key != null));
|
||||
|
||||
BlockDirection blockDirection = scrollDirection == ScrollDirection.vertical
|
||||
? BlockDirection.vertical
|
||||
: BlockDirection.horizontal;
|
||||
|
||||
// TODO(ianh): Refactor this so that it does the building in the
|
||||
// same frame as the size observing, similar to MixedViewport, but
|
||||
// keeping the fixed-height optimisations.
|
||||
return new SizeObserver(
|
||||
callback: _handleSizeChanged,
|
||||
child: new Viewport(
|
||||
scrollDirection: scrollDirection,
|
||||
scrollOffset: viewportOffset,
|
||||
child: new Container(
|
||||
padding: _crossAxisPadding,
|
||||
child: new BlockBody(items, direction: blockDirection)
|
||||
child: new Container(
|
||||
padding: _crossAxisPadding,
|
||||
child: new HomogeneousViewport(
|
||||
builder: _buildItems,
|
||||
itemExtent: itemExtent,
|
||||
itemCount: itemCount,
|
||||
direction: scrollDirection,
|
||||
startOffset: scrollOffset - _leadingPadding
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildItems(int start, int count) {
|
||||
List<Widget> result = buildItems(start, count);
|
||||
assert(result.every((item) => item.key != null));
|
||||
return result;
|
||||
}
|
||||
|
||||
List<Widget> buildItems(int start, int count);
|
||||
|
||||
}
|
||||
|
||||
@ -22,19 +22,13 @@ void main() {
|
||||
]);
|
||||
}
|
||||
|
||||
tester.pumpFrame(builder);
|
||||
// TODO(abarth): We shouldn't need to pump a second frame here.
|
||||
tester.pumpFrame(builder);
|
||||
|
||||
expect(currentValue, isNull);
|
||||
tester.tap(tester.findText('2015'));
|
||||
tester.pumpFrame(builder);
|
||||
// TODO(jackson): We shouldn't need to pump a second frame here.
|
||||
tester.pumpFrame(builder);
|
||||
tester.tap(tester.findText('2014'));
|
||||
tester.pumpFrame(builder);
|
||||
// TODO(jackson): We shouldn't need to pump a second frame here.
|
||||
tester.pumpFrame(builder);
|
||||
expect(currentValue, equals(new DateTime(2014, 6, 9)));
|
||||
tester.tap(tester.findText('30'));
|
||||
expect(currentValue, equals(new DateTime(2013, 1, 30)));
|
||||
|
||||
170
sky/unit/test/widget/homogeneous_viewport_test.dart
Normal file
170
sky/unit/test/widget/homogeneous_viewport_test.dart
Normal file
@ -0,0 +1,170 @@
|
||||
import 'package:sky/widgets.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'widget_tester.dart';
|
||||
|
||||
class TestComponent extends StatefulComponent {
|
||||
TestComponent(this.viewport);
|
||||
HomogeneousViewport viewport;
|
||||
void syncConstructorArguments(TestComponent source) {
|
||||
viewport = source.viewport;
|
||||
}
|
||||
bool _flag = true;
|
||||
void go(bool flag) {
|
||||
setState(() {
|
||||
_flag = flag;
|
||||
});
|
||||
}
|
||||
Widget build() {
|
||||
return _flag ? viewport : new Text('Not Today');
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
test('HomogeneousViewport mount/dismount smoke test', () {
|
||||
WidgetTester tester = new WidgetTester();
|
||||
|
||||
List<int> callbackTracker = <int>[];
|
||||
|
||||
// the root view is 800x600 in the test environment
|
||||
// so if our widget is 100 pixels tall, it should fit exactly 6 times.
|
||||
|
||||
TestComponent testComponent;
|
||||
Widget builder() {
|
||||
testComponent = new TestComponent(new HomogeneousViewport(
|
||||
builder: (int start, int count) {
|
||||
List<Widget> result = <Widget>[];
|
||||
for (int index = start; index < start + count; index += 1) {
|
||||
callbackTracker.add(index);
|
||||
result.add(new Container(
|
||||
key: new ValueKey<int>(index),
|
||||
height: 100.0,
|
||||
child: new Text("$index")
|
||||
));
|
||||
}
|
||||
return result;
|
||||
},
|
||||
startOffset: 0.0,
|
||||
itemExtent: 100.0
|
||||
));
|
||||
return testComponent;
|
||||
}
|
||||
|
||||
tester.pumpFrame(builder);
|
||||
|
||||
expect(callbackTracker, equals([0, 1, 2, 3, 4, 5]));
|
||||
|
||||
callbackTracker.clear();
|
||||
testComponent.go(false);
|
||||
tester.pumpFrameWithoutChange();
|
||||
|
||||
expect(callbackTracker, equals([]));
|
||||
|
||||
callbackTracker.clear();
|
||||
testComponent.go(true);
|
||||
tester.pumpFrameWithoutChange();
|
||||
|
||||
expect(callbackTracker, equals([0, 1, 2, 3, 4, 5]));
|
||||
});
|
||||
|
||||
test('HomogeneousViewport vertical', () {
|
||||
WidgetTester tester = new WidgetTester();
|
||||
|
||||
List<int> callbackTracker = <int>[];
|
||||
|
||||
// the root view is 800x600 in the test environment
|
||||
// so if our widget is 200 pixels tall, it should fit exactly 3 times.
|
||||
// but if we are offset by 300 pixels, there will be 4, numbered 1-4.
|
||||
|
||||
double offset = 300.0;
|
||||
|
||||
ListBuilder itemBuilder = (int start, int count) {
|
||||
List<Widget> result = <Widget>[];
|
||||
for (int index = start; index < start + count; index += 1) {
|
||||
callbackTracker.add(index);
|
||||
result.add(new Container(
|
||||
key: new ValueKey<int>(index),
|
||||
width: 500.0, // this should be ignored
|
||||
height: 400.0, // should be overridden by itemExtent
|
||||
child: new Text("$index")
|
||||
));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
TestComponent testComponent;
|
||||
Widget builder() {
|
||||
testComponent = new TestComponent(new HomogeneousViewport(
|
||||
builder: itemBuilder,
|
||||
startOffset: offset,
|
||||
itemExtent: 200.0
|
||||
));
|
||||
return testComponent;
|
||||
}
|
||||
|
||||
tester.pumpFrame(builder);
|
||||
|
||||
expect(callbackTracker, equals([1, 2, 3, 4]));
|
||||
|
||||
callbackTracker.clear();
|
||||
|
||||
offset = 400.0; // now only 3 should fit, numbered 2-4.
|
||||
|
||||
tester.pumpFrame(builder);
|
||||
|
||||
expect(callbackTracker, equals([2, 3, 4]));
|
||||
|
||||
callbackTracker.clear();
|
||||
});
|
||||
|
||||
test('HomogeneousViewport horizontal', () {
|
||||
WidgetTester tester = new WidgetTester();
|
||||
|
||||
List<int> callbackTracker = <int>[];
|
||||
|
||||
// the root view is 800x600 in the test environment
|
||||
// so if our widget is 200 pixels wide, it should fit exactly 4 times.
|
||||
// but if we are offset by 300 pixels, there will be 5, numbered 1-5.
|
||||
|
||||
double offset = 300.0;
|
||||
|
||||
ListBuilder itemBuilder = (int start, int count) {
|
||||
List<Widget> result = <Widget>[];
|
||||
for (int index = start; index < start + count; index += 1) {
|
||||
callbackTracker.add(index);
|
||||
result.add(new Container(
|
||||
key: new ValueKey<int>(index),
|
||||
width: 400.0, // this should be overridden by itemExtent
|
||||
height: 500.0, // this should be ignored
|
||||
child: new Text("$index")
|
||||
));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
TestComponent testComponent;
|
||||
Widget builder() {
|
||||
testComponent = new TestComponent(new HomogeneousViewport(
|
||||
builder: itemBuilder,
|
||||
startOffset: offset,
|
||||
itemExtent: 200.0,
|
||||
direction: ScrollDirection.horizontal
|
||||
));
|
||||
return testComponent;
|
||||
}
|
||||
|
||||
tester.pumpFrame(builder);
|
||||
|
||||
expect(callbackTracker, equals([1, 2, 3, 4, 5]));
|
||||
|
||||
callbackTracker.clear();
|
||||
|
||||
offset = 400.0; // now only 4 should fit, numbered 2-5.
|
||||
|
||||
tester.pumpFrame(builder);
|
||||
|
||||
expect(callbackTracker, equals([2, 3, 4, 5]));
|
||||
|
||||
callbackTracker.clear();
|
||||
});
|
||||
}
|
||||
@ -37,8 +37,6 @@ void main() {
|
||||
);
|
||||
}
|
||||
|
||||
tester.pumpFrame(builder);
|
||||
// TODO(abarth): We shouldn't need to pump a second frame here.
|
||||
tester.pumpFrame(builder);
|
||||
|
||||
expect(currentPage, isNull);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user