Commit b1f9138f authored by Adam Barth's avatar Adam Barth

Switch clients of ScrollableList to ScrollableList2

This patch also changed ScrollableList2 to use an Iterable instead of an
List for its children. This change lets clients map their underlying
data lazily. If the clients actually have a concrete list, we skip the
extra copy and grab the child list directly.
parent 37106ea6
......@@ -14,11 +14,10 @@ class FitnessItemList extends StatelessComponent {
final FitnessItemHandler onDismissed;
Widget build(BuildContext context) {
return new ScrollableList<FitnessItem>(
return new ScrollableList2(
padding: const EdgeDims.all(4.0),
items: items,
itemExtent: kFitnessItemHeight,
itemBuilder: (BuildContext context, FitnessItem item, int index) => item.toRow(onDismissed: onDismissed)
children: items.map((FitnessItem item) => item.toRow(onDismissed: onDismissed))
);
}
}
......
......@@ -14,10 +14,9 @@ class StockList extends StatelessComponent {
final StockRowActionCallback onAction;
Widget build(BuildContext context) {
return new ScrollableList<Stock>(
items: stocks,
return new ScrollableList2(
itemExtent: StockRow.kHeight,
itemBuilder: (BuildContext context, Stock stock, int index) {
children: stocks.map((Stock stock) {
return new StockRow(
keySalt: keySalt,
stock: stock,
......@@ -25,7 +24,7 @@ class StockList extends StatelessComponent {
onDoubleTap: onShow,
onLongPressed: onAction
);
}
})
);
}
}
......@@ -393,12 +393,11 @@ class CardCollectionState extends State<CardCollection> {
Widget build(BuildContext context) {
Widget cardCollection;
if (_fixedSizeCards) {
cardCollection = new ScrollableList<CardModel> (
cardCollection = new ScrollableList2 (
snapOffsetCallback: _snapToCenter ? _toSnapOffset : null,
snapAlignmentOffset: _cardCollectionSize.height / 2.0,
items: _cardModels,
itemBuilder: (BuildContext context, CardModel card, int index) => _buildCard(context, card.value),
itemExtent: _cardModels[0].height
itemExtent: _cardModels[0].height,
children: _cardModels.map((CardModel card) => _buildCard(context, card.value))
);
} else {
cardCollection = new ScrollableMixedWidgetList(
......
......@@ -74,12 +74,12 @@ class MediaQueryExample extends StatelessComponent {
if (MediaQuery.of(context).size.width < _gridViewBreakpoint) {
return new ScrollableList2(
itemExtent: 50.0,
children: items.map((AdaptiveItem item) => item.toListItem()).toList()
children: items.map((AdaptiveItem item) => item.toListItem())
);
} else {
return new ScrollableGrid(
delegate: new MaxTileWidthGridDelegate(maxTileWidth: _maxTileWidth),
children: items.map((AdaptiveItem item) => item.toCard()).toList()
children: items.map((AdaptiveItem item) => item.toCard())
);
}
}
......
......@@ -5,6 +5,21 @@
import 'package:intl/intl.dart';
import 'package:flutter/material.dart';
final NumberFormat _dd = new NumberFormat("00", "en_US");
class _Item extends StatelessComponent {
_Item(this.index);
int index;
Widget build(BuildContext context) {
return new Text('Item ${_dd.format(index)}',
key: new ValueKey<int>(index),
style: Theme.of(context).text.title
);
}
}
class ScrollbarApp extends StatefulComponent {
ScrollbarAppState createState() => new ScrollbarAppState();
}
......@@ -15,17 +30,10 @@ class ScrollbarAppState extends State<ScrollbarApp> {
final ScrollbarPainter _scrollbarPainter = new ScrollbarPainter();
Widget _buildMenu(BuildContext context) {
NumberFormat dd = new NumberFormat("00", "en_US");
return new ScrollableList<int>(
items: new List<int>.generate(_itemCount, (int i) => i),
return new ScrollableList2(
itemExtent: _itemExtent,
itemBuilder: (_, __, int index) {
return new Text('Item ${dd.format(index)}',
key: new ValueKey<int>(index),
style: Theme.of(context).text.title
);
},
scrollableListPainter: _scrollbarPainter
scrollableListPainter: _scrollbarPainter,
children: new List<Widget>.generate(_itemCount, (int i) => new _Item(i))
);
}
......
......@@ -21,44 +21,35 @@ Map<MaterialListType, double> _kItemExtent = const <MaterialListType, double>{
MaterialListType.threeLine: kThreeLineListItemHeight,
};
class MaterialList<T> extends StatefulComponent {
class MaterialList extends StatefulComponent {
MaterialList({
Key key,
this.initialScrollOffset,
this.onScroll,
this.items,
this.itemBuilder,
this.type: MaterialListType.twoLine
this.type: MaterialListType.twoLine,
this.children
}) : super(key: key);
final double initialScrollOffset;
final ScrollListener onScroll;
final List<T> items;
final ItemBuilder<T> itemBuilder;
final MaterialListType type;
final Iterable<Widget> children;
_MaterialListState<T> createState() => new _MaterialListState<T>();
_MaterialListState createState() => new _MaterialListState();
}
class _MaterialListState<T> extends State<MaterialList<T>> {
void initState() {
super.initState();
_scrollbarPainter = new ScrollbarPainter();
}
ScrollbarPainter _scrollbarPainter;
class _MaterialListState extends State<MaterialList> {
ScrollbarPainter _scrollbarPainter = new ScrollbarPainter();
Widget build(BuildContext context) {
return new ScrollableList<T>(
return new ScrollableList2(
initialScrollOffset: config.initialScrollOffset,
scrollDirection: ScrollDirection.vertical,
onScroll: config.onScroll,
items: config.items,
itemBuilder: config.itemBuilder,
itemExtent: _kItemExtent[config.type],
padding: const EdgeDims.symmetric(vertical: 8.0),
scrollableListPainter: _scrollbarPainter
scrollableListPainter: _scrollbarPainter,
children: config.children
);
}
}
......@@ -36,7 +36,7 @@ typedef double _Constrainer(double value);
abstract class RenderBlockBase extends RenderBox
with ContainerRenderObjectMixin<RenderBox, BlockParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, BlockParentData>
implements RenderScrollable {
implements HasScrollDirection {
RenderBlockBase({
List<RenderBox> children,
......
......@@ -11,7 +11,7 @@ import 'viewport.dart';
/// Parent data for use with [RenderList].
class ListParentData extends ContainerBoxParentDataMixin<RenderBox> { }
class RenderList extends RenderVirtualViewport<ListParentData> implements RenderScrollable {
class RenderList extends RenderVirtualViewport<ListParentData> implements HasScrollDirection {
RenderList({
List<RenderBox> children,
double itemExtent,
......
......@@ -18,7 +18,7 @@ enum ScrollDirection {
vertical,
}
abstract class RenderScrollable {
abstract class HasScrollDirection {
ScrollDirection get scrollDirection;
}
......@@ -32,7 +32,7 @@ abstract class RenderScrollable {
/// Viewport is the core scrolling primitive in the system, but it can be used
/// in other situations.
class RenderViewport extends RenderBox with RenderObjectWithChildMixin<RenderBox>
implements RenderScrollable {
implements HasScrollDirection {
RenderViewport({
RenderBox child,
......
......@@ -446,14 +446,15 @@ class Block extends StatelessComponent {
abstract class ScrollableListPainter extends Painter {
void attach(RenderObject renderObject) {
assert(renderObject is RenderBox);
assert(renderObject is RenderScrollable);
assert(renderObject is HasScrollDirection);
super.attach(renderObject);
}
RenderBox get renderObject => super.renderObject;
ScrollDirection get scrollDirection {
return (renderObject as RenderScrollable)?.scrollDirection;
HasScrollDirection scrollable = renderObject as dynamic;
return scrollable?.scrollDirection;
}
Size get viewportSize => renderObject.size;
......
......@@ -36,7 +36,7 @@ class ScrollableGrid extends Scrollable {
);
final GridDelegate delegate;
final List<Widget> children;
final Iterable<Widget> children;
ScrollableState createState() => new _ScrollableGridState();
}
......@@ -77,7 +77,7 @@ class GridViewport extends VirtualViewport {
final double startOffset;
final GridDelegate delegate;
final ExtentsChangedCallback onExtentsChanged;
final List<Widget> children;
final Iterable<Widget> children;
// TODO(abarth): Support horizontal scrolling;
ScrollDirection get scrollDirection => ScrollDirection.vertical;
......@@ -139,8 +139,8 @@ class _GridViewportElement extends VirtualViewportElement<GridViewport> {
int materializedRowBase = math.max(0, _lowerBound(_specification.rowOffsets, widget.startOffset) - 1);
int materializedRowLimit = math.min(_specification.rowCount, _lowerBound(_specification.rowOffsets, widget.startOffset + containerExtent));
_materializedChildBase = (materializedRowBase * _specification.columnCount).clamp(0, widget.children.length);
_materializedChildCount = (materializedRowLimit * _specification.columnCount).clamp(0, widget.children.length) - _materializedChildBase;
_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];
......
......@@ -39,7 +39,7 @@ class ScrollableList2 extends Scrollable {
final bool itemsWrap;
final EdgeDims padding;
final ScrollableListPainter scrollableListPainter;
final List<Widget> children;
final Iterable<Widget> children;
ScrollableState createState() => new _ScrollableList2State();
}
......@@ -111,7 +111,7 @@ class ListViewport extends VirtualViewport {
final bool itemsWrap;
final EdgeDims padding;
final Painter overlayPainter;
final List<Widget> children;
final Iterable<Widget> children;
RenderList createRenderObject() => new RenderList(itemExtent: itemExtent);
......@@ -156,14 +156,14 @@ class _ListViewportElement extends VirtualViewportElement<ListViewport> {
}
void layout(BoxConstraints constraints) {
double contentExtent = widget.itemExtent * widget.children.length;
int length = renderObject.virtualChildCount;
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());
if (!widget.itemsWrap) {
int length = widget.children.length;
_materializedChildBase = math.min(length, _materializedChildBase);
materializedChildLimit = math.min(length, materializedChildLimit);
}
......
......@@ -12,7 +12,7 @@ typedef void ExtentsChangedCallback(double contentExtent, double containerExtent
abstract class VirtualViewport extends RenderObjectWidget {
double get startOffset;
ScrollDirection get scrollDirection;
List<Widget> get children;
Iterable<Widget> get children;
}
abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderObjectElement<T> {
......@@ -36,6 +36,8 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_iterator = null;
_widgets = <Widget>[];
renderObject.callback = layout;
updateRenderObject();
}
......@@ -46,6 +48,10 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
}
void update(T newWidget) {
if (widget.children != newWidget.children) {
_iterator = null;
_widgets = <Widget>[];
}
super.update(newWidget);
updateRenderObject();
if (!renderObject.needsLayout)
......@@ -96,15 +102,37 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
BuildableElement.lockState(_materializeChildren);
}
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 + count);
List<Widget> newWidgets = new List<Widget>(count);
for (int i = 0; i < count; ++i) {
int childIndex = base + i;
Widget child = widget.children[childIndex % widget.children.length];
Widget child = _widgets[childIndex % length];
Key key = new ValueKey(child.key ?? childIndex);
newWidgets[i] = new RepaintBoundary(key: key, child: child);
}
......
......@@ -21,7 +21,7 @@ void handleOnDismissed(int item) {
dismissedItems.add(item);
}
Widget buildDismissableItem(BuildContext context, int item, int index) {
Widget buildDismissableItem(int item) {
return new Dismissable(
key: new ValueKey<int>(item),
direction: dismissDirection,
......@@ -38,11 +38,12 @@ Widget buildDismissableItem(BuildContext context, int item, int index) {
Widget widgetBuilder() {
return new Container(
padding: const EdgeDims.all(10.0),
child: new ScrollableList<int>(
items: <int>[0, 1, 2, 3, 4].where((int i) => !dismissedItems.contains(i)).toList(),
itemBuilder: buildDismissableItem,
child: new ScrollableList2(
scrollDirection: scrollDirection,
itemExtent: itemExtent
itemExtent: itemExtent,
children: <int>[0, 1, 2, 3, 4].where(
(int i) => !dismissedItems.contains(i)
).map(buildDismissableItem)
)
);
}
......
......@@ -99,16 +99,15 @@ void main() {
(key.currentState as StateMarkerState).marker = "marked";
tester.pumpWidget(new ScrollableList<int>(
items: <int>[0],
tester.pumpWidget(new ScrollableList2(
itemExtent: 100.0,
itemBuilder: (BuildContext context, int item, int index) {
return new Container(
children: <Widget>[
new Container(
key: new Key('container'),
height: 100.0,
child: new StateMarker(key: key)
);
}
)
]
));
expect((key.currentState as StateMarkerState).marker, equals("marked"));
......
......@@ -17,20 +17,18 @@ void main() {
tester.pumpWidget(new Center(
child: new Container(
height: 50.0,
child: new ScrollableList<int>(
child: new ScrollableList2(
key: new GlobalKey(),
items: items,
itemBuilder: (BuildContext context, int item, int index) {
itemExtent: 290.0,
scrollDirection: ScrollDirection.horizontal,
children: items.map((int item) {
return new Container(
key: new ValueKey<int>(item),
child: new GestureDetector(
onTap: () { tapped.add(item); },
child: new Text('$item')
)
);
},
itemExtent: 290.0,
scrollDirection: ScrollDirection.horizontal
})
)
)
));
......@@ -59,20 +57,18 @@ void main() {
tester.pumpWidget(new Center(
child: new Container(
width: 50.0,
child: new ScrollableList<int>(
child: new ScrollableList2(
key: new GlobalKey(),
items: items,
itemBuilder: (BuildContext context, int item, int index) {
itemExtent: 290.0,
scrollDirection: ScrollDirection.vertical,
children: items.map((int item) {
return new Container(
key: new ValueKey<int>(item),
child: new GestureDetector(
onTap: () { tapped.add(item); },
child: new Text('$item')
)
);
},
itemExtent: 290.0,
scrollDirection: ScrollDirection.vertical
})
)
)
));
......
......@@ -13,16 +13,14 @@ Widget buildFrame() {
return new Center(
child: new Container(
height: 50.0,
child: new ScrollableList<int>(
items: items,
itemBuilder: (BuildContext context, int item, int index) {
child: new ScrollableList2(
itemExtent: 290.0,
scrollDirection: ScrollDirection.horizontal,
children: items.map((int item) {
return new Container(
key: new ValueKey<int>(item),
child: new Text('$item')
);
},
itemExtent: 290.0,
scrollDirection: ScrollDirection.horizontal
})
)
)
);
......
......@@ -9,16 +9,14 @@ import 'package:test/test.dart';
const List<int> items = const <int>[0, 1, 2, 3, 4, 5];
Widget buildFrame() {
return new ScrollableList<int>(
items: items,
itemBuilder: (BuildContext context, int item, int index) {
return new ScrollableList2(
itemExtent: 290.0,
scrollDirection: ScrollDirection.vertical,
children: items.map((int item) {
return new Container(
key: new ValueKey<int>(item),
child: new Text('$item')
);
},
itemExtent: 290.0,
scrollDirection: ScrollDirection.vertical
})
);
}
......
......@@ -12,9 +12,8 @@ const double itemExtent = 200.0;
ScrollDirection scrollDirection = ScrollDirection.vertical;
GlobalKey scrollableListKey;
Widget buildItem(BuildContext context, int item, int index) {
Widget buildItem(int item) {
return new Container(
key: new ValueKey<int>(item),
width: itemExtent,
height: itemExtent,
child: new Text(item.toString())
......@@ -30,13 +29,12 @@ Widget buildFrame() {
return new Center(
child: new Container(
height: itemExtent * 2.0,
child: new ScrollableList<int>(
child: new ScrollableList2(
key: scrollableListKey,
snapOffsetCallback: snapOffsetCallback,
scrollDirection: scrollDirection,
items: <int>[0, 1, 2, 3, 4, 5, 7, 8, 9],
itemBuilder: buildItem,
itemExtent: itemExtent
itemExtent: itemExtent,
children: <int>[0, 1, 2, 3, 4, 5, 7, 8, 9].map(buildItem)
)
)
);
......
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