Commit c4faad11 authored by Adam Barth's avatar Adam Barth

Merge pull request #1074 from abarth/scrollable_list_features

Complete features of ScrollableList2
parents f72c8f6d b1f9138f
......@@ -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
);
}
}
......@@ -30,22 +30,25 @@ class ScrollbarPainter extends ScrollableListPainter {
Point thumbOrigin;
Size thumbSize;
if (isVertical) {
double thumbHeight = viewportBounds.height * viewportBounds.height / contentExtent;
thumbHeight = thumbHeight.clamp(_kMinScrollbarThumbLength, viewportBounds.height);
final double maxThumbTop = viewportBounds.height - thumbHeight;
double thumbTop = (scrollOffset / (contentExtent - viewportBounds.height)) * maxThumbTop;
thumbTop = viewportBounds.top + thumbTop.clamp(0.0, maxThumbTop);
thumbOrigin = new Point(viewportBounds.right - _kScrollbarThumbGirth, thumbTop);
thumbSize = new Size(_kScrollbarThumbGirth, thumbHeight);
} else {
double thumbWidth = viewportBounds.width * viewportBounds.width / contentExtent;
thumbWidth = thumbWidth.clamp(_kMinScrollbarThumbLength, viewportBounds.width);
final double maxThumbLeft = viewportBounds.width - thumbWidth;
double thumbLeft = (scrollOffset / (contentExtent - viewportBounds.width)) * maxThumbLeft;
thumbLeft = viewportBounds.left + thumbLeft.clamp(0.0, maxThumbLeft);
thumbOrigin = new Point(thumbLeft, viewportBounds.height - _kScrollbarThumbGirth);
thumbSize = new Size(thumbWidth, _kScrollbarThumbGirth);
switch (scrollDirection) {
case ScrollDirection.vertical:
double thumbHeight = viewportBounds.height * viewportBounds.height / contentExtent;
thumbHeight = thumbHeight.clamp(_kMinScrollbarThumbLength, viewportBounds.height);
final double maxThumbTop = viewportBounds.height - thumbHeight;
double thumbTop = (scrollOffset / (contentExtent - viewportBounds.height)) * maxThumbTop;
thumbTop = viewportBounds.top + thumbTop.clamp(0.0, maxThumbTop);
thumbOrigin = new Point(viewportBounds.right - _kScrollbarThumbGirth, thumbTop);
thumbSize = new Size(_kScrollbarThumbGirth, thumbHeight);
break;
case ScrollDirection.horizontal:
double thumbWidth = viewportBounds.width * viewportBounds.width / contentExtent;
thumbWidth = thumbWidth.clamp(_kMinScrollbarThumbLength, viewportBounds.width);
final double maxThumbLeft = viewportBounds.width - thumbWidth;
double thumbLeft = (scrollOffset / (contentExtent - viewportBounds.width)) * maxThumbLeft;
thumbLeft = viewportBounds.left + thumbLeft.clamp(0.0, maxThumbLeft);
thumbOrigin = new Point(thumbLeft, viewportBounds.height - _kScrollbarThumbGirth);
thumbSize = new Size(thumbWidth, _kScrollbarThumbGirth);
break;
}
paintThumb(context, thumbOrigin & thumbSize);
......@@ -65,7 +68,7 @@ class ScrollbarPainter extends ScrollableListPainter {
..variable = new AnimatedValue<double>(0.0, end: 1.0, curve: Curves.ease)
..addListener(() {
_opacity = _fade.value;
renderer?.markNeedsPaint();
renderObject?.markNeedsPaint();
});
return _fade.forward();
}
......
......@@ -8,6 +8,7 @@ import 'package:vector_math/vector_math_64.dart';
import 'box.dart';
import 'object.dart';
import 'viewport.dart';
/// Parent data for use with [RenderBlockBase].
class BlockParentData extends ContainerBoxParentDataMixin<RenderBox> { }
......@@ -32,8 +33,10 @@ typedef double _Constrainer(double value);
/// children. Because blocks expand in the main axis, blocks must be given
/// unlimited space in the main axis, typically by being contained in a
/// viewport with a scrolling direction that matches the block's main axis.
abstract class RenderBlockBase extends RenderBox with ContainerRenderObjectMixin<RenderBox, BlockParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, BlockParentData> {
abstract class RenderBlockBase extends RenderBox
with ContainerRenderObjectMixin<RenderBox, BlockParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, BlockParentData>
implements HasScrollDirection {
RenderBlockBase({
List<RenderBox> children,
......@@ -82,6 +85,9 @@ abstract class RenderBlockBase extends RenderBox with ContainerRenderObjectMixin
/// Whether the main axis is vertical.
bool get isVertical => _direction == BlockDirection.vertical;
// TODO(abarth): Remove BlockDirection in favor of ScrollDirection.
ScrollDirection get scrollDirection => isVertical ? ScrollDirection.vertical : ScrollDirection.horizontal;
BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
if (isVertical)
return new BoxConstraints.tightFor(width: constraints.constrainWidth(constraints.maxWidth),
......
......@@ -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 'box.dart';
import 'object.dart';
import 'viewport.dart';
......@@ -9,18 +11,23 @@ import 'viewport.dart';
/// Parent data for use with [RenderList].
class ListParentData extends ContainerBoxParentDataMixin<RenderBox> { }
class RenderList extends RenderVirtualViewport<ListParentData> {
class RenderList extends RenderVirtualViewport<ListParentData> implements HasScrollDirection {
RenderList({
List<RenderBox> children,
double itemExtent,
EdgeDims padding,
int virtualChildCount,
Offset paintOffset: Offset.zero,
ScrollDirection scrollDirection: ScrollDirection.vertical,
LayoutCallback callback
}) : _itemExtent = itemExtent, super(
virtualChildCount: virtualChildCount,
paintOffset: paintOffset,
callback: callback
) {
}) : _itemExtent = itemExtent,
_padding = padding,
_scrollDirection = scrollDirection,
super(
virtualChildCount: virtualChildCount,
paintOffset: paintOffset,
callback: callback
) {
assert(itemExtent != null);
addAll(children);
}
......@@ -35,50 +42,124 @@ class RenderList extends RenderVirtualViewport<ListParentData> {
markNeedsLayout();
}
EdgeDims get padding => _padding;
EdgeDims _padding;
void set padding (EdgeDims newValue) {
if (_padding == newValue)
return;
_padding = newValue;
markNeedsLayout();
}
ScrollDirection get scrollDirection => _scrollDirection;
ScrollDirection _scrollDirection;
void set scrollDirection (ScrollDirection newValue) {
if (_scrollDirection == newValue)
return;
_scrollDirection = newValue;
markNeedsLayout();
}
void setupParentData(RenderBox child) {
if (child.parentData is! ListParentData)
child.parentData = new ListParentData();
}
double get _preferredMainAxisExtent => itemExtent * virtualChildCount;
double get _scrollAxisPadding {
switch (scrollDirection) {
case ScrollDirection.vertical:
return padding.vertical;
case ScrollDirection.horizontal:
return padding.horizontal;
}
}
double getMinIntrinsicWidth(BoxConstraints constraints) {
double get _preferredExtent {
double extent = itemExtent * virtualChildCount;
if (padding != null)
extent += _scrollAxisPadding;
return extent;
}
double _getIntrinsicWidth(BoxConstraints constraints) {
assert(constraints.isNormalized);
return constraints.constrainWidth(0.0);
switch (scrollDirection) {
case ScrollDirection.vertical:
return constraints.constrainWidth(0.0);
case ScrollDirection.horizontal:
return constraints.constrainWidth(_preferredExtent);
}
}
double getMinIntrinsicWidth(BoxConstraints constraints) {
return _getIntrinsicWidth(constraints);
}
double getMaxIntrinsicWidth(BoxConstraints constraints) {
return _getIntrinsicWidth(constraints);
}
double _getIntrinsicHeight(BoxConstraints constraints) {
assert(constraints.isNormalized);
return constraints.constrainWidth(0.0);
switch (scrollDirection) {
case ScrollDirection.vertical:
return constraints.constrainHeight(_preferredExtent);
case ScrollDirection.horizontal:
return constraints.constrainHeight(0.0);
}
}
double getMinIntrinsicHeight(BoxConstraints constraints) {
assert(constraints.isNormalized);
return constraints.constrainHeight(_preferredMainAxisExtent);
return _getIntrinsicHeight(constraints);
}
double getMaxIntrinsicHeight(BoxConstraints constraints) {
assert(constraints.isNormalized);
return constraints.constrainHeight(_preferredMainAxisExtent);
return _getIntrinsicHeight(constraints);
}
void performLayout() {
double height = _preferredMainAxisExtent;
size = new Size(constraints.maxWidth, constraints.constrainHeight(height));
size = new Size(constraints.maxWidth,
constraints.constrainHeight(_preferredExtent));
if (callback != null)
invokeLayoutCallback(callback);
double itemWidth;
double itemHeight;
double x = 0.0;
double dx = 0.0;
double y = 0.0;
double dy = 0.0;
switch (scrollDirection) {
case ScrollDirection.vertical:
itemWidth = math.max(0, size.width - (padding == null ? 0.0 : padding.horizontal));
itemHeight = itemExtent;
y = padding != null ? padding.top : 0.0;
dy = itemExtent;
break;
case ScrollDirection.horizontal:
itemWidth = itemExtent;
itemHeight = math.max(0, size.height - (padding == null ? 0.0 : padding.vertical));
x = padding != null ? padding.left : 0.0;
dx = itemExtent;
break;
}
BoxConstraints innerConstraints =
new BoxConstraints.tightFor(width: size.width, height: itemExtent);
new BoxConstraints.tightFor(width: itemWidth, height: itemHeight);
int childIndex = 0;
RenderBox child = firstChild;
while (child != null) {
child.layout(innerConstraints);
final ListParentData childParentData = child.parentData;
childParentData.offset = new Offset(0.0, childIndex * itemExtent);
childIndex += 1;
childParentData.offset = new Offset(x, y);
x += dx;
y += dy;
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
......
......@@ -18,6 +18,10 @@ enum ScrollDirection {
vertical,
}
abstract class HasScrollDirection {
ScrollDirection get scrollDirection;
}
/// A render object that's bigger on the inside.
///
/// The child of a viewport can layout to a larger size than the viewport
......@@ -27,7 +31,8 @@ enum ScrollDirection {
///
/// Viewport is the core scrolling primitive in the system, but it can be used
/// in other situations.
class RenderViewport extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
class RenderViewport extends RenderBox with RenderObjectWithChildMixin<RenderBox>
implements HasScrollDirection {
RenderViewport({
RenderBox child,
......@@ -177,10 +182,12 @@ abstract class RenderVirtualViewport<T extends ContainerBoxParentDataMixin<Rende
RenderVirtualViewport({
int virtualChildCount,
Offset paintOffset,
LayoutCallback callback
LayoutCallback callback,
Painter overlayPainter
}) : _virtualChildCount = virtualChildCount,
_paintOffset = paintOffset,
_callback = callback;
_callback = callback,
_overlayPainter = overlayPainter;
int get virtualChildCount => _virtualChildCount ?? childCount;
int _virtualChildCount;
......@@ -217,8 +224,31 @@ abstract class RenderVirtualViewport<T extends ContainerBoxParentDataMixin<Rende
markNeedsLayout();
}
Painter get overlayPainter => _overlayPainter;
Painter _overlayPainter;
void set overlayPainter(Painter value) {
if (_overlayPainter == value)
return;
if (attached)
_overlayPainter?.detach();
_overlayPainter = value;
if (attached)
_overlayPainter?.attach(this);
markNeedsPaint();
}
void attach() {
super.attach();
_overlayPainter?.attach(this);
}
void detach() {
super.detach();
_overlayPainter?.detach();
}
void applyPaintTransform(RenderBox child, Matrix4 transform) {
super.applyPaintTransform(child, transform.translate(paintOffset));
super.applyPaintTransform(child, transform.translate(paintOffset.dx, paintOffset.dy));
}
bool hitTestChildren(HitTestResult result, { Point position }) {
......@@ -227,6 +257,7 @@ abstract class RenderVirtualViewport<T extends ContainerBoxParentDataMixin<Rende
void _paintContents(PaintingContext context, Offset offset) {
defaultPaint(context, offset + paintOffset);
_overlayPainter?.paint(context, offset);
}
void paint(PaintingContext context, Offset offset) {
......
......@@ -445,15 +445,19 @@ class Block extends StatelessComponent {
abstract class ScrollableListPainter extends Painter {
void attach(RenderObject renderObject) {
assert(renderObject is RenderBlockViewport);
assert(renderObject is RenderBox);
assert(renderObject is HasScrollDirection);
super.attach(renderObject);
}
RenderBlockViewport get renderer => renderObject;
RenderBox get renderObject => super.renderObject;
bool get isVertical => renderer.isVertical;
ScrollDirection get scrollDirection {
HasScrollDirection scrollable = renderObject as dynamic;
return scrollable?.scrollDirection;
}
Size get viewportSize => renderer.size;
Size get viewportSize => renderObject.size;
double get contentExtent => _contentExtent;
double _contentExtent = 0.0;
......@@ -463,7 +467,7 @@ abstract class ScrollableListPainter extends Painter {
if (_contentExtent == value)
return;
_contentExtent = value;
renderer?.markNeedsPaint();
renderObject?.markNeedsPaint();
}
double get scrollOffset => _scrollOffset;
......@@ -473,7 +477,7 @@ abstract class ScrollableListPainter extends Painter {
if (_scrollOffset == value)
return;
_scrollOffset = value;
renderer?.markNeedsPaint();
renderObject?.markNeedsPaint();
}
/// Called when a scroll starts. Subclasses may override this method to
......@@ -675,7 +679,7 @@ class ScrollableList<T> extends ScrollableWidgetList {
double snapAlignmentOffset: 0.0,
this.items,
this.itemBuilder,
itemsWrap: false,
bool itemsWrap: false,
double itemExtent,
EdgeDims padding,
ScrollableListPainter scrollableListPainter
......
......@@ -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,10 @@ 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;
RenderGrid createRenderObject() => new RenderGrid(delegate: delegate);
......@@ -136,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];
......
......@@ -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 'framework.dart';
import 'scrollable.dart';
import 'virtual_viewport.dart';
......@@ -13,23 +15,31 @@ class ScrollableList2 extends Scrollable {
ScrollableList2({
Key key,
double initialScrollOffset,
ScrollDirection scrollDirection: ScrollDirection.vertical,
ScrollListener onScroll,
SnapOffsetCallback snapOffsetCallback,
double snapAlignmentOffset: 0.0,
this.itemExtent,
this.itemsWrap: false,
this.padding,
this.scrollableListPainter,
this.children
}) : super(
key: key,
initialScrollOffset: initialScrollOffset,
// TODO(abarth): Support horizontal offsets.
scrollDirection: ScrollDirection.vertical,
scrollDirection: scrollDirection,
onScroll: onScroll,
snapOffsetCallback: snapOffsetCallback,
snapAlignmentOffset: snapAlignmentOffset
);
) {
assert(itemExtent != null);
}
final double itemExtent;
final List<Widget> children;
final bool itemsWrap;
final EdgeDims padding;
final ScrollableListPainter scrollableListPainter;
final Iterable<Widget> children;
ScrollableState createState() => new _ScrollableList2State();
}
......@@ -39,20 +49,40 @@ class _ScrollableList2State extends ScrollableState<ScrollableList2> {
ExtentScrollBehavior get scrollBehavior => super.scrollBehavior;
void _handleExtentsChanged(double contentExtent, double containerExtent) {
config.scrollableListPainter?.contentExtent = contentExtent;
setState(() {
scrollTo(scrollBehavior.updateExtents(
contentExtent: contentExtent,
contentExtent: config.itemsWrap ? double.INFINITY : 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 ListViewport(
onExtentsChanged: _handleExtentsChanged,
startOffset: scrollOffset,
scrollDirection: config.scrollDirection,
itemExtent: config.itemExtent,
onExtentsChanged: _handleExtentsChanged,
itemsWrap: config.itemsWrap,
padding: config.padding,
overlayPainter: config.scrollableListPainter,
children: config.children
);
}
......@@ -61,16 +91,27 @@ class _ScrollableList2State extends ScrollableState<ScrollableList2> {
class ListViewport extends VirtualViewport {
ListViewport({
Key key,
this.startOffset,
this.itemExtent,
this.onExtentsChanged,
this.startOffset: 0.0,
this.scrollDirection: ScrollDirection.vertical,
this.itemExtent,
this.itemsWrap: false,
this.padding,
this.overlayPainter,
this.children
});
}) {
assert(scrollDirection != null);
assert(itemExtent != null);
}
final ExtentsChangedCallback onExtentsChanged;
final double startOffset;
final ScrollDirection scrollDirection;
final double itemExtent;
final ExtentsChangedCallback onExtentsChanged;
final List<Widget> children;
final bool itemsWrap;
final EdgeDims padding;
final Painter overlayPainter;
final Iterable<Widget> children;
RenderList createRenderObject() => new RenderList(itemExtent: itemExtent);
......@@ -95,21 +136,39 @@ class _ListViewportElement extends VirtualViewportElement<ListViewport> {
double _repaintOffsetLimit;
void updateRenderObject() {
renderObject.scrollDirection = widget.scrollDirection;
renderObject.itemExtent = widget.itemExtent;
renderObject.padding = widget.padding;
renderObject.overlayPainter = widget.overlayPainter;
super.updateRenderObject();
}
double _contentExtent;
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) {
double contentExtent = widget.itemExtent * widget.children.length;
double containerExtent = renderObject.size.height;
int length = renderObject.virtualChildCount;
double contentExtent = widget.itemExtent * length;
double containerExtent = _getContainerExtentFromRenderObject();
_materializedChildBase = (widget.startOffset ~/ widget.itemExtent).clamp(0, widget.children.length);
int materializedChildLimit = ((widget.startOffset + containerExtent) / widget.itemExtent).ceil().clamp(0, widget.children.length);
_materializedChildCount = materializedChildLimit - _materializedChildBase;
_materializedChildBase = math.max(0, widget.startOffset ~/ widget.itemExtent);
int materializedChildLimit = math.max(0, ((widget.startOffset + containerExtent) / widget.itemExtent).ceil());
if (!widget.itemsWrap) {
_materializedChildBase = math.min(length, _materializedChildBase);
materializedChildLimit = math.min(length, materializedChildLimit);
}
_materializedChildCount = materializedChildLimit - _materializedChildBase;
_repaintOffsetBase = _materializedChildBase * widget.itemExtent;
_repaintOffsetLimit = materializedChildLimit * widget.itemExtent;
......
......@@ -11,7 +11,8 @@ typedef void ExtentsChangedCallback(double contentExtent, double containerExtent
abstract class VirtualViewport extends RenderObjectWidget {
double get startOffset;
List<Widget> get children;
ScrollDirection get scrollDirection;
Iterable<Widget> get children;
}
abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderObjectElement<T> {
......@@ -35,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();
}
......@@ -45,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)
......@@ -52,8 +59,23 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
}
void _updatePaintOffset() {
renderObject.paintOffset =
renderObject.paintOffset = new Offset(0.0, -(widget.startOffset - repaintOffsetBase));
switch (widget.scrollDirection) {
case ScrollDirection.vertical:
renderObject.paintOffset = new Offset(0.0, -(widget.startOffset - repaintOffsetBase));
break;
case ScrollDirection.horizontal:
renderObject.paintOffset = new Offset(-(widget.startOffset - repaintOffsetBase), 0.0);
break;
}
}
double get _containerExtent {
switch (widget.scrollDirection) {
case ScrollDirection.vertical:
return renderObject.size.height;
case ScrollDirection.horizontal:
return renderObject.size.width;
}
}
void updateRenderObject() {
......@@ -67,7 +89,7 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
if (!renderObject.needsLayout) {
if (repaintOffsetBase != null && widget.startOffset < repaintOffsetBase)
renderObject.markNeedsLayout();
else if (repaintOffsetLimit != null && widget.startOffset + renderObject.size.height > repaintOffsetLimit)
else if (repaintOffsetLimit != null && widget.startOffset + _containerExtent > repaintOffsetLimit)
renderObject.markNeedsLayout();
}
}
......@@ -80,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 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