From e64d93a52089d33d7d3cdd5ddd81e79dad588285 Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Wed, 6 Jan 2016 15:13:40 -0800 Subject: [PATCH] Switch PageableList over to using RenderList This patch moves PageableList off HomogeneousViewport and onto RenderList and friends, making it match the new ScrollableList. --- .../material_gallery/lib/demo/tabs_demo.dart | 2 +- examples/stocks/lib/stock_home.dart | 2 +- examples/widgets/pageable_list.dart | 7 +- packages/flutter/lib/src/material/tabs.dart | 24 +-- packages/flutter/lib/src/rendering/list.dart | 23 ++- .../lib/src/widgets/homogeneous_viewport.dart | 77 ---------- .../lib/src/widgets/pageable_list.dart | 145 +++++++++++++----- .../flutter/lib/src/widgets/scrollable.dart | 2 - .../lib/src/widgets/scrollable_grid.dart | 12 +- .../lib/src/widgets/scrollable_list.dart | 22 +-- .../lib/src/widgets/virtual_viewport.dart | 27 ++-- .../test/widget/pageable_list_test.dart | 9 +- 12 files changed, 179 insertions(+), 173 deletions(-) diff --git a/examples/material_gallery/lib/demo/tabs_demo.dart b/examples/material_gallery/lib/demo/tabs_demo.dart index 74b11515866..7e4235391c2 100644 --- a/examples/material_gallery/lib/demo/tabs_demo.dart +++ b/examples/material_gallery/lib/demo/tabs_demo.dart @@ -29,7 +29,7 @@ class _TabsDemoState extends State { Widget build(_) { return new TabBarView( items: _iconNames, - itemBuilder: (BuildContext context, String iconName, int index) { + itemBuilder: (String iconName) { return new Container( key: new ValueKey(iconName), padding: const EdgeDims.all(12.0), diff --git a/examples/stocks/lib/stock_home.dart b/examples/stocks/lib/stock_home.dart index 3ecdac413be..fd53f0fc89d 100644 --- a/examples/stocks/lib/stock_home.dart +++ b/examples/stocks/lib/stock_home.dart @@ -273,7 +273,7 @@ class StockHomeState extends State { drawer: _buildDrawer(context), body: new TabBarView( items: [StockHomeTab.market, StockHomeTab.portfolio], - itemBuilder: (BuildContext context, StockHomeTab tab, _) { + itemBuilder: (StockHomeTab tab) { switch (tab) { case StockHomeTab.market: return _buildStockTab(context, tab, config.symbols); diff --git a/examples/widgets/pageable_list.dart b/examples/widgets/pageable_list.dart index a0f5ae8e9d1..9d2f1e027fb 100644 --- a/examples/widgets/pageable_list.dart +++ b/examples/widgets/pageable_list.dart @@ -41,7 +41,7 @@ class PageableListAppState extends State { ScrollDirection scrollDirection = ScrollDirection.horizontal; bool itemsWrap = false; - Widget buildCard(BuildContext context, CardModel cardModel, int index) { + Widget buildCard(CardModel cardModel) { Widget card = new Card( color: cardModel.color, child: new Container( @@ -114,10 +114,9 @@ class PageableListAppState extends State { } Widget _buildBody(BuildContext context) { - return new PageableList( - items: cardModels, + return new PageableList( + children: cardModels.map(buildCard), itemsWrap: itemsWrap, - itemBuilder: buildCard, scrollDirection: scrollDirection ); } diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index ecbdfe39b9f..83f104db6b3 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -772,26 +772,30 @@ class _TabBarState extends ScrollableState> implements TabBarSelect } } -class TabBarView extends PageableList { +typedef Widget TabItemBuilder(T item); + +class TabBarView extends PageableList { TabBarView({ Key key, List items, - ItemBuilder itemBuilder - }) : super( + TabItemBuilder itemBuilder + }) : items = items, itemBuilder = itemBuilder, super( key: key, scrollDirection: ScrollDirection.horizontal, - items: items, - itemBuilder: itemBuilder, + children: items.map((T item) => itemBuilder(item)), itemsWrap: false ) { assert(items != null); assert(items.length > 1); } + final List items; + final TabItemBuilder itemBuilder; + _TabBarViewState createState() => new _TabBarViewState(); } -class _TabBarViewState extends PageableListState> implements TabBarSelectionPerformanceListener { +class _TabBarViewState extends PageableListState> implements TabBarSelectionPerformanceListener { TabBarSelectionState _selection; List _itemIndices = [0, 1]; @@ -916,14 +920,10 @@ class _TabBarViewState extends PageableListState> implements return settleScrollOffset(); } - List buildItems(BuildContext context, int start, int count) { + Widget buildContent(BuildContext context) { TabBarSelectionState newSelection = TabBarSelection.of(context); if (_selection != newSelection) _initSelection(newSelection); - return _itemIndices - .skip(start) - .take(count) - .map((int i) => config.itemBuilder(context, config.items[i], i)) - .toList(); + return super.buildContent(context); } } diff --git a/packages/flutter/lib/src/rendering/list.dart b/packages/flutter/lib/src/rendering/list.dart index b94284f2227..786cb05f09b 100644 --- a/packages/flutter/lib/src/rendering/list.dart +++ b/packages/flutter/lib/src/rendering/list.dart @@ -28,7 +28,6 @@ class RenderList extends RenderVirtualViewport implements HasScr paintOffset: paintOffset, callback: callback ) { - assert(itemExtent != null); addAll(children); } @@ -75,6 +74,8 @@ class RenderList extends RenderVirtualViewport implements HasScr } double get _preferredExtent { + if (itemExtent == null) + return double.INFINITY; double extent = itemExtent * virtualChildCount; if (padding != null) extent += _scrollAxisPadding; @@ -118,8 +119,16 @@ class RenderList extends RenderVirtualViewport implements HasScr } void performLayout() { - size = new Size(constraints.maxWidth, - constraints.constrainHeight(_preferredExtent)); + switch (scrollDirection) { + case ScrollDirection.vertical: + size = new Size(constraints.maxWidth, + constraints.constrainHeight(_preferredExtent)); + break; + case ScrollDirection.horizontal: + size = new Size(constraints.constrainWidth(_preferredExtent), + constraints.maxHeight); + break; + } if (callback != null) invokeLayoutCallback(callback); @@ -136,15 +145,15 @@ class RenderList extends RenderVirtualViewport implements HasScr switch (scrollDirection) { case ScrollDirection.vertical: 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; - dy = itemExtent; + dy = itemHeight; break; case ScrollDirection.horizontal: - itemWidth = itemExtent; + itemWidth = itemExtent ?? size.width; itemHeight = math.max(0, size.height - (padding == null ? 0.0 : padding.vertical)); x = padding != null ? padding.left : 0.0; - dx = itemExtent; + dx = itemWidth; break; } diff --git a/packages/flutter/lib/src/widgets/homogeneous_viewport.dart b/packages/flutter/lib/src/widgets/homogeneous_viewport.dart index f6786340c73..4047047655d 100644 --- a/packages/flutter/lib/src/widgets/homogeneous_viewport.dart +++ b/packages/flutter/lib/src/widgets/homogeneous_viewport.dart @@ -220,80 +220,3 @@ class _HomogeneousViewportElement extends _ViewportBaseElement new _HomogeneousPageViewportElement(this); -} - -class _HomogeneousPageViewportElement extends _ViewportBaseElement { - _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; - } -} diff --git a/packages/flutter/lib/src/widgets/pageable_list.dart b/packages/flutter/lib/src/widgets/pageable_list.dart index 0b9ed3da13a..1cf0402e897 100644 --- a/packages/flutter/lib/src/widgets/pageable_list.dart +++ b/packages/flutter/lib/src/widgets/pageable_list.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:math' as math; import 'package:flutter/animation.dart'; import 'package:flutter/gestures.dart'; @@ -11,8 +10,8 @@ import 'package:flutter/rendering.dart'; import 'basic.dart'; import 'framework.dart'; -import 'homogeneous_viewport.dart'; import 'scrollable.dart'; +import 'virtual_viewport.dart'; /// Controls what alignment items use when settling. enum ItemsSnapAlignment { @@ -22,7 +21,7 @@ enum ItemsSnapAlignment { typedef void PageChangedCallback(int newPage); -class PageableList extends Scrollable { +class PageableList extends Scrollable { PageableList({ Key key, initialScrollOffset, @@ -32,14 +31,13 @@ class PageableList extends Scrollable { ScrollListener onScrollEnd, SnapOffsetCallback snapOffsetCallback, double snapAlignmentOffset: 0.0, - this.items, - this.itemBuilder, this.itemsWrap: false, this.itemsSnapAlignment: ItemsSnapAlignment.adjacentItem, this.onPageChanged, this.scrollableListPainter, this.duration: const Duration(milliseconds: 200), - this.curve: Curves.ease + this.curve: Curves.ease, + this.children }) : super( key: key, initialScrollOffset: initialScrollOffset, @@ -51,21 +49,19 @@ class PageableList extends Scrollable { snapAlignmentOffset: snapAlignmentOffset ); - final List items; - final ItemBuilder itemBuilder; - final ItemsSnapAlignment itemsSnapAlignment; final bool itemsWrap; + final ItemsSnapAlignment itemsSnapAlignment; final PageChangedCallback onPageChanged; final ScrollableListPainter scrollableListPainter; final Duration duration; final Curve curve; + final Iterable children; - PageableListState> createState() => new PageableListState>(); + PageableListState createState() => new PageableListState(); } -class PageableListState> extends ScrollableState { - - int get itemCount => config.items?.length ?? 0; +class PageableListState extends ScrollableState { + int get itemCount => config.children?.length ?? 0; int _previousItemCount; double pixelToScrollOffset(double value) { @@ -76,7 +72,12 @@ class PageableListState> extends ScrollableSta return pixelScrollExtent == 0.0 ? 0.0 : value / pixelScrollExtent; } - void didUpdateConfig(Config oldConfig) { + void initState() { + super.initState(); + _updateScrollBehavior(); + } + + void didUpdateConfig(PageableList oldConfig) { super.didUpdateConfig(oldConfig); bool scrollBehaviorUpdateNeeded = config.scrollDirection != oldConfig.scrollDirection; @@ -94,9 +95,7 @@ class PageableListState> extends ScrollableSta } void _updateScrollBehavior() { - // if you don't call this from build(), you must call it from setState(). - if (config.scrollableListPainter != null) - config.scrollableListPainter.contentExtent = itemCount.toDouble(); + config.scrollableListPainter?.contentExtent = itemCount.toDouble(); scrollTo(scrollBehavior.updateExtents( contentExtent: itemCount.toDouble(), containerExtent: 1.0, @@ -111,8 +110,7 @@ class PageableListState> extends ScrollableSta void dispatchOnScroll() { super.dispatchOnScroll(); - if (config.scrollableListPainter != null) - config.scrollableListPainter.scrollOffset = scrollOffset; + config.scrollableListPainter?.scrollOffset = scrollOffset; } void dispatchOnScrollEnd() { @@ -121,17 +119,12 @@ class PageableListState> extends ScrollableSta } Widget buildContent(BuildContext context) { - if (itemCount != _previousItemCount) { - _previousItemCount = itemCount; - _updateScrollBehavior(); - } - return new HomogeneousPageViewport( - builder: buildItems, + return new PageViewport( itemsWrap: config.itemsWrap, - itemCount: itemCount, - direction: config.scrollDirection, + scrollDirection: config.scrollDirection, startOffset: scrollOffset, - overlayPainter: config.scrollableListPainter + overlayPainter: config.scrollableListPainter, + children: config.children ); } @@ -180,18 +173,94 @@ class PageableListState> extends ScrollableSta .then(_notifyPageChanged); } - List buildItems(BuildContext context, int start, int count) { - final List result = new List(); - 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(_) { if (config.onPageChanged != null) 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 children; + + RenderList createRenderObject() => new RenderList(); + + _PageViewportElement createElement() => new _PageViewportElement(this); +} + +class _PageViewportElement extends VirtualViewportElement { + _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); + } +} diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index 2af0e69f868..0d777280fe4 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -664,8 +664,6 @@ abstract class ScrollableWidgetListState extends } -typedef Widget ItemBuilder(BuildContext context, T item, int index); - /// 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. diff --git a/packages/flutter/lib/src/widgets/scrollable_grid.dart b/packages/flutter/lib/src/widgets/scrollable_grid.dart index 850cc54f433..58d7789f6e1 100644 --- a/packages/flutter/lib/src/widgets/scrollable_grid.dart +++ b/packages/flutter/lib/src/widgets/scrollable_grid.dart @@ -116,11 +116,11 @@ class _GridViewportElement extends VirtualViewportElement { int get materializedChildCount => _materializedChildCount; int _materializedChildCount; - double get repaintOffsetBase => _repaintOffsetBase; - double _repaintOffsetBase; + double get startOffsetBase => _startOffsetBase; + double _startOffsetBase; - double get repaintOffsetLimit =>_repaintOffsetLimit; - double _repaintOffsetLimit; + double get startOffsetLimit =>_startOffsetLimit; + double _startOffsetLimit; void updateRenderObject() { renderObject.delegate = widget.delegate; @@ -141,8 +141,8 @@ class _GridViewportElement extends VirtualViewportElement { _materializedChildBase = (materializedRowBase * _specification.columnCount).clamp(0, renderObject.virtualChildCount); _materializedChildCount = (materializedRowLimit * _specification.columnCount).clamp(0, renderObject.virtualChildCount) - _materializedChildBase; - _repaintOffsetBase = _specification.rowOffsets[materializedRowBase]; - _repaintOffsetLimit = _specification.rowOffsets[materializedRowLimit] - containerExtent; + _startOffsetBase = _specification.rowOffsets[materializedRowBase]; + _startOffsetLimit = _specification.rowOffsets[materializedRowLimit] - containerExtent; super.layout(constraints); diff --git a/packages/flutter/lib/src/widgets/scrollable_list.dart b/packages/flutter/lib/src/widgets/scrollable_list.dart index 56e12e66111..a9d5f2f3142 100644 --- a/packages/flutter/lib/src/widgets/scrollable_list.dart +++ b/packages/flutter/lib/src/widgets/scrollable_list.dart @@ -129,11 +129,11 @@ class _ListViewportElement extends VirtualViewportElement { int get materializedChildCount => _materializedChildCount; int _materializedChildCount; - double get repaintOffsetBase => _repaintOffsetBase; - double _repaintOffsetBase; + double get startOffsetBase => _startOffsetBase; + double _startOffsetBase; - double get repaintOffsetLimit =>_repaintOffsetLimit; - double _repaintOffsetLimit; + double get startOffsetLimit =>_startOffsetLimit; + double _startOffsetLimit; void updateRenderObject() { renderObject.scrollDirection = widget.scrollDirection; @@ -156,21 +156,25 @@ class _ListViewportElement extends VirtualViewportElement { } void layout(BoxConstraints constraints) { - int length = renderObject.virtualChildCount; + final int length = renderObject.virtualChildCount; + final double itemExtent = widget.itemExtent; + double contentExtent = widget.itemExtent * length; double containerExtent = _getContainerExtentFromRenderObject(); - _materializedChildBase = math.max(0, widget.startOffset ~/ widget.itemExtent); - int materializedChildLimit = math.max(0, ((widget.startOffset + containerExtent) / widget.itemExtent).ceil()); + _materializedChildBase = math.max(0, widget.startOffset ~/ itemExtent); + int materializedChildLimit = math.max(0, ((widget.startOffset + containerExtent) / itemExtent).ceil()); if (!widget.itemsWrap) { _materializedChildBase = math.min(length, _materializedChildBase); materializedChildLimit = math.min(length, materializedChildLimit); + } else if (length == 0) { + materializedChildLimit = _materializedChildBase; } _materializedChildCount = materializedChildLimit - _materializedChildBase; - _repaintOffsetBase = _materializedChildBase * widget.itemExtent; - _repaintOffsetLimit = materializedChildLimit * widget.itemExtent - containerExtent; + _startOffsetBase = _materializedChildBase * itemExtent; + _startOffsetLimit = materializedChildLimit * itemExtent - containerExtent; super.layout(constraints); diff --git a/packages/flutter/lib/src/widgets/virtual_viewport.dart b/packages/flutter/lib/src/widgets/virtual_viewport.dart index 5898faf4d96..9620a3feecd 100644 --- a/packages/flutter/lib/src/widgets/virtual_viewport.dart +++ b/packages/flutter/lib/src/widgets/virtual_viewport.dart @@ -2,6 +2,8 @@ // 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 'basic.dart'; import 'framework.dart'; @@ -20,8 +22,10 @@ abstract class VirtualViewportElement extends RenderO int get materializedChildBase; int get materializedChildCount; - double get repaintOffsetBase; - double get repaintOffsetLimit; + double get startOffsetBase; + double get startOffsetLimit; + + double get paintOffset => -(widget.startOffset - startOffsetBase); List _materializedChildren = const []; @@ -61,10 +65,10 @@ abstract class VirtualViewportElement extends RenderO void _updatePaintOffset() { switch (widget.scrollDirection) { case ScrollDirection.vertical: - renderObject.paintOffset = new Offset(0.0, -(widget.startOffset - repaintOffsetBase)); + renderObject.paintOffset = new Offset(0.0, paintOffset); break; case ScrollDirection.horizontal: - renderObject.paintOffset = new Offset(-(widget.startOffset - repaintOffsetBase), 0.0); + renderObject.paintOffset = new Offset(paintOffset, 0.0); break; } } @@ -72,24 +76,25 @@ abstract class VirtualViewportElement extends RenderO void updateRenderObject() { renderObject.virtualChildCount = widget.children.length; - if (repaintOffsetBase != null) { + if (startOffsetBase != null) { _updatePaintOffset(); // If we don't already need layout, we need to request a layout if the // viewport has shifted to expose new children. if (!renderObject.needsLayout) { - if (repaintOffsetBase != null && widget.startOffset < repaintOffsetBase) + if (startOffsetBase != null && widget.startOffset < startOffsetBase) renderObject.markNeedsLayout(); - else if (repaintOffsetLimit != null && widget.startOffset > repaintOffsetLimit) + else if (startOffsetLimit != null && widget.startOffset > startOffsetLimit) renderObject.markNeedsLayout(); } } } void layout(BoxConstraints constraints) { - assert(repaintOffsetBase != null); - assert(repaintOffsetLimit != null); + assert(startOffsetBase != null); + assert(startOffsetLimit != null); _updatePaintOffset(); + // TODO(abarth): Set building: true here. BuildableElement.lockState(_materializeChildren); } @@ -119,11 +124,11 @@ abstract class VirtualViewportElement extends RenderO int length = renderObject.virtualChildCount; assert(base != null); assert(count != null); - _populateWidgets(base + count); + _populateWidgets(base < 0 ? length : math.min(length, base + count)); List newWidgets = new List(count); for (int i = 0; i < count; ++i) { int childIndex = base + i; - Widget child = _widgets[childIndex % length]; + Widget child = _widgets[(childIndex % length).abs()]; Key key = new ValueKey(child.key ?? childIndex); newWidgets[i] = new RepaintBoundary(key: key, child: child); } diff --git a/packages/flutter/test/widget/pageable_list_test.dart b/packages/flutter/test/widget/pageable_list_test.dart index 7945016b67c..d1c1783d5c4 100644 --- a/packages/flutter/test/widget/pageable_list_test.dart +++ b/packages/flutter/test/widget/pageable_list_test.dart @@ -13,7 +13,7 @@ final List globalKeys = defaultPages.map((_) => new GlobalKey()).toLi int currentPage = null; bool itemsWrap = false; -Widget buildPage(BuildContext context, int page, int index) { +Widget buildPage(int page) { return new Container( key: globalKeys[page], width: pageSize.width, @@ -23,9 +23,8 @@ Widget buildPage(BuildContext context, int page, int index) { } Widget buildFrame({ List pages: defaultPages }) { - final list = new PageableList( - items: pages, - itemBuilder: buildPage, + final list = new PageableList( + children: pages.map(buildPage), itemsWrap: itemsWrap, scrollDirection: ScrollDirection.horizontal, onPageChanged: (int page) { currentPage = page; } @@ -136,7 +135,7 @@ void main() { testWidgets((WidgetTester tester) { currentPage = null; itemsWrap = true; - tester.pumpWidget(buildFrame(pages: null)); + tester.pumpWidget(buildFrame(pages: [])); expect(currentPage, isNull); }); });