Commit d46f0ceb authored by Adam Barth's avatar Adam Barth

Merge pull request #1098 from abarth/pageable_list2

Switch PageableList over to using RenderList
parents e1b16729 e64d93a5
...@@ -29,7 +29,7 @@ class _TabsDemoState extends State<TabsDemo> { ...@@ -29,7 +29,7 @@ class _TabsDemoState extends State<TabsDemo> {
Widget build(_) { Widget build(_) {
return new TabBarView<String>( return new TabBarView<String>(
items: _iconNames, items: _iconNames,
itemBuilder: (BuildContext context, String iconName, int index) { itemBuilder: (String iconName) {
return new Container( return new Container(
key: new ValueKey<String>(iconName), key: new ValueKey<String>(iconName),
padding: const EdgeDims.all(12.0), padding: const EdgeDims.all(12.0),
......
...@@ -273,7 +273,7 @@ class StockHomeState extends State<StockHome> { ...@@ -273,7 +273,7 @@ class StockHomeState extends State<StockHome> {
drawer: _buildDrawer(context), drawer: _buildDrawer(context),
body: new TabBarView<StockHomeTab>( body: new TabBarView<StockHomeTab>(
items: <StockHomeTab>[StockHomeTab.market, StockHomeTab.portfolio], items: <StockHomeTab>[StockHomeTab.market, StockHomeTab.portfolio],
itemBuilder: (BuildContext context, StockHomeTab tab, _) { itemBuilder: (StockHomeTab tab) {
switch (tab) { switch (tab) {
case StockHomeTab.market: case StockHomeTab.market:
return _buildStockTab(context, tab, config.symbols); return _buildStockTab(context, tab, config.symbols);
......
...@@ -41,7 +41,7 @@ class PageableListAppState extends State<PageableListApp> { ...@@ -41,7 +41,7 @@ class PageableListAppState extends State<PageableListApp> {
ScrollDirection scrollDirection = ScrollDirection.horizontal; ScrollDirection scrollDirection = ScrollDirection.horizontal;
bool itemsWrap = false; bool itemsWrap = false;
Widget buildCard(BuildContext context, CardModel cardModel, int index) { Widget buildCard(CardModel cardModel) {
Widget card = new Card( Widget card = new Card(
color: cardModel.color, color: cardModel.color,
child: new Container( child: new Container(
...@@ -114,10 +114,9 @@ class PageableListAppState extends State<PageableListApp> { ...@@ -114,10 +114,9 @@ class PageableListAppState extends State<PageableListApp> {
} }
Widget _buildBody(BuildContext context) { Widget _buildBody(BuildContext context) {
return new PageableList<CardModel>( return new PageableList(
items: cardModels, children: cardModels.map(buildCard),
itemsWrap: itemsWrap, itemsWrap: itemsWrap,
itemBuilder: buildCard,
scrollDirection: scrollDirection scrollDirection: scrollDirection
); );
} }
......
...@@ -772,26 +772,30 @@ class _TabBarState<T> extends ScrollableState<TabBar<T>> implements TabBarSelect ...@@ -772,26 +772,30 @@ class _TabBarState<T> extends ScrollableState<TabBar<T>> implements TabBarSelect
} }
} }
class TabBarView<T> extends PageableList<T> { typedef Widget TabItemBuilder<T>(T item);
class TabBarView<T> extends PageableList {
TabBarView({ TabBarView({
Key key, Key key,
List<T> items, List<T> items,
ItemBuilder<T> itemBuilder TabItemBuilder<T> itemBuilder
}) : super( }) : items = items, itemBuilder = itemBuilder, super(
key: key, key: key,
scrollDirection: ScrollDirection.horizontal, scrollDirection: ScrollDirection.horizontal,
items: items, children: items.map((T item) => itemBuilder(item)),
itemBuilder: itemBuilder,
itemsWrap: false itemsWrap: false
) { ) {
assert(items != null); assert(items != null);
assert(items.length > 1); assert(items.length > 1);
} }
final List<T> items;
final TabItemBuilder<T> itemBuilder;
_TabBarViewState createState() => new _TabBarViewState<T>(); _TabBarViewState createState() => new _TabBarViewState<T>();
} }
class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> implements TabBarSelectionPerformanceListener { class _TabBarViewState<T> extends PageableListState<TabBarView<T>> implements TabBarSelectionPerformanceListener {
TabBarSelectionState _selection; TabBarSelectionState _selection;
List<int> _itemIndices = [0, 1]; List<int> _itemIndices = [0, 1];
...@@ -916,14 +920,10 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> implements ...@@ -916,14 +920,10 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> implements
return settleScrollOffset(); return settleScrollOffset();
} }
List<Widget> buildItems(BuildContext context, int start, int count) { Widget buildContent(BuildContext context) {
TabBarSelectionState<T> newSelection = TabBarSelection.of(context); TabBarSelectionState<T> newSelection = TabBarSelection.of(context);
if (_selection != newSelection) if (_selection != newSelection)
_initSelection(newSelection); _initSelection(newSelection);
return _itemIndices return super.buildContent(context);
.skip(start)
.take(count)
.map((int i) => config.itemBuilder(context, config.items[i], i))
.toList();
} }
} }
...@@ -28,7 +28,6 @@ class RenderList extends RenderVirtualViewport<ListParentData> implements HasScr ...@@ -28,7 +28,6 @@ class RenderList extends RenderVirtualViewport<ListParentData> implements HasScr
paintOffset: paintOffset, paintOffset: paintOffset,
callback: callback callback: callback
) { ) {
assert(itemExtent != null);
addAll(children); addAll(children);
} }
...@@ -75,6 +74,8 @@ class RenderList extends RenderVirtualViewport<ListParentData> implements HasScr ...@@ -75,6 +74,8 @@ class RenderList extends RenderVirtualViewport<ListParentData> implements HasScr
} }
double get _preferredExtent { double get _preferredExtent {
if (itemExtent == null)
return double.INFINITY;
double extent = itemExtent * virtualChildCount; double extent = itemExtent * virtualChildCount;
if (padding != null) if (padding != null)
extent += _scrollAxisPadding; extent += _scrollAxisPadding;
...@@ -118,8 +119,16 @@ class RenderList extends RenderVirtualViewport<ListParentData> implements HasScr ...@@ -118,8 +119,16 @@ class RenderList extends RenderVirtualViewport<ListParentData> implements HasScr
} }
void performLayout() { void performLayout() {
switch (scrollDirection) {
case ScrollDirection.vertical:
size = new Size(constraints.maxWidth, size = new Size(constraints.maxWidth,
constraints.constrainHeight(_preferredExtent)); constraints.constrainHeight(_preferredExtent));
break;
case ScrollDirection.horizontal:
size = new Size(constraints.constrainWidth(_preferredExtent),
constraints.maxHeight);
break;
}
if (callback != null) if (callback != null)
invokeLayoutCallback(callback); invokeLayoutCallback(callback);
...@@ -136,15 +145,15 @@ class RenderList extends RenderVirtualViewport<ListParentData> implements HasScr ...@@ -136,15 +145,15 @@ class RenderList extends RenderVirtualViewport<ListParentData> implements HasScr
switch (scrollDirection) { switch (scrollDirection) {
case ScrollDirection.vertical: case ScrollDirection.vertical:
itemWidth = math.max(0, size.width - (padding == null ? 0.0 : padding.horizontal)); itemWidth = math.max(0, size.width - (padding == null ? 0.0 : padding.horizontal));
itemHeight = itemExtent; itemHeight = itemExtent ?? size.height;
y = padding != null ? padding.top : 0.0; y = padding != null ? padding.top : 0.0;
dy = itemExtent; dy = itemHeight;
break; break;
case ScrollDirection.horizontal: case ScrollDirection.horizontal:
itemWidth = itemExtent; itemWidth = itemExtent ?? size.width;
itemHeight = math.max(0, size.height - (padding == null ? 0.0 : padding.vertical)); itemHeight = math.max(0, size.height - (padding == null ? 0.0 : padding.vertical));
x = padding != null ? padding.left : 0.0; x = padding != null ? padding.left : 0.0;
dx = itemExtent; dx = itemWidth;
break; break;
} }
......
...@@ -220,80 +220,3 @@ class _HomogeneousViewportElement extends _ViewportBaseElement<HomogeneousViewpo ...@@ -220,80 +220,3 @@ class _HomogeneousViewportElement extends _ViewportBaseElement<HomogeneousViewpo
return widget.itemCount != null ? widget.itemCount * widget.itemExtent : double.INFINITY; return widget.itemCount != null ? widget.itemCount * widget.itemExtent : double.INFINITY;
} }
} }
class HomogeneousPageViewport extends _ViewportBase {
HomogeneousPageViewport({
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
ScrollDirection direction: ScrollDirection.vertical,
double startOffset: 0.0,
Painter overlayPainter
}) : super(
key: key,
builder: builder,
itemsWrap: itemsWrap,
itemCount: itemCount,
direction: direction,
startOffset: startOffset,
overlayPainter: overlayPainter
);
_HomogeneousPageViewportElement createElement() => new _HomogeneousPageViewportElement(this);
}
class _HomogeneousPageViewportElement extends _ViewportBaseElement<HomogeneousPageViewport> {
_HomogeneousPageViewportElement(HomogeneousPageViewport 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 itemExtent = widget.direction == ScrollDirection.vertical ? constraints.maxHeight : constraints.maxWidth;
double offset;
if (widget.startOffset <= 0.0 && !widget.itemsWrap) {
_layoutFirstIndex = 0;
offset = -widget.startOffset * itemExtent;
} else {
_layoutFirstIndex = widget.startOffset.floor();
offset = -((widget.startOffset * itemExtent) % itemExtent);
}
if (itemExtent < double.INFINITY && widget.itemCount != null) {
final double contentExtent = itemExtent * widget.itemCount;
_layoutItemCount = contentExtent == 0.0 ? 0 : ((contentExtent - offset) / contentExtent).ceil();
if (!widget.itemsWrap)
_layoutItemCount = math.min(_layoutItemCount, widget.itemCount - _layoutFirstIndex);
} else {
assert(() {
'This HomogeneousPageViewport 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 HomogeneousPageViewport (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 == ScrollDirection.vertical ? BlockDirection.vertical : BlockDirection.horizontal;
renderObject.itemExtent = itemExtent;
renderObject.minExtent = itemExtent;
renderObject.startOffset = offset;
renderObject.overlayPainter = widget.overlayPainter;
}, building: true);
}
double getTotalExtent(BoxConstraints constraints) {
double itemExtent = widget.direction == ScrollDirection.vertical ? constraints.maxHeight : constraints.maxWidth;
return widget.itemCount != null ? widget.itemCount * itemExtent : double.INFINITY;
}
}
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/animation.dart'; import 'package:flutter/animation.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
...@@ -11,8 +10,8 @@ import 'package:flutter/rendering.dart'; ...@@ -11,8 +10,8 @@ import 'package:flutter/rendering.dart';
import 'basic.dart'; import 'basic.dart';
import 'framework.dart'; import 'framework.dart';
import 'homogeneous_viewport.dart';
import 'scrollable.dart'; import 'scrollable.dart';
import 'virtual_viewport.dart';
/// Controls what alignment items use when settling. /// Controls what alignment items use when settling.
enum ItemsSnapAlignment { enum ItemsSnapAlignment {
...@@ -22,7 +21,7 @@ enum ItemsSnapAlignment { ...@@ -22,7 +21,7 @@ enum ItemsSnapAlignment {
typedef void PageChangedCallback(int newPage); typedef void PageChangedCallback(int newPage);
class PageableList<T> extends Scrollable { class PageableList extends Scrollable {
PageableList({ PageableList({
Key key, Key key,
initialScrollOffset, initialScrollOffset,
...@@ -32,14 +31,13 @@ class PageableList<T> extends Scrollable { ...@@ -32,14 +31,13 @@ class PageableList<T> extends Scrollable {
ScrollListener onScrollEnd, ScrollListener onScrollEnd,
SnapOffsetCallback snapOffsetCallback, SnapOffsetCallback snapOffsetCallback,
double snapAlignmentOffset: 0.0, double snapAlignmentOffset: 0.0,
this.items,
this.itemBuilder,
this.itemsWrap: false, this.itemsWrap: false,
this.itemsSnapAlignment: ItemsSnapAlignment.adjacentItem, this.itemsSnapAlignment: ItemsSnapAlignment.adjacentItem,
this.onPageChanged, this.onPageChanged,
this.scrollableListPainter, this.scrollableListPainter,
this.duration: const Duration(milliseconds: 200), this.duration: const Duration(milliseconds: 200),
this.curve: Curves.ease this.curve: Curves.ease,
this.children
}) : super( }) : super(
key: key, key: key,
initialScrollOffset: initialScrollOffset, initialScrollOffset: initialScrollOffset,
...@@ -51,21 +49,19 @@ class PageableList<T> extends Scrollable { ...@@ -51,21 +49,19 @@ class PageableList<T> extends Scrollable {
snapAlignmentOffset: snapAlignmentOffset snapAlignmentOffset: snapAlignmentOffset
); );
final List<T> items;
final ItemBuilder<T> itemBuilder;
final ItemsSnapAlignment itemsSnapAlignment;
final bool itemsWrap; final bool itemsWrap;
final ItemsSnapAlignment itemsSnapAlignment;
final PageChangedCallback onPageChanged; final PageChangedCallback onPageChanged;
final ScrollableListPainter scrollableListPainter; final ScrollableListPainter scrollableListPainter;
final Duration duration; final Duration duration;
final Curve curve; final Curve curve;
final Iterable<Widget> children;
PageableListState<T, PageableList<T>> createState() => new PageableListState<T, PageableList<T>>(); PageableListState createState() => new PageableListState();
} }
class PageableListState<T, Config extends PageableList<T>> extends ScrollableState<Config> { class PageableListState<T extends PageableList> extends ScrollableState<T> {
int get itemCount => config.children?.length ?? 0;
int get itemCount => config.items?.length ?? 0;
int _previousItemCount; int _previousItemCount;
double pixelToScrollOffset(double value) { double pixelToScrollOffset(double value) {
...@@ -76,7 +72,12 @@ class PageableListState<T, Config extends PageableList<T>> extends ScrollableSta ...@@ -76,7 +72,12 @@ class PageableListState<T, Config extends PageableList<T>> extends ScrollableSta
return pixelScrollExtent == 0.0 ? 0.0 : value / pixelScrollExtent; return pixelScrollExtent == 0.0 ? 0.0 : value / pixelScrollExtent;
} }
void didUpdateConfig(Config oldConfig) { void initState() {
super.initState();
_updateScrollBehavior();
}
void didUpdateConfig(PageableList oldConfig) {
super.didUpdateConfig(oldConfig); super.didUpdateConfig(oldConfig);
bool scrollBehaviorUpdateNeeded = config.scrollDirection != oldConfig.scrollDirection; bool scrollBehaviorUpdateNeeded = config.scrollDirection != oldConfig.scrollDirection;
...@@ -94,9 +95,7 @@ class PageableListState<T, Config extends PageableList<T>> extends ScrollableSta ...@@ -94,9 +95,7 @@ class PageableListState<T, Config extends PageableList<T>> extends ScrollableSta
} }
void _updateScrollBehavior() { void _updateScrollBehavior() {
// if you don't call this from build(), you must call it from setState(). config.scrollableListPainter?.contentExtent = itemCount.toDouble();
if (config.scrollableListPainter != null)
config.scrollableListPainter.contentExtent = itemCount.toDouble();
scrollTo(scrollBehavior.updateExtents( scrollTo(scrollBehavior.updateExtents(
contentExtent: itemCount.toDouble(), contentExtent: itemCount.toDouble(),
containerExtent: 1.0, containerExtent: 1.0,
...@@ -111,8 +110,7 @@ class PageableListState<T, Config extends PageableList<T>> extends ScrollableSta ...@@ -111,8 +110,7 @@ class PageableListState<T, Config extends PageableList<T>> extends ScrollableSta
void dispatchOnScroll() { void dispatchOnScroll() {
super.dispatchOnScroll(); super.dispatchOnScroll();
if (config.scrollableListPainter != null) config.scrollableListPainter?.scrollOffset = scrollOffset;
config.scrollableListPainter.scrollOffset = scrollOffset;
} }
void dispatchOnScrollEnd() { void dispatchOnScrollEnd() {
...@@ -121,17 +119,12 @@ class PageableListState<T, Config extends PageableList<T>> extends ScrollableSta ...@@ -121,17 +119,12 @@ class PageableListState<T, Config extends PageableList<T>> extends ScrollableSta
} }
Widget buildContent(BuildContext context) { Widget buildContent(BuildContext context) {
if (itemCount != _previousItemCount) { return new PageViewport(
_previousItemCount = itemCount;
_updateScrollBehavior();
}
return new HomogeneousPageViewport(
builder: buildItems,
itemsWrap: config.itemsWrap, itemsWrap: config.itemsWrap,
itemCount: itemCount, scrollDirection: config.scrollDirection,
direction: config.scrollDirection,
startOffset: scrollOffset, startOffset: scrollOffset,
overlayPainter: config.scrollableListPainter overlayPainter: config.scrollableListPainter,
children: config.children
); );
} }
...@@ -180,18 +173,94 @@ class PageableListState<T, Config extends PageableList<T>> extends ScrollableSta ...@@ -180,18 +173,94 @@ class PageableListState<T, Config extends PageableList<T>> extends ScrollableSta
.then(_notifyPageChanged); .then(_notifyPageChanged);
} }
List<Widget> buildItems(BuildContext context, int start, int count) {
final List<Widget> result = new List<Widget>();
final int begin = config.itemsWrap ? start : math.max(0, start);
final int end = config.itemsWrap ? begin + count : math.min(begin + count, itemCount);
for (int i = begin; i < end; ++i)
result.add(config.itemBuilder(context, config.items[i % itemCount], i));
assert(result.every((Widget item) => item.key != null));
return result;
}
void _notifyPageChanged(_) { void _notifyPageChanged(_) {
if (config.onPageChanged != null) if (config.onPageChanged != null)
config.onPageChanged(itemCount == 0 ? 0 : scrollOffset.floor() % itemCount); config.onPageChanged(itemCount == 0 ? 0 : scrollOffset.floor() % itemCount);
} }
} }
class PageViewport extends VirtualViewport {
PageViewport({
Key key,
this.startOffset: 0.0,
this.scrollDirection: ScrollDirection.vertical,
this.itemsWrap: false,
this.overlayPainter,
this.children
}) {
assert(scrollDirection != null);
}
final double startOffset;
final ScrollDirection scrollDirection;
final bool itemsWrap;
final Painter overlayPainter;
final Iterable<Widget> children;
RenderList createRenderObject() => new RenderList();
_PageViewportElement createElement() => new _PageViewportElement(this);
}
class _PageViewportElement extends VirtualViewportElement<PageViewport> {
_PageViewportElement(PageViewport widget) : super(widget);
RenderList get renderObject => super.renderObject;
int get materializedChildBase => _materializedChildBase;
int _materializedChildBase;
int get materializedChildCount => _materializedChildCount;
int _materializedChildCount;
double get startOffsetBase => _repaintOffsetBase;
double _repaintOffsetBase;
double get startOffsetLimit =>_repaintOffsetLimit;
double _repaintOffsetLimit;
double get paintOffset {
if (_containerExtent == null)
return 0.0;
return -(widget.startOffset - startOffsetBase) * _containerExtent;
}
void updateRenderObject() {
renderObject.scrollDirection = widget.scrollDirection;
renderObject.overlayPainter = widget.overlayPainter;
super.updateRenderObject();
}
double _containerExtent;
double _getContainerExtentFromRenderObject() {
switch (widget.scrollDirection) {
case ScrollDirection.vertical:
return renderObject.size.height;
case ScrollDirection.horizontal:
return renderObject.size.width;
}
}
void layout(BoxConstraints constraints) {
int length = renderObject.virtualChildCount;
_containerExtent = _getContainerExtentFromRenderObject();
_materializedChildBase = widget.startOffset.floor();
int materializedChildLimit = (widget.startOffset + 1.0).ceil();
if (!widget.itemsWrap) {
_materializedChildBase = _materializedChildBase.clamp(0, length);
materializedChildLimit = materializedChildLimit.clamp(0, length);
} else if (length == 0) {
materializedChildLimit = _materializedChildBase;
}
_materializedChildCount = materializedChildLimit - _materializedChildBase;
_repaintOffsetBase = _materializedChildBase.toDouble();
_repaintOffsetLimit = (materializedChildLimit - 1).toDouble();
super.layout(constraints);
}
}
...@@ -664,8 +664,6 @@ abstract class ScrollableWidgetListState<T extends ScrollableWidgetList> extends ...@@ -664,8 +664,6 @@ abstract class ScrollableWidgetListState<T extends ScrollableWidgetList> extends
} }
typedef Widget ItemBuilder<T>(BuildContext context, T item, int index);
/// A general scrollable list for a large number of children that might not all /// 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. Prefer [ScrollableWidgetList] when all the children
/// have the same height because it can use that property to be more efficient. /// have the same height because it can use that property to be more efficient.
......
...@@ -116,11 +116,11 @@ class _GridViewportElement extends VirtualViewportElement<GridViewport> { ...@@ -116,11 +116,11 @@ class _GridViewportElement extends VirtualViewportElement<GridViewport> {
int get materializedChildCount => _materializedChildCount; int get materializedChildCount => _materializedChildCount;
int _materializedChildCount; int _materializedChildCount;
double get repaintOffsetBase => _repaintOffsetBase; double get startOffsetBase => _startOffsetBase;
double _repaintOffsetBase; double _startOffsetBase;
double get repaintOffsetLimit =>_repaintOffsetLimit; double get startOffsetLimit =>_startOffsetLimit;
double _repaintOffsetLimit; double _startOffsetLimit;
void updateRenderObject() { void updateRenderObject() {
renderObject.delegate = widget.delegate; renderObject.delegate = widget.delegate;
...@@ -141,8 +141,8 @@ class _GridViewportElement extends VirtualViewportElement<GridViewport> { ...@@ -141,8 +141,8 @@ class _GridViewportElement extends VirtualViewportElement<GridViewport> {
_materializedChildBase = (materializedRowBase * _specification.columnCount).clamp(0, renderObject.virtualChildCount); _materializedChildBase = (materializedRowBase * _specification.columnCount).clamp(0, renderObject.virtualChildCount);
_materializedChildCount = (materializedRowLimit * _specification.columnCount).clamp(0, renderObject.virtualChildCount) - _materializedChildBase; _materializedChildCount = (materializedRowLimit * _specification.columnCount).clamp(0, renderObject.virtualChildCount) - _materializedChildBase;
_repaintOffsetBase = _specification.rowOffsets[materializedRowBase]; _startOffsetBase = _specification.rowOffsets[materializedRowBase];
_repaintOffsetLimit = _specification.rowOffsets[materializedRowLimit] - containerExtent; _startOffsetLimit = _specification.rowOffsets[materializedRowLimit] - containerExtent;
super.layout(constraints); super.layout(constraints);
......
...@@ -129,11 +129,11 @@ class _ListViewportElement extends VirtualViewportElement<ListViewport> { ...@@ -129,11 +129,11 @@ class _ListViewportElement extends VirtualViewportElement<ListViewport> {
int get materializedChildCount => _materializedChildCount; int get materializedChildCount => _materializedChildCount;
int _materializedChildCount; int _materializedChildCount;
double get repaintOffsetBase => _repaintOffsetBase; double get startOffsetBase => _startOffsetBase;
double _repaintOffsetBase; double _startOffsetBase;
double get repaintOffsetLimit =>_repaintOffsetLimit; double get startOffsetLimit =>_startOffsetLimit;
double _repaintOffsetLimit; double _startOffsetLimit;
void updateRenderObject() { void updateRenderObject() {
renderObject.scrollDirection = widget.scrollDirection; renderObject.scrollDirection = widget.scrollDirection;
...@@ -156,21 +156,25 @@ class _ListViewportElement extends VirtualViewportElement<ListViewport> { ...@@ -156,21 +156,25 @@ class _ListViewportElement extends VirtualViewportElement<ListViewport> {
} }
void layout(BoxConstraints constraints) { void layout(BoxConstraints constraints) {
int length = renderObject.virtualChildCount; final int length = renderObject.virtualChildCount;
final double itemExtent = widget.itemExtent;
double contentExtent = widget.itemExtent * length; double contentExtent = widget.itemExtent * length;
double containerExtent = _getContainerExtentFromRenderObject(); double containerExtent = _getContainerExtentFromRenderObject();
_materializedChildBase = math.max(0, widget.startOffset ~/ widget.itemExtent); _materializedChildBase = math.max(0, widget.startOffset ~/ itemExtent);
int materializedChildLimit = math.max(0, ((widget.startOffset + containerExtent) / widget.itemExtent).ceil()); int materializedChildLimit = math.max(0, ((widget.startOffset + containerExtent) / itemExtent).ceil());
if (!widget.itemsWrap) { if (!widget.itemsWrap) {
_materializedChildBase = math.min(length, _materializedChildBase); _materializedChildBase = math.min(length, _materializedChildBase);
materializedChildLimit = math.min(length, materializedChildLimit); materializedChildLimit = math.min(length, materializedChildLimit);
} else if (length == 0) {
materializedChildLimit = _materializedChildBase;
} }
_materializedChildCount = materializedChildLimit - _materializedChildBase; _materializedChildCount = materializedChildLimit - _materializedChildBase;
_repaintOffsetBase = _materializedChildBase * widget.itemExtent; _startOffsetBase = _materializedChildBase * itemExtent;
_repaintOffsetLimit = materializedChildLimit * widget.itemExtent - containerExtent; _startOffsetLimit = materializedChildLimit * itemExtent - containerExtent;
super.layout(constraints); super.layout(constraints);
......
...@@ -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:math' as math;
import 'basic.dart'; import 'basic.dart';
import 'framework.dart'; import 'framework.dart';
...@@ -20,8 +22,10 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO ...@@ -20,8 +22,10 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
int get materializedChildBase; int get materializedChildBase;
int get materializedChildCount; int get materializedChildCount;
double get repaintOffsetBase; double get startOffsetBase;
double get repaintOffsetLimit; double get startOffsetLimit;
double get paintOffset => -(widget.startOffset - startOffsetBase);
List<Element> _materializedChildren = const <Element>[]; List<Element> _materializedChildren = const <Element>[];
...@@ -61,10 +65,10 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO ...@@ -61,10 +65,10 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
void _updatePaintOffset() { void _updatePaintOffset() {
switch (widget.scrollDirection) { switch (widget.scrollDirection) {
case ScrollDirection.vertical: case ScrollDirection.vertical:
renderObject.paintOffset = new Offset(0.0, -(widget.startOffset - repaintOffsetBase)); renderObject.paintOffset = new Offset(0.0, paintOffset);
break; break;
case ScrollDirection.horizontal: case ScrollDirection.horizontal:
renderObject.paintOffset = new Offset(-(widget.startOffset - repaintOffsetBase), 0.0); renderObject.paintOffset = new Offset(paintOffset, 0.0);
break; break;
} }
} }
...@@ -72,24 +76,25 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO ...@@ -72,24 +76,25 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
void updateRenderObject() { void updateRenderObject() {
renderObject.virtualChildCount = widget.children.length; renderObject.virtualChildCount = widget.children.length;
if (repaintOffsetBase != null) { if (startOffsetBase != null) {
_updatePaintOffset(); _updatePaintOffset();
// If we don't already need layout, we need to request a layout if the // If we don't already need layout, we need to request a layout if the
// viewport has shifted to expose new children. // viewport has shifted to expose new children.
if (!renderObject.needsLayout) { if (!renderObject.needsLayout) {
if (repaintOffsetBase != null && widget.startOffset < repaintOffsetBase) if (startOffsetBase != null && widget.startOffset < startOffsetBase)
renderObject.markNeedsLayout(); renderObject.markNeedsLayout();
else if (repaintOffsetLimit != null && widget.startOffset > repaintOffsetLimit) else if (startOffsetLimit != null && widget.startOffset > startOffsetLimit)
renderObject.markNeedsLayout(); renderObject.markNeedsLayout();
} }
} }
} }
void layout(BoxConstraints constraints) { void layout(BoxConstraints constraints) {
assert(repaintOffsetBase != null); assert(startOffsetBase != null);
assert(repaintOffsetLimit != null); assert(startOffsetLimit != null);
_updatePaintOffset(); _updatePaintOffset();
// TODO(abarth): Set building: true here.
BuildableElement.lockState(_materializeChildren); BuildableElement.lockState(_materializeChildren);
} }
...@@ -119,11 +124,11 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO ...@@ -119,11 +124,11 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
int length = renderObject.virtualChildCount; int length = renderObject.virtualChildCount;
assert(base != null); assert(base != null);
assert(count != null); assert(count != null);
_populateWidgets(base + count); _populateWidgets(base < 0 ? length : math.min(length, base + count));
List<Widget> newWidgets = new List<Widget>(count); List<Widget> newWidgets = new List<Widget>(count);
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
int childIndex = base + i; int childIndex = base + i;
Widget child = _widgets[childIndex % length]; Widget child = _widgets[(childIndex % length).abs()];
Key key = new ValueKey(child.key ?? childIndex); Key key = new ValueKey(child.key ?? childIndex);
newWidgets[i] = new RepaintBoundary(key: key, child: child); newWidgets[i] = new RepaintBoundary(key: key, child: child);
} }
......
...@@ -13,7 +13,7 @@ final List<GlobalKey> globalKeys = defaultPages.map((_) => new GlobalKey()).toLi ...@@ -13,7 +13,7 @@ final List<GlobalKey> globalKeys = defaultPages.map((_) => new GlobalKey()).toLi
int currentPage = null; int currentPage = null;
bool itemsWrap = false; bool itemsWrap = false;
Widget buildPage(BuildContext context, int page, int index) { Widget buildPage(int page) {
return new Container( return new Container(
key: globalKeys[page], key: globalKeys[page],
width: pageSize.width, width: pageSize.width,
...@@ -23,9 +23,8 @@ Widget buildPage(BuildContext context, int page, int index) { ...@@ -23,9 +23,8 @@ Widget buildPage(BuildContext context, int page, int index) {
} }
Widget buildFrame({ List<int> pages: defaultPages }) { Widget buildFrame({ List<int> pages: defaultPages }) {
final list = new PageableList<int>( final list = new PageableList(
items: pages, children: pages.map(buildPage),
itemBuilder: buildPage,
itemsWrap: itemsWrap, itemsWrap: itemsWrap,
scrollDirection: ScrollDirection.horizontal, scrollDirection: ScrollDirection.horizontal,
onPageChanged: (int page) { currentPage = page; } onPageChanged: (int page) { currentPage = page; }
...@@ -136,7 +135,7 @@ void main() { ...@@ -136,7 +135,7 @@ void main() {
testWidgets((WidgetTester tester) { testWidgets((WidgetTester tester) {
currentPage = null; currentPage = null;
itemsWrap = true; itemsWrap = true;
tester.pumpWidget(buildFrame(pages: null)); tester.pumpWidget(buildFrame(pages: <int>[]));
expect(currentPage, isNull); expect(currentPage, isNull);
}); });
}); });
......
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