Merge pull request #1642 from abarth/rm_viewport
Remove HomogeneousViewport
This commit is contained in:
@@ -268,15 +268,15 @@ class DayPicker extends StatelessComponent {
|
||||
}
|
||||
}
|
||||
|
||||
// Scrollable list of DayPickers to allow choosing a month
|
||||
class MonthPicker extends ScrollableWidgetList {
|
||||
class MonthPicker extends StatefulComponent {
|
||||
MonthPicker({
|
||||
Key key,
|
||||
this.selectedDate,
|
||||
this.onChanged,
|
||||
this.firstDate,
|
||||
this.lastDate,
|
||||
double itemExtent
|
||||
}) : super(itemExtent: itemExtent) {
|
||||
this.itemExtent
|
||||
}) : super(key: key) {
|
||||
assert(selectedDate != null);
|
||||
assert(onChanged != null);
|
||||
assert(lastDate.isAfter(firstDate));
|
||||
@@ -286,11 +286,12 @@ class MonthPicker extends ScrollableWidgetList {
|
||||
final ValueChanged<DateTime> onChanged;
|
||||
final DateTime firstDate;
|
||||
final DateTime lastDate;
|
||||
final double itemExtent;
|
||||
|
||||
_MonthPickerState createState() => new _MonthPickerState();
|
||||
}
|
||||
|
||||
class _MonthPickerState extends ScrollableWidgetListState<MonthPicker> {
|
||||
class _MonthPickerState extends State<MonthPicker> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_updateCurrentDate();
|
||||
@@ -313,8 +314,6 @@ class _MonthPickerState extends ScrollableWidgetListState<MonthPicker> {
|
||||
});
|
||||
}
|
||||
|
||||
int get itemCount => (config.lastDate.year - config.firstDate.year) * 12 + config.lastDate.month - config.firstDate.month + 1;
|
||||
|
||||
List<Widget> buildItems(BuildContext context, int start, int count) {
|
||||
List<Widget> result = new List<Widget>();
|
||||
DateTime startDate = new DateTime(config.firstDate.year + start ~/ 12, config.firstDate.month + start % 12);
|
||||
@@ -335,6 +334,14 @@ class _MonthPickerState extends ScrollableWidgetListState<MonthPicker> {
|
||||
return result;
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return new ScrollableLazyList(
|
||||
itemExtent: config.itemExtent,
|
||||
itemCount: (config.lastDate.year - config.firstDate.year) * 12 + config.lastDate.month - config.firstDate.month + 1,
|
||||
itemBuilder: buildItems
|
||||
);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
if (_timer != null)
|
||||
_timer.cancel();
|
||||
@@ -343,13 +350,14 @@ class _MonthPickerState extends ScrollableWidgetListState<MonthPicker> {
|
||||
}
|
||||
|
||||
// Scrollable list of years to allow picking a year
|
||||
class YearPicker extends ScrollableWidgetList {
|
||||
class YearPicker extends StatefulComponent {
|
||||
YearPicker({
|
||||
Key key,
|
||||
this.selectedDate,
|
||||
this.onChanged,
|
||||
this.firstDate,
|
||||
this.lastDate
|
||||
}) : super(itemExtent: 50.0) {
|
||||
}) : super(key: key) {
|
||||
assert(selectedDate != null);
|
||||
assert(onChanged != null);
|
||||
assert(lastDate.isAfter(firstDate));
|
||||
@@ -363,8 +371,8 @@ class YearPicker extends ScrollableWidgetList {
|
||||
_YearPickerState createState() => new _YearPickerState();
|
||||
}
|
||||
|
||||
class _YearPickerState extends ScrollableWidgetListState<YearPicker> {
|
||||
int get itemCount => config.lastDate.year - config.firstDate.year + 1;
|
||||
class _YearPickerState extends State<YearPicker> {
|
||||
static const double _itemExtent = 50.0;
|
||||
|
||||
List<Widget> buildItems(BuildContext context, int start, int count) {
|
||||
TextStyle style = Theme.of(context).text.body1.copyWith(color: Colors.black54);
|
||||
@@ -379,7 +387,7 @@ class _YearPickerState extends ScrollableWidgetListState<YearPicker> {
|
||||
config.onChanged(result);
|
||||
},
|
||||
child: new Container(
|
||||
height: config.itemExtent,
|
||||
height: _itemExtent,
|
||||
decoration: year == config.selectedDate.year ? new BoxDecoration(
|
||||
backgroundColor: Theme.of(context).primarySwatch[100],
|
||||
shape: BoxShape.circle
|
||||
@@ -393,4 +401,12 @@ class _YearPickerState extends ScrollableWidgetListState<YearPicker> {
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return new ScrollableLazyList(
|
||||
itemExtent: _itemExtent,
|
||||
itemCount: config.lastDate.year - config.firstDate.year + 1,
|
||||
itemBuilder: buildItems
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,6 +344,8 @@ class RenderGrid extends RenderVirtualViewport<GridParentData> {
|
||||
_delegate = newDelegate;
|
||||
}
|
||||
|
||||
int get virtualChildCount => super.virtualChildCount ?? childCount;
|
||||
|
||||
/// The virtual index of the first child.
|
||||
///
|
||||
/// When asking the delegate for the position of each child, the grid will add
|
||||
|
||||
@@ -76,7 +76,10 @@ class RenderList extends RenderVirtualViewport<ListParentData> implements HasScr
|
||||
double get _preferredExtent {
|
||||
if (itemExtent == null)
|
||||
return double.INFINITY;
|
||||
double extent = itemExtent * virtualChildCount;
|
||||
int count = virtualChildCount;
|
||||
if (count == null)
|
||||
return double.INFINITY;
|
||||
double extent = itemExtent * count;
|
||||
if (padding != null)
|
||||
extent += _scrollAxisPadding;
|
||||
return extent;
|
||||
|
||||
@@ -192,7 +192,7 @@ abstract class RenderVirtualViewport<T extends ContainerBoxParentDataMixin<Rende
|
||||
_callback = callback,
|
||||
_overlayPainter = overlayPainter;
|
||||
|
||||
int get virtualChildCount => _virtualChildCount ?? childCount;
|
||||
int get virtualChildCount => _virtualChildCount;
|
||||
int _virtualChildCount;
|
||||
void set virtualChildCount(int value) {
|
||||
if (_virtualChildCount == value)
|
||||
|
||||
@@ -1,220 +0,0 @@
|
||||
// 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:flutter/rendering.dart';
|
||||
|
||||
import 'framework.dart';
|
||||
import 'basic.dart';
|
||||
|
||||
typedef List<Widget> ListBuilder(BuildContext context, int startIndex, int count);
|
||||
|
||||
abstract class _ViewportBase extends RenderObjectWidget {
|
||||
_ViewportBase({
|
||||
Key key,
|
||||
this.builder,
|
||||
this.itemsWrap: false,
|
||||
this.itemCount,
|
||||
this.direction: Axis.vertical,
|
||||
this.startOffset: 0.0,
|
||||
this.overlayPainter
|
||||
}) : super(key: key);
|
||||
|
||||
final ListBuilder builder;
|
||||
final bool itemsWrap;
|
||||
final int itemCount;
|
||||
final Axis direction;
|
||||
final double startOffset;
|
||||
final Painter overlayPainter;
|
||||
|
||||
// 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 createRenderObject() => new RenderBlockViewport();
|
||||
|
||||
bool isLayoutDifferentThan(_ViewportBase oldWidget) {
|
||||
// changing the builder doesn't imply the layout changed
|
||||
return itemsWrap != oldWidget.itemsWrap ||
|
||||
itemCount != oldWidget.itemCount ||
|
||||
direction != oldWidget.direction ||
|
||||
startOffset != oldWidget.startOffset;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _ViewportBaseElement<T extends _ViewportBase> extends RenderObjectElement<T> {
|
||||
_ViewportBaseElement(T widget) : super(widget);
|
||||
|
||||
List<Element> _children = const <Element>[];
|
||||
int _layoutFirstIndex;
|
||||
int _layoutItemCount;
|
||||
|
||||
RenderBlockViewport get renderObject => super.renderObject;
|
||||
|
||||
void visitChildren(ElementVisitor visitor) {
|
||||
if (_children == null)
|
||||
return;
|
||||
for (Element child in _children)
|
||||
visitor(child);
|
||||
}
|
||||
|
||||
void mount(Element parent, dynamic newSlot) {
|
||||
super.mount(parent, newSlot);
|
||||
renderObject.callback = layout;
|
||||
renderObject.totalExtentCallback = getTotalExtent;
|
||||
renderObject.minCrossAxisExtentCallback = getMinCrossAxisExtent;
|
||||
renderObject.maxCrossAxisExtentCallback = getMaxCrossAxisExtent;
|
||||
renderObject.overlayPainter = widget.overlayPainter;
|
||||
}
|
||||
|
||||
void unmount() {
|
||||
renderObject.callback = null;
|
||||
renderObject.totalExtentCallback = null;
|
||||
renderObject.minCrossAxisExtentCallback = null;
|
||||
renderObject.maxCrossAxisExtentCallback = null;
|
||||
renderObject.overlayPainter = null;
|
||||
super.unmount();
|
||||
}
|
||||
|
||||
void update(T newWidget) {
|
||||
bool needLayout = newWidget.isLayoutDifferentThan(widget);
|
||||
super.update(newWidget);
|
||||
// TODO(abarth): Don't we need to update overlayPainter here?
|
||||
if (needLayout)
|
||||
renderObject.markNeedsLayout();
|
||||
else
|
||||
_updateChildren();
|
||||
}
|
||||
|
||||
void reinvokeBuilders() {
|
||||
_updateChildren();
|
||||
}
|
||||
|
||||
void layout(BoxConstraints constraints);
|
||||
|
||||
void _updateChildren() {
|
||||
assert(_layoutFirstIndex != null);
|
||||
assert(_layoutItemCount != null);
|
||||
List<Widget> newWidgets;
|
||||
if (_layoutItemCount > 0)
|
||||
newWidgets = widget.builder(this, _layoutFirstIndex, _layoutItemCount).map((Widget widget) {
|
||||
return new RepaintBoundary(key: new ValueKey<Key>(widget.key), child: widget);
|
||||
}).toList();
|
||||
else
|
||||
newWidgets = <Widget>[];
|
||||
_children = updateChildren(_children, newWidgets);
|
||||
}
|
||||
|
||||
double getTotalExtent(BoxConstraints constraints);
|
||||
|
||||
double getMinCrossAxisExtent(BoxConstraints constraints) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double getMaxCrossAxisExtent(BoxConstraints constraints) {
|
||||
if (widget.direction == Axis.vertical)
|
||||
return constraints.maxWidth;
|
||||
return constraints.maxHeight;
|
||||
}
|
||||
|
||||
void insertChildRenderObject(RenderObject child, Element slot) {
|
||||
renderObject.insert(child, after: slot?.renderObject);
|
||||
}
|
||||
|
||||
void moveChildRenderObject(RenderObject child, Element slot) {
|
||||
assert(child.parent == renderObject);
|
||||
renderObject.move(child, after: slot?.renderObject);
|
||||
}
|
||||
|
||||
void removeChildRenderObject(RenderObject child) {
|
||||
assert(child.parent == renderObject);
|
||||
renderObject.remove(child);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class HomogeneousViewport extends _ViewportBase {
|
||||
HomogeneousViewport({
|
||||
Key key,
|
||||
ListBuilder builder,
|
||||
bool itemsWrap: false,
|
||||
int itemCount, // optional, but you cannot shrink-wrap this class or otherwise use its intrinsic dimensions if you don't specify it
|
||||
Axis direction: Axis.vertical,
|
||||
double startOffset: 0.0,
|
||||
Painter overlayPainter,
|
||||
this.itemExtent // required, must be non-zero
|
||||
}) : super(
|
||||
key: key,
|
||||
builder: builder,
|
||||
itemsWrap: itemsWrap,
|
||||
itemCount: itemCount,
|
||||
direction: direction,
|
||||
startOffset: startOffset,
|
||||
overlayPainter: overlayPainter
|
||||
) {
|
||||
assert(itemExtent != null);
|
||||
assert(itemExtent > 0);
|
||||
}
|
||||
|
||||
final double itemExtent;
|
||||
|
||||
_HomogeneousViewportElement createElement() => new _HomogeneousViewportElement(this);
|
||||
|
||||
bool isLayoutDifferentThan(HomogeneousViewport oldWidget) {
|
||||
return itemExtent != oldWidget.itemExtent || super.isLayoutDifferentThan(oldWidget);
|
||||
}
|
||||
}
|
||||
|
||||
class _HomogeneousViewportElement extends _ViewportBaseElement<HomogeneousViewport> {
|
||||
_HomogeneousViewportElement(HomogeneousViewport widget) : super(widget);
|
||||
|
||||
void layout(BoxConstraints constraints) {
|
||||
// We enter a build scope (meaning that markNeedsBuild() is forbidden)
|
||||
// because we are in the middle of layout and if we allowed people to set
|
||||
// state, they'd expect to have that state reflected immediately, which, if
|
||||
// we were to try to honour it, would potentially result in assertions
|
||||
// because you can't normally mutate the render object tree during layout.
|
||||
// (If there were a way to limit these writes to descendants of this, it'd
|
||||
// be ok because we are exempt from that assert since we are still actively
|
||||
// doing our own layout.)
|
||||
BuildableElement.lockState(() {
|
||||
double mainAxisExtent = widget.direction == Axis.vertical ? constraints.maxHeight : constraints.maxWidth;
|
||||
double offset;
|
||||
if (widget.startOffset <= 0.0 && !widget.itemsWrap) {
|
||||
_layoutFirstIndex = 0;
|
||||
offset = -widget.startOffset;
|
||||
} else {
|
||||
_layoutFirstIndex = (widget.startOffset / widget.itemExtent).floor();
|
||||
offset = -(widget.startOffset % widget.itemExtent);
|
||||
}
|
||||
if (mainAxisExtent < double.INFINITY) {
|
||||
_layoutItemCount = ((mainAxisExtent - offset) / widget.itemExtent).ceil();
|
||||
if (widget.itemCount != null && !widget.itemsWrap)
|
||||
_layoutItemCount = math.min(_layoutItemCount, widget.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 widget.itemCount != null;
|
||||
});
|
||||
_layoutItemCount = widget.itemCount - _layoutFirstIndex;
|
||||
}
|
||||
_layoutItemCount = math.max(0, _layoutItemCount);
|
||||
_updateChildren();
|
||||
// Update the renderObject configuration
|
||||
renderObject.direction = widget.direction;
|
||||
renderObject.itemExtent = widget.itemExtent;
|
||||
renderObject.minExtent = getTotalExtent(null);
|
||||
renderObject.startOffset = offset;
|
||||
renderObject.overlayPainter = widget.overlayPainter;
|
||||
}, building: true);
|
||||
}
|
||||
|
||||
double getTotalExtent(BoxConstraints constraints) {
|
||||
// constraints is null when called by layout() above
|
||||
return widget.itemCount != null ? widget.itemCount * widget.itemExtent : double.INFINITY;
|
||||
}
|
||||
}
|
||||
@@ -180,9 +180,8 @@ class PageableListState<T extends PageableList> extends ScrollableState<T> {
|
||||
}
|
||||
}
|
||||
|
||||
class PageViewport extends VirtualViewport {
|
||||
class PageViewport extends VirtualViewport with VirtualViewportIterableMixin {
|
||||
PageViewport({
|
||||
Key key,
|
||||
this.startOffset: 0.0,
|
||||
this.scrollDirection: Axis.vertical,
|
||||
this.itemsWrap: false,
|
||||
|
||||
@@ -14,7 +14,6 @@ import 'package:flutter/rendering.dart';
|
||||
import 'basic.dart';
|
||||
import 'framework.dart';
|
||||
import 'gesture_detector.dart';
|
||||
import 'homogeneous_viewport.dart';
|
||||
import 'mixed_viewport.dart';
|
||||
import 'notification_listener.dart';
|
||||
import 'page_storage.dart';
|
||||
@@ -597,178 +596,6 @@ abstract class ScrollableListPainter extends Painter {
|
||||
Future scrollEnded() => new Future.value();
|
||||
}
|
||||
|
||||
/// An optimized scrollable widget for a large number of children that are all
|
||||
/// the same size (extent) in the scrollDirection. For example for
|
||||
/// ScrollDirection.vertical itemExtent is the height of each item. Use this
|
||||
/// widget when you have a large number of children or when you are concerned
|
||||
// about offscreen widgets consuming resources.
|
||||
abstract class ScrollableWidgetList extends Scrollable {
|
||||
ScrollableWidgetList({
|
||||
Key key,
|
||||
double initialScrollOffset,
|
||||
Axis scrollDirection: Axis.vertical,
|
||||
ScrollListener onScroll,
|
||||
SnapOffsetCallback snapOffsetCallback,
|
||||
double snapAlignmentOffset: 0.0,
|
||||
this.itemsWrap: false,
|
||||
this.itemExtent,
|
||||
this.padding,
|
||||
this.scrollableListPainter
|
||||
}) : super(
|
||||
key: key,
|
||||
initialScrollOffset: initialScrollOffset,
|
||||
scrollDirection: scrollDirection,
|
||||
onScroll: onScroll,
|
||||
snapOffsetCallback: snapOffsetCallback,
|
||||
snapAlignmentOffset: snapAlignmentOffset
|
||||
) {
|
||||
assert(itemExtent != null);
|
||||
}
|
||||
|
||||
final bool itemsWrap;
|
||||
final double itemExtent;
|
||||
final EdgeDims padding;
|
||||
final ScrollableListPainter scrollableListPainter;
|
||||
}
|
||||
|
||||
abstract class ScrollableWidgetListState<T extends ScrollableWidgetList> extends ScrollableState<T> {
|
||||
/// Subclasses must implement `get itemCount` to tell ScrollableWidgetList
|
||||
/// how many items there are in the list.
|
||||
int get itemCount;
|
||||
int _previousItemCount;
|
||||
|
||||
Size _containerSize = Size.zero;
|
||||
|
||||
void didUpdateConfig(T oldConfig) {
|
||||
super.didUpdateConfig(oldConfig);
|
||||
|
||||
bool scrollBehaviorUpdateNeeded =
|
||||
config.padding != oldConfig.padding ||
|
||||
config.itemExtent != oldConfig.itemExtent ||
|
||||
config.scrollDirection != oldConfig.scrollDirection;
|
||||
|
||||
if (config.itemsWrap != oldConfig.itemsWrap) {
|
||||
_scrollBehavior = null;
|
||||
scrollBehaviorUpdateNeeded = true;
|
||||
}
|
||||
|
||||
if (itemCount != _previousItemCount) {
|
||||
_previousItemCount = itemCount;
|
||||
scrollBehaviorUpdateNeeded = true;
|
||||
}
|
||||
|
||||
if (scrollBehaviorUpdateNeeded)
|
||||
_updateScrollBehavior();
|
||||
}
|
||||
|
||||
ScrollBehavior createScrollBehavior() => new OverscrollBehavior();
|
||||
ExtentScrollBehavior get scrollBehavior => super.scrollBehavior;
|
||||
|
||||
double get _containerExtent {
|
||||
return config.scrollDirection == Axis.vertical
|
||||
? _containerSize.height
|
||||
: _containerSize.width;
|
||||
}
|
||||
|
||||
void _handleSizeChanged(Size newSize) {
|
||||
setState(() {
|
||||
_containerSize = newSize;
|
||||
_updateScrollBehavior();
|
||||
});
|
||||
}
|
||||
|
||||
double get _leadingPadding {
|
||||
EdgeDims padding = config.padding;
|
||||
if (config.scrollDirection == Axis.vertical)
|
||||
return padding != null ? padding.top : 0.0;
|
||||
return padding != null ? padding.left : -.0;
|
||||
}
|
||||
|
||||
double get _trailingPadding {
|
||||
EdgeDims padding = config.padding;
|
||||
if (config.scrollDirection == Axis.vertical)
|
||||
return padding != null ? padding.bottom : 0.0;
|
||||
return padding != null ? padding.right : 0.0;
|
||||
}
|
||||
|
||||
EdgeDims get _crossAxisPadding {
|
||||
EdgeDims padding = config.padding;
|
||||
if (padding == null)
|
||||
return null;
|
||||
if (config.scrollDirection == Axis.vertical)
|
||||
return new EdgeDims.only(left: padding.left, right: padding.right);
|
||||
return new EdgeDims.only(top: padding.top, bottom: padding.bottom);
|
||||
}
|
||||
|
||||
double get _contentExtent {
|
||||
if (itemCount == null)
|
||||
return null;
|
||||
double contentExtent = config.itemExtent * itemCount;
|
||||
if (config.padding != null)
|
||||
contentExtent += _leadingPadding + _trailingPadding;
|
||||
return contentExtent;
|
||||
}
|
||||
|
||||
void _updateScrollBehavior() {
|
||||
// if you don't call this from build(), you must call it from setState().
|
||||
if (config.scrollableListPainter != null)
|
||||
config.scrollableListPainter.contentExtent = _contentExtent;
|
||||
scrollTo(scrollBehavior.updateExtents(
|
||||
contentExtent: _contentExtent,
|
||||
containerExtent: _containerExtent,
|
||||
scrollOffset: scrollOffset
|
||||
));
|
||||
}
|
||||
|
||||
void dispatchOnScrollStart() {
|
||||
super.dispatchOnScrollStart();
|
||||
config.scrollableListPainter?.scrollStarted();
|
||||
}
|
||||
|
||||
void dispatchOnScroll() {
|
||||
super.dispatchOnScroll();
|
||||
if (config.scrollableListPainter != null)
|
||||
config.scrollableListPainter.scrollOffset = scrollOffset;
|
||||
}
|
||||
|
||||
void dispatchOnScrollEnd() {
|
||||
super.dispatchOnScrollEnd();
|
||||
config.scrollableListPainter?.scrollEnded();
|
||||
}
|
||||
|
||||
Widget buildContent(BuildContext context) {
|
||||
if (itemCount != _previousItemCount) {
|
||||
_previousItemCount = itemCount;
|
||||
_updateScrollBehavior();
|
||||
}
|
||||
|
||||
return new SizeObserver(
|
||||
onSizeChanged: _handleSizeChanged,
|
||||
child: new Container(
|
||||
padding: _crossAxisPadding,
|
||||
child: new HomogeneousViewport(
|
||||
builder: _buildItems,
|
||||
itemsWrap: config.itemsWrap,
|
||||
itemExtent: config.itemExtent,
|
||||
itemCount: itemCount,
|
||||
direction: config.scrollDirection,
|
||||
startOffset: scrollOffset - _leadingPadding,
|
||||
overlayPainter: config.scrollableListPainter
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildItems(BuildContext context, int start, int count) {
|
||||
List<Widget> result = buildItems(context, start, count);
|
||||
assert(result.every((Widget item) => item.key != null));
|
||||
return result;
|
||||
}
|
||||
|
||||
List<Widget> buildItems(BuildContext context, int start, int count);
|
||||
|
||||
}
|
||||
|
||||
/// A general scrollable list for a large number of children that might not all
|
||||
/// have the same height. Prefer [ScrollableWidgetList] when all the children
|
||||
/// have the same height because it can use that property to be more efficient.
|
||||
|
||||
@@ -65,9 +65,8 @@ class _ScrollableGridState extends ScrollableState<ScrollableGrid> {
|
||||
}
|
||||
}
|
||||
|
||||
class GridViewport extends VirtualViewport {
|
||||
class GridViewport extends VirtualViewport with VirtualViewportIterableMixin {
|
||||
GridViewport({
|
||||
Key key,
|
||||
this.startOffset,
|
||||
this.delegate,
|
||||
this.onExtentsChanged,
|
||||
|
||||
@@ -88,18 +88,16 @@ class _ScrollableListState extends ScrollableState<ScrollableList> {
|
||||
}
|
||||
}
|
||||
|
||||
class ListViewport extends VirtualViewport {
|
||||
ListViewport({
|
||||
Key key,
|
||||
class _VirtualListViewport extends VirtualViewport {
|
||||
_VirtualListViewport(
|
||||
this.onExtentsChanged,
|
||||
this.startOffset: 0.0,
|
||||
this.scrollDirection: Axis.vertical,
|
||||
this.startOffset,
|
||||
this.scrollDirection,
|
||||
this.itemExtent,
|
||||
this.itemsWrap: false,
|
||||
this.itemsWrap,
|
||||
this.padding,
|
||||
this.overlayPainter,
|
||||
this.children
|
||||
}) {
|
||||
this.overlayPainter
|
||||
) {
|
||||
assert(scrollDirection != null);
|
||||
assert(itemExtent != null);
|
||||
}
|
||||
@@ -111,15 +109,14 @@ class ListViewport extends VirtualViewport {
|
||||
final bool itemsWrap;
|
||||
final EdgeDims padding;
|
||||
final Painter overlayPainter;
|
||||
final Iterable<Widget> children;
|
||||
|
||||
RenderList createRenderObject() => new RenderList(itemExtent: itemExtent);
|
||||
|
||||
_ListViewportElement createElement() => new _ListViewportElement(this);
|
||||
_VirtualListViewportElement createElement() => new _VirtualListViewportElement(this);
|
||||
}
|
||||
|
||||
class _ListViewportElement extends VirtualViewportElement<ListViewport> {
|
||||
_ListViewportElement(ListViewport widget) : super(widget);
|
||||
class _VirtualListViewportElement extends VirtualViewportElement<_VirtualListViewport> {
|
||||
_VirtualListViewportElement(VirtualViewport widget) : super(widget);
|
||||
|
||||
RenderList get renderObject => super.renderObject;
|
||||
|
||||
@@ -135,11 +132,12 @@ class _ListViewportElement extends VirtualViewportElement<ListViewport> {
|
||||
double get startOffsetLimit =>_startOffsetLimit;
|
||||
double _startOffsetLimit;
|
||||
|
||||
void updateRenderObject(ListViewport oldWidget) {
|
||||
renderObject.scrollDirection = widget.scrollDirection;
|
||||
renderObject.itemExtent = widget.itemExtent;
|
||||
renderObject.padding = widget.padding;
|
||||
renderObject.overlayPainter = widget.overlayPainter;
|
||||
void updateRenderObject(_VirtualListViewport oldWidget) {
|
||||
renderObject
|
||||
..scrollDirection = widget.scrollDirection
|
||||
..itemExtent = widget.itemExtent
|
||||
..padding = widget.padding
|
||||
..overlayPainter = widget.overlayPainter;
|
||||
super.updateRenderObject(oldWidget);
|
||||
}
|
||||
|
||||
@@ -160,13 +158,13 @@ class _ListViewportElement extends VirtualViewportElement<ListViewport> {
|
||||
final double itemExtent = widget.itemExtent;
|
||||
final EdgeDims padding = widget.padding ?? EdgeDims.zero;
|
||||
|
||||
double contentExtent = widget.itemExtent * length + padding.top + padding.bottom;
|
||||
double contentExtent = length == null ? double.INFINITY : widget.itemExtent * length + padding.top + padding.bottom;
|
||||
double containerExtent = _getContainerExtentFromRenderObject();
|
||||
|
||||
_materializedChildBase = math.max(0, (widget.startOffset - padding.top) ~/ itemExtent);
|
||||
int materializedChildLimit = math.max(0, ((widget.startOffset + containerExtent) / itemExtent).ceil());
|
||||
|
||||
if (!widget.itemsWrap) {
|
||||
if (!widget.itemsWrap && length != null) {
|
||||
_materializedChildBase = math.min(length, _materializedChildBase);
|
||||
materializedChildLimit = math.min(length, materializedChildLimit);
|
||||
} else if (length == 0) {
|
||||
@@ -186,3 +184,133 @@ class _ListViewportElement extends VirtualViewportElement<ListViewport> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ListViewport extends _VirtualListViewport with VirtualViewportIterableMixin {
|
||||
ListViewport({
|
||||
ExtentsChangedCallback onExtentsChanged,
|
||||
double startOffset: 0.0,
|
||||
Axis scrollDirection: Axis.vertical,
|
||||
double itemExtent,
|
||||
bool itemsWrap: false,
|
||||
EdgeDims padding,
|
||||
Painter overlayPainter,
|
||||
this.children
|
||||
}) : super(
|
||||
onExtentsChanged,
|
||||
startOffset,
|
||||
scrollDirection,
|
||||
itemExtent,
|
||||
itemsWrap,
|
||||
padding,
|
||||
overlayPainter
|
||||
);
|
||||
|
||||
final Iterable<Widget> children;
|
||||
}
|
||||
|
||||
/// An optimized scrollable widget for a large number of children that are all
|
||||
/// the same size (extent) in the scrollDirection. For example for
|
||||
/// ScrollDirection.vertical itemExtent is the height of each item. Use this
|
||||
/// widget when you have a large number of children or when you are concerned
|
||||
// about offscreen widgets consuming resources.
|
||||
class ScrollableLazyList extends Scrollable {
|
||||
ScrollableLazyList({
|
||||
Key key,
|
||||
double initialScrollOffset,
|
||||
Axis scrollDirection: Axis.vertical,
|
||||
ScrollListener onScroll,
|
||||
SnapOffsetCallback snapOffsetCallback,
|
||||
double snapAlignmentOffset: 0.0,
|
||||
this.itemExtent,
|
||||
this.itemCount,
|
||||
this.itemBuilder,
|
||||
this.padding,
|
||||
this.scrollableListPainter
|
||||
}) : super(
|
||||
key: key,
|
||||
initialScrollOffset: initialScrollOffset,
|
||||
scrollDirection: scrollDirection,
|
||||
onScroll: onScroll,
|
||||
snapOffsetCallback: snapOffsetCallback,
|
||||
snapAlignmentOffset: snapAlignmentOffset
|
||||
) {
|
||||
assert(itemExtent != null);
|
||||
assert(itemBuilder != null);
|
||||
}
|
||||
|
||||
final double itemExtent;
|
||||
final int itemCount;
|
||||
final ItemListBuilder itemBuilder;
|
||||
final EdgeDims padding;
|
||||
final ScrollableListPainter scrollableListPainter;
|
||||
|
||||
ScrollableState createState() => new _ScrollableLazyListState();
|
||||
}
|
||||
|
||||
class _ScrollableLazyListState extends ScrollableState<ScrollableLazyList> {
|
||||
ScrollBehavior createScrollBehavior() => new OverscrollBehavior();
|
||||
ExtentScrollBehavior get scrollBehavior => super.scrollBehavior;
|
||||
|
||||
void _handleExtentsChanged(double contentExtent, double containerExtent) {
|
||||
config.scrollableListPainter?.contentExtent = contentExtent;
|
||||
setState(() {
|
||||
scrollTo(scrollBehavior.updateExtents(
|
||||
contentExtent: contentExtent,
|
||||
containerExtent: containerExtent,
|
||||
scrollOffset: scrollOffset
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
void dispatchOnScrollStart() {
|
||||
super.dispatchOnScrollStart();
|
||||
config.scrollableListPainter?.scrollStarted();
|
||||
}
|
||||
|
||||
void dispatchOnScroll() {
|
||||
super.dispatchOnScroll();
|
||||
config.scrollableListPainter?.scrollOffset = scrollOffset;
|
||||
}
|
||||
|
||||
void dispatchOnScrollEnd() {
|
||||
super.dispatchOnScrollEnd();
|
||||
config.scrollableListPainter?.scrollEnded();
|
||||
}
|
||||
|
||||
Widget buildContent(BuildContext context) {
|
||||
return new LazyListViewport(
|
||||
onExtentsChanged: _handleExtentsChanged,
|
||||
startOffset: scrollOffset,
|
||||
scrollDirection: config.scrollDirection,
|
||||
itemExtent: config.itemExtent,
|
||||
itemCount: config.itemCount,
|
||||
itemBuilder: config.itemBuilder,
|
||||
padding: config.padding,
|
||||
overlayPainter: config.scrollableListPainter
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LazyListViewport extends _VirtualListViewport with VirtualViewportLazyMixin {
|
||||
LazyListViewport({
|
||||
ExtentsChangedCallback onExtentsChanged,
|
||||
double startOffset: 0.0,
|
||||
Axis scrollDirection: Axis.vertical,
|
||||
double itemExtent,
|
||||
EdgeDims padding,
|
||||
Painter overlayPainter,
|
||||
this.itemCount,
|
||||
this.itemBuilder
|
||||
}) : super(
|
||||
onExtentsChanged,
|
||||
startOffset,
|
||||
scrollDirection,
|
||||
itemExtent,
|
||||
false, // Don't support wrapping yet.
|
||||
padding,
|
||||
overlayPainter
|
||||
);
|
||||
|
||||
final int itemCount;
|
||||
final ItemListBuilder itemBuilder;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,15 @@ typedef void ExtentsChangedCallback(double contentExtent, double containerExtent
|
||||
abstract class VirtualViewport extends RenderObjectWidget {
|
||||
double get startOffset;
|
||||
Axis get scrollDirection;
|
||||
Iterable<Widget> get children;
|
||||
|
||||
_WidgetProvider _createWidgetProvider();
|
||||
}
|
||||
|
||||
abstract class _WidgetProvider {
|
||||
void didUpdateWidget(VirtualViewport oldWidget, VirtualViewport newWidget);
|
||||
int get virtualChildCount;
|
||||
void prepareChildren(VirtualViewportElement context, int base, int count);
|
||||
Widget getChild(int i);
|
||||
}
|
||||
|
||||
abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderObjectElement<T> {
|
||||
@@ -38,10 +46,12 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
|
||||
visitor(child);
|
||||
}
|
||||
|
||||
_WidgetProvider _widgetProvider;
|
||||
|
||||
void mount(Element parent, dynamic newSlot) {
|
||||
_widgetProvider = widget._createWidgetProvider();
|
||||
_widgetProvider.didUpdateWidget(null, widget);
|
||||
super.mount(parent, newSlot);
|
||||
_iterator = null;
|
||||
_widgets = <Widget>[];
|
||||
renderObject.callback = layout;
|
||||
updateRenderObject(null);
|
||||
}
|
||||
@@ -52,11 +62,8 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
|
||||
}
|
||||
|
||||
void update(T newWidget) {
|
||||
if (widget.children != newWidget.children) {
|
||||
_iterator = null;
|
||||
_widgets = <Widget>[];
|
||||
}
|
||||
T oldWidget = widget;
|
||||
_widgetProvider.didUpdateWidget(oldWidget, newWidget);
|
||||
super.update(newWidget);
|
||||
updateRenderObject(oldWidget);
|
||||
if (!renderObject.needsLayout)
|
||||
@@ -75,7 +82,7 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
|
||||
}
|
||||
|
||||
void updateRenderObject(T oldWidget) {
|
||||
renderObject.virtualChildCount = widget.children.length;
|
||||
renderObject.virtualChildCount = _widgetProvider.virtualChildCount;
|
||||
|
||||
if (startOffsetBase != null) {
|
||||
_updatePaintOffset();
|
||||
@@ -111,37 +118,16 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
|
||||
BuildableElement.lockState(_materializeChildren, building: true);
|
||||
}
|
||||
|
||||
Iterator<Widget> _iterator;
|
||||
List<Widget> _widgets;
|
||||
|
||||
void _populateWidgets(int limit) {
|
||||
if (limit <= _widgets.length)
|
||||
return;
|
||||
if (widget.children is List<Widget>) {
|
||||
_widgets = widget.children;
|
||||
return;
|
||||
}
|
||||
_iterator ??= widget.children.iterator;
|
||||
while (_widgets.length < limit) {
|
||||
bool moved = _iterator.moveNext();
|
||||
assert(moved);
|
||||
Widget current = _iterator.current;
|
||||
assert(current != null);
|
||||
_widgets.add(current);
|
||||
}
|
||||
}
|
||||
|
||||
void _materializeChildren() {
|
||||
int base = materializedChildBase;
|
||||
int count = materializedChildCount;
|
||||
int length = renderObject.virtualChildCount;
|
||||
assert(base != null);
|
||||
assert(count != null);
|
||||
_populateWidgets(base < 0 ? length : math.min(length, base + count));
|
||||
_widgetProvider.prepareChildren(this, base, count);
|
||||
List<Widget> newWidgets = new List<Widget>(count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
int childIndex = base + i;
|
||||
Widget child = _widgets[(childIndex % length).abs()];
|
||||
Widget child = _widgetProvider.getChild(childIndex);
|
||||
Key key = new ValueKey(child.key ?? childIndex);
|
||||
newWidgets[i] = new RepaintBoundary(key: key, child: child);
|
||||
}
|
||||
@@ -162,3 +148,84 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
|
||||
renderObject.remove(child);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class VirtualViewportIterableMixin extends VirtualViewport {
|
||||
Iterable<Widget> get children;
|
||||
|
||||
_IterableWidgetProvider _createWidgetProvider() => new _IterableWidgetProvider();
|
||||
}
|
||||
|
||||
class _IterableWidgetProvider extends _WidgetProvider {
|
||||
int _length;
|
||||
Iterator<Widget> _iterator;
|
||||
List<Widget> _widgets;
|
||||
|
||||
void didUpdateWidget(VirtualViewportIterableMixin oldWidget, VirtualViewportIterableMixin newWidget) {
|
||||
if (oldWidget == null || newWidget.children != oldWidget.children) {
|
||||
_iterator = null;
|
||||
_widgets = <Widget>[];
|
||||
_length = newWidget.children.length;
|
||||
}
|
||||
}
|
||||
|
||||
int get virtualChildCount => _length;
|
||||
|
||||
void prepareChildren(VirtualViewportElement context, int base, int count) {
|
||||
int limit = base < 0 ? _length : math.min(_length, base + count);
|
||||
if (limit <= _widgets.length)
|
||||
return;
|
||||
VirtualViewportIterableMixin widget = context.widget;
|
||||
if (widget.children is List<Widget>) {
|
||||
_widgets = widget.children;
|
||||
return;
|
||||
}
|
||||
_iterator ??= widget.children.iterator;
|
||||
while (_widgets.length < limit) {
|
||||
bool moved = _iterator.moveNext();
|
||||
assert(moved);
|
||||
Widget current = _iterator.current;
|
||||
assert(current != null);
|
||||
_widgets.add(current);
|
||||
}
|
||||
}
|
||||
|
||||
Widget getChild(int i) => _widgets[(i % _length).abs()];
|
||||
}
|
||||
|
||||
typedef List<Widget> ItemListBuilder(BuildContext context, int start, int count);
|
||||
|
||||
abstract class VirtualViewportLazyMixin extends VirtualViewport {
|
||||
int get itemCount;
|
||||
ItemListBuilder get itemBuilder;
|
||||
|
||||
_LazyWidgetProvider _createWidgetProvider() => new _LazyWidgetProvider();
|
||||
}
|
||||
|
||||
class _LazyWidgetProvider extends _WidgetProvider {
|
||||
int _length;
|
||||
int _base;
|
||||
List<Widget> _widgets;
|
||||
|
||||
void didUpdateWidget(VirtualViewportLazyMixin oldWidget, VirtualViewportLazyMixin newWidget) {
|
||||
if (_length != newWidget.itemCount || oldWidget?.itemBuilder != newWidget.itemBuilder) {
|
||||
_length = newWidget.itemCount;
|
||||
_base = null;
|
||||
_widgets = null;
|
||||
}
|
||||
}
|
||||
|
||||
int get virtualChildCount => _length;
|
||||
|
||||
void prepareChildren(VirtualViewportElement context, int base, int count) {
|
||||
if (_widgets != null && _widgets.length == count && _base == base)
|
||||
return;
|
||||
VirtualViewportLazyMixin widget = context.widget;
|
||||
_base = base;
|
||||
_widgets = widget.itemBuilder(context, base, count);
|
||||
}
|
||||
|
||||
Widget getChild(int i) {
|
||||
int n = _length ?? _widgets.length;
|
||||
return _widgets[(i % n).abs()];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ export 'src/widgets/framework.dart';
|
||||
export 'src/widgets/gesture_detector.dart';
|
||||
export 'src/widgets/gridpaper.dart';
|
||||
export 'src/widgets/heroes.dart';
|
||||
export 'src/widgets/homogeneous_viewport.dart';
|
||||
export 'src/widgets/implicit_animations.dart';
|
||||
export 'src/widgets/locale_query.dart';
|
||||
export 'src/widgets/media_query.dart';
|
||||
|
||||
@@ -6,26 +6,20 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class ThePositiveNumbers extends ScrollableWidgetList {
|
||||
ThePositiveNumbers() : super(itemExtent: 100.0);
|
||||
ThePositiveNumbersState createState() => new ThePositiveNumbersState();
|
||||
}
|
||||
|
||||
class ThePositiveNumbersState extends ScrollableWidgetListState<ThePositiveNumbers> {
|
||||
|
||||
ScrollBehavior createScrollBehavior() => new UnboundedBehavior();
|
||||
|
||||
int get itemCount => null;
|
||||
|
||||
List<Widget> buildItems(BuildContext context, int start, int count) {
|
||||
List<Widget> result = new List<Widget>();
|
||||
for (int index = start; index < start + count; index += 1)
|
||||
result.add(new Text('$index', key: new ValueKey<int>(index)));
|
||||
return result;
|
||||
class ThePositiveNumbers extends StatelessComponent {
|
||||
Widget build(BuildContext context) {
|
||||
return new ScrollableLazyList(
|
||||
itemExtent: 100.0,
|
||||
itemBuilder: (BuildContext context, int start, int count) {
|
||||
List<Widget> result = new List<Widget>();
|
||||
for (int index = start; index < start + count; index += 1)
|
||||
result.add(new Text('$index', key: new ValueKey<int>(index)));
|
||||
return result;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void main() {
|
||||
test('whether we remember our scroll position', () {
|
||||
testWidgets((WidgetTester tester) {
|
||||
@@ -53,8 +47,8 @@ void main() {
|
||||
expect(tester.findText('10'), isNull);
|
||||
expect(tester.findText('100'), isNull);
|
||||
|
||||
StatefulComponentElement<ThePositiveNumbers, ThePositiveNumbersState> target =
|
||||
tester.findElement((Element element) => element.widget is ThePositiveNumbers);
|
||||
StatefulComponentElement<ScrollableLazyList, ScrollableState<ScrollableLazyList>> target =
|
||||
tester.findElement((Element element) => element.widget is ScrollableLazyList);
|
||||
target.state.scrollTo(1000.0);
|
||||
tester.pump(new Duration(seconds: 1));
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ void main() {
|
||||
|
||||
Widget builder() {
|
||||
return new FlipComponent(
|
||||
left: new HomogeneousViewport(
|
||||
builder: (BuildContext context, int start, int count) {
|
||||
left: new ScrollableLazyList(
|
||||
itemBuilder: (BuildContext context, int start, int count) {
|
||||
List<Widget> result = <Widget>[];
|
||||
for (int index = start; index < start + count; index += 1) {
|
||||
callbackTracker.add(index);
|
||||
@@ -31,7 +31,6 @@ void main() {
|
||||
}
|
||||
return result;
|
||||
},
|
||||
startOffset: 0.0,
|
||||
itemExtent: 100.0
|
||||
),
|
||||
right: new Text('Not Today')
|
||||
@@ -67,9 +66,7 @@ void main() {
|
||||
// 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 = (BuildContext context, int start, int count) {
|
||||
ItemListBuilder itemBuilder = (BuildContext context, int start, int count) {
|
||||
List<Widget> result = <Widget>[];
|
||||
for (int index = start; index < start + count; index += 1) {
|
||||
callbackTracker.add(index);
|
||||
@@ -83,28 +80,27 @@ void main() {
|
||||
return result;
|
||||
};
|
||||
|
||||
FlipComponent testComponent;
|
||||
Widget builder() {
|
||||
testComponent = new FlipComponent(
|
||||
left: new HomogeneousViewport(
|
||||
builder: itemBuilder,
|
||||
startOffset: offset,
|
||||
itemExtent: 200.0
|
||||
),
|
||||
right: new Text('Not Today')
|
||||
);
|
||||
return testComponent;
|
||||
}
|
||||
GlobalKey<ScrollableState<ScrollableLazyList>> scrollableKey = new GlobalKey<ScrollableState<ScrollableLazyList>>();
|
||||
FlipComponent testComponent = new FlipComponent(
|
||||
left: new ScrollableLazyList(
|
||||
key: scrollableKey,
|
||||
itemBuilder: itemBuilder,
|
||||
itemExtent: 200.0,
|
||||
initialScrollOffset: 300.0
|
||||
),
|
||||
right: new Text('Not Today')
|
||||
);
|
||||
|
||||
tester.pumpWidget(builder());
|
||||
tester.pumpWidget(testComponent);
|
||||
|
||||
expect(callbackTracker, equals([1, 2, 3, 4]));
|
||||
|
||||
callbackTracker.clear();
|
||||
|
||||
offset = 400.0; // now only 3 should fit, numbered 2-4.
|
||||
scrollableKey.currentState.scrollTo(400.0);
|
||||
// now only 3 should fit, numbered 2-4.
|
||||
|
||||
tester.pumpWidget(builder());
|
||||
tester.pumpWidget(testComponent);
|
||||
|
||||
expect(callbackTracker, equals([2, 3, 4]));
|
||||
|
||||
@@ -120,9 +116,7 @@ void main() {
|
||||
// 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 = (BuildContext context, int start, int count) {
|
||||
ItemListBuilder itemBuilder = (BuildContext context, int start, int count) {
|
||||
List<Widget> result = <Widget>[];
|
||||
for (int index = start; index < start + count; index += 1) {
|
||||
callbackTracker.add(index);
|
||||
@@ -136,29 +130,28 @@ void main() {
|
||||
return result;
|
||||
};
|
||||
|
||||
FlipComponent testComponent;
|
||||
Widget builder() {
|
||||
testComponent = new FlipComponent(
|
||||
left: new HomogeneousViewport(
|
||||
builder: itemBuilder,
|
||||
startOffset: offset,
|
||||
itemExtent: 200.0,
|
||||
direction: Axis.horizontal
|
||||
),
|
||||
right: new Text('Not Today')
|
||||
);
|
||||
return testComponent;
|
||||
}
|
||||
GlobalKey<ScrollableState<ScrollableLazyList>> scrollableKey = new GlobalKey<ScrollableState<ScrollableLazyList>>();
|
||||
FlipComponent testComponent = new FlipComponent(
|
||||
left: new ScrollableLazyList(
|
||||
key: scrollableKey,
|
||||
itemBuilder: itemBuilder,
|
||||
itemExtent: 200.0,
|
||||
initialScrollOffset: 300.0,
|
||||
scrollDirection: Axis.horizontal
|
||||
),
|
||||
right: new Text('Not Today')
|
||||
);
|
||||
|
||||
tester.pumpWidget(builder());
|
||||
tester.pumpWidget(testComponent);
|
||||
|
||||
expect(callbackTracker, equals([1, 2, 3, 4, 5]));
|
||||
|
||||
callbackTracker.clear();
|
||||
|
||||
offset = 400.0; // now only 4 should fit, numbered 2-5.
|
||||
scrollableKey.currentState.scrollTo(400.0);
|
||||
// now only 4 should fit, numbered 2-5.
|
||||
|
||||
tester.pumpWidget(builder());
|
||||
tester.pumpWidget(testComponent);
|
||||
|
||||
expect(callbackTracker, equals([2, 3, 4, 5]));
|
||||
|
||||
Reference in New Issue
Block a user