Merge pull request #1074 from abarth/scrollable_list_features
Complete features of ScrollableList2
This commit is contained in:
commit
c4faad115d
@ -14,11 +14,10 @@ class FitnessItemList extends StatelessComponent {
|
|||||||
final FitnessItemHandler onDismissed;
|
final FitnessItemHandler onDismissed;
|
||||||
|
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return new ScrollableList<FitnessItem>(
|
return new ScrollableList2(
|
||||||
padding: const EdgeDims.all(4.0),
|
padding: const EdgeDims.all(4.0),
|
||||||
items: items,
|
|
||||||
itemExtent: kFitnessItemHeight,
|
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;
|
final StockRowActionCallback onAction;
|
||||||
|
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return new ScrollableList<Stock>(
|
return new ScrollableList2(
|
||||||
items: stocks,
|
|
||||||
itemExtent: StockRow.kHeight,
|
itemExtent: StockRow.kHeight,
|
||||||
itemBuilder: (BuildContext context, Stock stock, int index) {
|
children: stocks.map((Stock stock) {
|
||||||
return new StockRow(
|
return new StockRow(
|
||||||
keySalt: keySalt,
|
keySalt: keySalt,
|
||||||
stock: stock,
|
stock: stock,
|
||||||
@ -25,7 +24,7 @@ class StockList extends StatelessComponent {
|
|||||||
onDoubleTap: onShow,
|
onDoubleTap: onShow,
|
||||||
onLongPressed: onAction
|
onLongPressed: onAction
|
||||||
);
|
);
|
||||||
}
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -393,12 +393,11 @@ class CardCollectionState extends State<CardCollection> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget cardCollection;
|
Widget cardCollection;
|
||||||
if (_fixedSizeCards) {
|
if (_fixedSizeCards) {
|
||||||
cardCollection = new ScrollableList<CardModel> (
|
cardCollection = new ScrollableList2 (
|
||||||
snapOffsetCallback: _snapToCenter ? _toSnapOffset : null,
|
snapOffsetCallback: _snapToCenter ? _toSnapOffset : null,
|
||||||
snapAlignmentOffset: _cardCollectionSize.height / 2.0,
|
snapAlignmentOffset: _cardCollectionSize.height / 2.0,
|
||||||
items: _cardModels,
|
itemExtent: _cardModels[0].height,
|
||||||
itemBuilder: (BuildContext context, CardModel card, int index) => _buildCard(context, card.value),
|
children: _cardModels.map((CardModel card) => _buildCard(context, card.value))
|
||||||
itemExtent: _cardModels[0].height
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
cardCollection = new ScrollableMixedWidgetList(
|
cardCollection = new ScrollableMixedWidgetList(
|
||||||
|
@ -74,12 +74,12 @@ class MediaQueryExample extends StatelessComponent {
|
|||||||
if (MediaQuery.of(context).size.width < _gridViewBreakpoint) {
|
if (MediaQuery.of(context).size.width < _gridViewBreakpoint) {
|
||||||
return new ScrollableList2(
|
return new ScrollableList2(
|
||||||
itemExtent: 50.0,
|
itemExtent: 50.0,
|
||||||
children: items.map((AdaptiveItem item) => item.toListItem()).toList()
|
children: items.map((AdaptiveItem item) => item.toListItem())
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return new ScrollableGrid(
|
return new ScrollableGrid(
|
||||||
delegate: new MaxTileWidthGridDelegate(maxTileWidth: _maxTileWidth),
|
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:intl/intl.dart';
|
||||||
import 'package:flutter/material.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 {
|
class ScrollbarApp extends StatefulComponent {
|
||||||
ScrollbarAppState createState() => new ScrollbarAppState();
|
ScrollbarAppState createState() => new ScrollbarAppState();
|
||||||
}
|
}
|
||||||
@ -15,17 +30,10 @@ class ScrollbarAppState extends State<ScrollbarApp> {
|
|||||||
final ScrollbarPainter _scrollbarPainter = new ScrollbarPainter();
|
final ScrollbarPainter _scrollbarPainter = new ScrollbarPainter();
|
||||||
|
|
||||||
Widget _buildMenu(BuildContext context) {
|
Widget _buildMenu(BuildContext context) {
|
||||||
NumberFormat dd = new NumberFormat("00", "en_US");
|
return new ScrollableList2(
|
||||||
return new ScrollableList<int>(
|
|
||||||
items: new List<int>.generate(_itemCount, (int i) => i),
|
|
||||||
itemExtent: _itemExtent,
|
itemExtent: _itemExtent,
|
||||||
itemBuilder: (_, __, int index) {
|
scrollableListPainter: _scrollbarPainter,
|
||||||
return new Text('Item ${dd.format(index)}',
|
children: new List<Widget>.generate(_itemCount, (int i) => new _Item(i))
|
||||||
key: new ValueKey<int>(index),
|
|
||||||
style: Theme.of(context).text.title
|
|
||||||
);
|
|
||||||
},
|
|
||||||
scrollableListPainter: _scrollbarPainter
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,44 +21,35 @@ Map<MaterialListType, double> _kItemExtent = const <MaterialListType, double>{
|
|||||||
MaterialListType.threeLine: kThreeLineListItemHeight,
|
MaterialListType.threeLine: kThreeLineListItemHeight,
|
||||||
};
|
};
|
||||||
|
|
||||||
class MaterialList<T> extends StatefulComponent {
|
class MaterialList extends StatefulComponent {
|
||||||
MaterialList({
|
MaterialList({
|
||||||
Key key,
|
Key key,
|
||||||
this.initialScrollOffset,
|
this.initialScrollOffset,
|
||||||
this.onScroll,
|
this.onScroll,
|
||||||
this.items,
|
this.type: MaterialListType.twoLine,
|
||||||
this.itemBuilder,
|
this.children
|
||||||
this.type: MaterialListType.twoLine
|
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final double initialScrollOffset;
|
final double initialScrollOffset;
|
||||||
final ScrollListener onScroll;
|
final ScrollListener onScroll;
|
||||||
final List<T> items;
|
|
||||||
final ItemBuilder<T> itemBuilder;
|
|
||||||
final MaterialListType type;
|
final MaterialListType type;
|
||||||
|
final Iterable<Widget> children;
|
||||||
|
|
||||||
_MaterialListState<T> createState() => new _MaterialListState<T>();
|
_MaterialListState createState() => new _MaterialListState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MaterialListState<T> extends State<MaterialList<T>> {
|
class _MaterialListState extends State<MaterialList> {
|
||||||
|
ScrollbarPainter _scrollbarPainter = new ScrollbarPainter();
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_scrollbarPainter = new ScrollbarPainter();
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollbarPainter _scrollbarPainter;
|
|
||||||
|
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return new ScrollableList<T>(
|
return new ScrollableList2(
|
||||||
initialScrollOffset: config.initialScrollOffset,
|
initialScrollOffset: config.initialScrollOffset,
|
||||||
scrollDirection: ScrollDirection.vertical,
|
scrollDirection: ScrollDirection.vertical,
|
||||||
onScroll: config.onScroll,
|
onScroll: config.onScroll,
|
||||||
items: config.items,
|
|
||||||
itemBuilder: config.itemBuilder,
|
|
||||||
itemExtent: _kItemExtent[config.type],
|
itemExtent: _kItemExtent[config.type],
|
||||||
padding: const EdgeDims.symmetric(vertical: 8.0),
|
padding: const EdgeDims.symmetric(vertical: 8.0),
|
||||||
scrollableListPainter: _scrollbarPainter
|
scrollableListPainter: _scrollbarPainter,
|
||||||
|
children: config.children
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,22 +30,25 @@ class ScrollbarPainter extends ScrollableListPainter {
|
|||||||
Point thumbOrigin;
|
Point thumbOrigin;
|
||||||
Size thumbSize;
|
Size thumbSize;
|
||||||
|
|
||||||
if (isVertical) {
|
switch (scrollDirection) {
|
||||||
double thumbHeight = viewportBounds.height * viewportBounds.height / contentExtent;
|
case ScrollDirection.vertical:
|
||||||
thumbHeight = thumbHeight.clamp(_kMinScrollbarThumbLength, viewportBounds.height);
|
double thumbHeight = viewportBounds.height * viewportBounds.height / contentExtent;
|
||||||
final double maxThumbTop = viewportBounds.height - thumbHeight;
|
thumbHeight = thumbHeight.clamp(_kMinScrollbarThumbLength, viewportBounds.height);
|
||||||
double thumbTop = (scrollOffset / (contentExtent - viewportBounds.height)) * maxThumbTop;
|
final double maxThumbTop = viewportBounds.height - thumbHeight;
|
||||||
thumbTop = viewportBounds.top + thumbTop.clamp(0.0, maxThumbTop);
|
double thumbTop = (scrollOffset / (contentExtent - viewportBounds.height)) * maxThumbTop;
|
||||||
thumbOrigin = new Point(viewportBounds.right - _kScrollbarThumbGirth, thumbTop);
|
thumbTop = viewportBounds.top + thumbTop.clamp(0.0, maxThumbTop);
|
||||||
thumbSize = new Size(_kScrollbarThumbGirth, thumbHeight);
|
thumbOrigin = new Point(viewportBounds.right - _kScrollbarThumbGirth, thumbTop);
|
||||||
} else {
|
thumbSize = new Size(_kScrollbarThumbGirth, thumbHeight);
|
||||||
double thumbWidth = viewportBounds.width * viewportBounds.width / contentExtent;
|
break;
|
||||||
thumbWidth = thumbWidth.clamp(_kMinScrollbarThumbLength, viewportBounds.width);
|
case ScrollDirection.horizontal:
|
||||||
final double maxThumbLeft = viewportBounds.width - thumbWidth;
|
double thumbWidth = viewportBounds.width * viewportBounds.width / contentExtent;
|
||||||
double thumbLeft = (scrollOffset / (contentExtent - viewportBounds.width)) * maxThumbLeft;
|
thumbWidth = thumbWidth.clamp(_kMinScrollbarThumbLength, viewportBounds.width);
|
||||||
thumbLeft = viewportBounds.left + thumbLeft.clamp(0.0, maxThumbLeft);
|
final double maxThumbLeft = viewportBounds.width - thumbWidth;
|
||||||
thumbOrigin = new Point(thumbLeft, viewportBounds.height - _kScrollbarThumbGirth);
|
double thumbLeft = (scrollOffset / (contentExtent - viewportBounds.width)) * maxThumbLeft;
|
||||||
thumbSize = new Size(thumbWidth, _kScrollbarThumbGirth);
|
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);
|
paintThumb(context, thumbOrigin & thumbSize);
|
||||||
@ -65,7 +68,7 @@ class ScrollbarPainter extends ScrollableListPainter {
|
|||||||
..variable = new AnimatedValue<double>(0.0, end: 1.0, curve: Curves.ease)
|
..variable = new AnimatedValue<double>(0.0, end: 1.0, curve: Curves.ease)
|
||||||
..addListener(() {
|
..addListener(() {
|
||||||
_opacity = _fade.value;
|
_opacity = _fade.value;
|
||||||
renderer?.markNeedsPaint();
|
renderObject?.markNeedsPaint();
|
||||||
});
|
});
|
||||||
return _fade.forward();
|
return _fade.forward();
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import 'package:vector_math/vector_math_64.dart';
|
|||||||
|
|
||||||
import 'box.dart';
|
import 'box.dart';
|
||||||
import 'object.dart';
|
import 'object.dart';
|
||||||
|
import 'viewport.dart';
|
||||||
|
|
||||||
/// Parent data for use with [RenderBlockBase].
|
/// Parent data for use with [RenderBlockBase].
|
||||||
class BlockParentData extends ContainerBoxParentDataMixin<RenderBox> { }
|
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
|
/// children. Because blocks expand in the main axis, blocks must be given
|
||||||
/// unlimited space in the main axis, typically by being contained in a
|
/// unlimited space in the main axis, typically by being contained in a
|
||||||
/// viewport with a scrolling direction that matches the block's main axis.
|
/// viewport with a scrolling direction that matches the block's main axis.
|
||||||
abstract class RenderBlockBase extends RenderBox with ContainerRenderObjectMixin<RenderBox, BlockParentData>,
|
abstract class RenderBlockBase extends RenderBox
|
||||||
RenderBoxContainerDefaultsMixin<RenderBox, BlockParentData> {
|
with ContainerRenderObjectMixin<RenderBox, BlockParentData>,
|
||||||
|
RenderBoxContainerDefaultsMixin<RenderBox, BlockParentData>
|
||||||
|
implements HasScrollDirection {
|
||||||
|
|
||||||
RenderBlockBase({
|
RenderBlockBase({
|
||||||
List<RenderBox> children,
|
List<RenderBox> children,
|
||||||
@ -82,6 +85,9 @@ abstract class RenderBlockBase extends RenderBox with ContainerRenderObjectMixin
|
|||||||
/// Whether the main axis is vertical.
|
/// Whether the main axis is vertical.
|
||||||
bool get isVertical => _direction == BlockDirection.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) {
|
BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
|
||||||
if (isVertical)
|
if (isVertical)
|
||||||
return new BoxConstraints.tightFor(width: constraints.constrainWidth(constraints.maxWidth),
|
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
|
// 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 'box.dart';
|
import 'box.dart';
|
||||||
import 'object.dart';
|
import 'object.dart';
|
||||||
import 'viewport.dart';
|
import 'viewport.dart';
|
||||||
@ -9,18 +11,23 @@ import 'viewport.dart';
|
|||||||
/// Parent data for use with [RenderList].
|
/// Parent data for use with [RenderList].
|
||||||
class ListParentData extends ContainerBoxParentDataMixin<RenderBox> { }
|
class ListParentData extends ContainerBoxParentDataMixin<RenderBox> { }
|
||||||
|
|
||||||
class RenderList extends RenderVirtualViewport<ListParentData> {
|
class RenderList extends RenderVirtualViewport<ListParentData> implements HasScrollDirection {
|
||||||
RenderList({
|
RenderList({
|
||||||
List<RenderBox> children,
|
List<RenderBox> children,
|
||||||
double itemExtent,
|
double itemExtent,
|
||||||
|
EdgeDims padding,
|
||||||
int virtualChildCount,
|
int virtualChildCount,
|
||||||
Offset paintOffset: Offset.zero,
|
Offset paintOffset: Offset.zero,
|
||||||
|
ScrollDirection scrollDirection: ScrollDirection.vertical,
|
||||||
LayoutCallback callback
|
LayoutCallback callback
|
||||||
}) : _itemExtent = itemExtent, super(
|
}) : _itemExtent = itemExtent,
|
||||||
virtualChildCount: virtualChildCount,
|
_padding = padding,
|
||||||
paintOffset: paintOffset,
|
_scrollDirection = scrollDirection,
|
||||||
callback: callback
|
super(
|
||||||
) {
|
virtualChildCount: virtualChildCount,
|
||||||
|
paintOffset: paintOffset,
|
||||||
|
callback: callback
|
||||||
|
) {
|
||||||
assert(itemExtent != null);
|
assert(itemExtent != null);
|
||||||
addAll(children);
|
addAll(children);
|
||||||
}
|
}
|
||||||
@ -35,50 +42,124 @@ class RenderList extends RenderVirtualViewport<ListParentData> {
|
|||||||
markNeedsLayout();
|
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) {
|
void setupParentData(RenderBox child) {
|
||||||
if (child.parentData is! ListParentData)
|
if (child.parentData is! ListParentData)
|
||||||
child.parentData = new 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 get _preferredExtent {
|
||||||
|
double extent = itemExtent * virtualChildCount;
|
||||||
|
if (padding != null)
|
||||||
|
extent += _scrollAxisPadding;
|
||||||
|
return extent;
|
||||||
|
}
|
||||||
|
|
||||||
|
double _getIntrinsicWidth(BoxConstraints constraints) {
|
||||||
|
assert(constraints.isNormalized);
|
||||||
|
switch (scrollDirection) {
|
||||||
|
case ScrollDirection.vertical:
|
||||||
|
return constraints.constrainWidth(0.0);
|
||||||
|
case ScrollDirection.horizontal:
|
||||||
|
return constraints.constrainWidth(_preferredExtent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
double getMinIntrinsicWidth(BoxConstraints constraints) {
|
double getMinIntrinsicWidth(BoxConstraints constraints) {
|
||||||
assert(constraints.isNormalized);
|
return _getIntrinsicWidth(constraints);
|
||||||
return constraints.constrainWidth(0.0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double getMaxIntrinsicWidth(BoxConstraints constraints) {
|
double getMaxIntrinsicWidth(BoxConstraints constraints) {
|
||||||
|
return _getIntrinsicWidth(constraints);
|
||||||
|
}
|
||||||
|
|
||||||
|
double _getIntrinsicHeight(BoxConstraints constraints) {
|
||||||
assert(constraints.isNormalized);
|
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) {
|
double getMinIntrinsicHeight(BoxConstraints constraints) {
|
||||||
assert(constraints.isNormalized);
|
return _getIntrinsicHeight(constraints);
|
||||||
return constraints.constrainHeight(_preferredMainAxisExtent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double getMaxIntrinsicHeight(BoxConstraints constraints) {
|
double getMaxIntrinsicHeight(BoxConstraints constraints) {
|
||||||
assert(constraints.isNormalized);
|
return _getIntrinsicHeight(constraints);
|
||||||
return constraints.constrainHeight(_preferredMainAxisExtent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void performLayout() {
|
void performLayout() {
|
||||||
double height = _preferredMainAxisExtent;
|
size = new Size(constraints.maxWidth,
|
||||||
size = new Size(constraints.maxWidth, constraints.constrainHeight(height));
|
constraints.constrainHeight(_preferredExtent));
|
||||||
|
|
||||||
if (callback != null)
|
if (callback != null)
|
||||||
invokeLayoutCallback(callback);
|
invokeLayoutCallback(callback);
|
||||||
|
|
||||||
BoxConstraints innerConstraints =
|
double itemWidth;
|
||||||
new BoxConstraints.tightFor(width: size.width, height: itemExtent);
|
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: itemWidth, height: itemHeight);
|
||||||
|
|
||||||
int childIndex = 0;
|
|
||||||
RenderBox child = firstChild;
|
RenderBox child = firstChild;
|
||||||
while (child != null) {
|
while (child != null) {
|
||||||
child.layout(innerConstraints);
|
child.layout(innerConstraints);
|
||||||
|
|
||||||
final ListParentData childParentData = child.parentData;
|
final ListParentData childParentData = child.parentData;
|
||||||
childParentData.offset = new Offset(0.0, childIndex * itemExtent);
|
childParentData.offset = new Offset(x, y);
|
||||||
childIndex += 1;
|
x += dx;
|
||||||
|
y += dy;
|
||||||
|
|
||||||
assert(child.parentData == childParentData);
|
assert(child.parentData == childParentData);
|
||||||
child = childParentData.nextSibling;
|
child = childParentData.nextSibling;
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,10 @@ enum ScrollDirection {
|
|||||||
vertical,
|
vertical,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class HasScrollDirection {
|
||||||
|
ScrollDirection get scrollDirection;
|
||||||
|
}
|
||||||
|
|
||||||
/// A render object that's bigger on the inside.
|
/// A render object that's bigger on the inside.
|
||||||
///
|
///
|
||||||
/// The child of a viewport can layout to a larger size than the viewport
|
/// 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
|
/// Viewport is the core scrolling primitive in the system, but it can be used
|
||||||
/// in other situations.
|
/// in other situations.
|
||||||
class RenderViewport extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
|
class RenderViewport extends RenderBox with RenderObjectWithChildMixin<RenderBox>
|
||||||
|
implements HasScrollDirection {
|
||||||
|
|
||||||
RenderViewport({
|
RenderViewport({
|
||||||
RenderBox child,
|
RenderBox child,
|
||||||
@ -177,10 +182,12 @@ abstract class RenderVirtualViewport<T extends ContainerBoxParentDataMixin<Rende
|
|||||||
RenderVirtualViewport({
|
RenderVirtualViewport({
|
||||||
int virtualChildCount,
|
int virtualChildCount,
|
||||||
Offset paintOffset,
|
Offset paintOffset,
|
||||||
LayoutCallback callback
|
LayoutCallback callback,
|
||||||
|
Painter overlayPainter
|
||||||
}) : _virtualChildCount = virtualChildCount,
|
}) : _virtualChildCount = virtualChildCount,
|
||||||
_paintOffset = paintOffset,
|
_paintOffset = paintOffset,
|
||||||
_callback = callback;
|
_callback = callback,
|
||||||
|
_overlayPainter = overlayPainter;
|
||||||
|
|
||||||
int get virtualChildCount => _virtualChildCount ?? childCount;
|
int get virtualChildCount => _virtualChildCount ?? childCount;
|
||||||
int _virtualChildCount;
|
int _virtualChildCount;
|
||||||
@ -217,8 +224,31 @@ abstract class RenderVirtualViewport<T extends ContainerBoxParentDataMixin<Rende
|
|||||||
markNeedsLayout();
|
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) {
|
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 }) {
|
bool hitTestChildren(HitTestResult result, { Point position }) {
|
||||||
@ -227,6 +257,7 @@ abstract class RenderVirtualViewport<T extends ContainerBoxParentDataMixin<Rende
|
|||||||
|
|
||||||
void _paintContents(PaintingContext context, Offset offset) {
|
void _paintContents(PaintingContext context, Offset offset) {
|
||||||
defaultPaint(context, offset + paintOffset);
|
defaultPaint(context, offset + paintOffset);
|
||||||
|
_overlayPainter?.paint(context, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void paint(PaintingContext context, Offset offset) {
|
void paint(PaintingContext context, Offset offset) {
|
||||||
|
@ -445,15 +445,19 @@ class Block extends StatelessComponent {
|
|||||||
|
|
||||||
abstract class ScrollableListPainter extends Painter {
|
abstract class ScrollableListPainter extends Painter {
|
||||||
void attach(RenderObject renderObject) {
|
void attach(RenderObject renderObject) {
|
||||||
assert(renderObject is RenderBlockViewport);
|
assert(renderObject is RenderBox);
|
||||||
|
assert(renderObject is HasScrollDirection);
|
||||||
super.attach(renderObject);
|
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 get contentExtent => _contentExtent;
|
||||||
double _contentExtent = 0.0;
|
double _contentExtent = 0.0;
|
||||||
@ -463,7 +467,7 @@ abstract class ScrollableListPainter extends Painter {
|
|||||||
if (_contentExtent == value)
|
if (_contentExtent == value)
|
||||||
return;
|
return;
|
||||||
_contentExtent = value;
|
_contentExtent = value;
|
||||||
renderer?.markNeedsPaint();
|
renderObject?.markNeedsPaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
double get scrollOffset => _scrollOffset;
|
double get scrollOffset => _scrollOffset;
|
||||||
@ -473,7 +477,7 @@ abstract class ScrollableListPainter extends Painter {
|
|||||||
if (_scrollOffset == value)
|
if (_scrollOffset == value)
|
||||||
return;
|
return;
|
||||||
_scrollOffset = value;
|
_scrollOffset = value;
|
||||||
renderer?.markNeedsPaint();
|
renderObject?.markNeedsPaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when a scroll starts. Subclasses may override this method to
|
/// Called when a scroll starts. Subclasses may override this method to
|
||||||
@ -675,7 +679,7 @@ class ScrollableList<T> extends ScrollableWidgetList {
|
|||||||
double snapAlignmentOffset: 0.0,
|
double snapAlignmentOffset: 0.0,
|
||||||
this.items,
|
this.items,
|
||||||
this.itemBuilder,
|
this.itemBuilder,
|
||||||
itemsWrap: false,
|
bool itemsWrap: false,
|
||||||
double itemExtent,
|
double itemExtent,
|
||||||
EdgeDims padding,
|
EdgeDims padding,
|
||||||
ScrollableListPainter scrollableListPainter
|
ScrollableListPainter scrollableListPainter
|
||||||
|
@ -36,7 +36,7 @@ class ScrollableGrid extends Scrollable {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final GridDelegate delegate;
|
final GridDelegate delegate;
|
||||||
final List<Widget> children;
|
final Iterable<Widget> children;
|
||||||
|
|
||||||
ScrollableState createState() => new _ScrollableGridState();
|
ScrollableState createState() => new _ScrollableGridState();
|
||||||
}
|
}
|
||||||
@ -77,7 +77,10 @@ class GridViewport extends VirtualViewport {
|
|||||||
final double startOffset;
|
final double startOffset;
|
||||||
final GridDelegate delegate;
|
final GridDelegate delegate;
|
||||||
final ExtentsChangedCallback onExtentsChanged;
|
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);
|
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 materializedRowBase = math.max(0, _lowerBound(_specification.rowOffsets, widget.startOffset) - 1);
|
||||||
int materializedRowLimit = math.min(_specification.rowCount, _lowerBound(_specification.rowOffsets, widget.startOffset + containerExtent));
|
int materializedRowLimit = math.min(_specification.rowCount, _lowerBound(_specification.rowOffsets, widget.startOffset + containerExtent));
|
||||||
|
|
||||||
_materializedChildBase = (materializedRowBase * _specification.columnCount).clamp(0, widget.children.length);
|
_materializedChildBase = (materializedRowBase * _specification.columnCount).clamp(0, renderObject.virtualChildCount);
|
||||||
_materializedChildCount = (materializedRowLimit * _specification.columnCount).clamp(0, widget.children.length) - _materializedChildBase;
|
_materializedChildCount = (materializedRowLimit * _specification.columnCount).clamp(0, renderObject.virtualChildCount) - _materializedChildBase;
|
||||||
_repaintOffsetBase = _specification.rowOffsets[materializedRowBase];
|
_repaintOffsetBase = _specification.rowOffsets[materializedRowBase];
|
||||||
_repaintOffsetLimit = _specification.rowOffsets[materializedRowLimit];
|
_repaintOffsetLimit = _specification.rowOffsets[materializedRowLimit];
|
||||||
|
|
||||||
|
@ -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 'framework.dart';
|
import 'framework.dart';
|
||||||
import 'scrollable.dart';
|
import 'scrollable.dart';
|
||||||
import 'virtual_viewport.dart';
|
import 'virtual_viewport.dart';
|
||||||
@ -13,23 +15,31 @@ class ScrollableList2 extends Scrollable {
|
|||||||
ScrollableList2({
|
ScrollableList2({
|
||||||
Key key,
|
Key key,
|
||||||
double initialScrollOffset,
|
double initialScrollOffset,
|
||||||
|
ScrollDirection scrollDirection: ScrollDirection.vertical,
|
||||||
ScrollListener onScroll,
|
ScrollListener onScroll,
|
||||||
SnapOffsetCallback snapOffsetCallback,
|
SnapOffsetCallback snapOffsetCallback,
|
||||||
double snapAlignmentOffset: 0.0,
|
double snapAlignmentOffset: 0.0,
|
||||||
this.itemExtent,
|
this.itemExtent,
|
||||||
|
this.itemsWrap: false,
|
||||||
|
this.padding,
|
||||||
|
this.scrollableListPainter,
|
||||||
this.children
|
this.children
|
||||||
}) : super(
|
}) : super(
|
||||||
key: key,
|
key: key,
|
||||||
initialScrollOffset: initialScrollOffset,
|
initialScrollOffset: initialScrollOffset,
|
||||||
// TODO(abarth): Support horizontal offsets.
|
scrollDirection: scrollDirection,
|
||||||
scrollDirection: ScrollDirection.vertical,
|
|
||||||
onScroll: onScroll,
|
onScroll: onScroll,
|
||||||
snapOffsetCallback: snapOffsetCallback,
|
snapOffsetCallback: snapOffsetCallback,
|
||||||
snapAlignmentOffset: snapAlignmentOffset
|
snapAlignmentOffset: snapAlignmentOffset
|
||||||
);
|
) {
|
||||||
|
assert(itemExtent != null);
|
||||||
|
}
|
||||||
|
|
||||||
final double itemExtent;
|
final double itemExtent;
|
||||||
final List<Widget> children;
|
final bool itemsWrap;
|
||||||
|
final EdgeDims padding;
|
||||||
|
final ScrollableListPainter scrollableListPainter;
|
||||||
|
final Iterable<Widget> children;
|
||||||
|
|
||||||
ScrollableState createState() => new _ScrollableList2State();
|
ScrollableState createState() => new _ScrollableList2State();
|
||||||
}
|
}
|
||||||
@ -39,20 +49,40 @@ class _ScrollableList2State extends ScrollableState<ScrollableList2> {
|
|||||||
ExtentScrollBehavior get scrollBehavior => super.scrollBehavior;
|
ExtentScrollBehavior get scrollBehavior => super.scrollBehavior;
|
||||||
|
|
||||||
void _handleExtentsChanged(double contentExtent, double containerExtent) {
|
void _handleExtentsChanged(double contentExtent, double containerExtent) {
|
||||||
|
config.scrollableListPainter?.contentExtent = contentExtent;
|
||||||
setState(() {
|
setState(() {
|
||||||
scrollTo(scrollBehavior.updateExtents(
|
scrollTo(scrollBehavior.updateExtents(
|
||||||
contentExtent: contentExtent,
|
contentExtent: config.itemsWrap ? double.INFINITY : contentExtent,
|
||||||
containerExtent: containerExtent,
|
containerExtent: containerExtent,
|
||||||
scrollOffset: scrollOffset
|
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) {
|
Widget buildContent(BuildContext context) {
|
||||||
return new ListViewport(
|
return new ListViewport(
|
||||||
startOffset: scrollOffset,
|
|
||||||
itemExtent: config.itemExtent,
|
|
||||||
onExtentsChanged: _handleExtentsChanged,
|
onExtentsChanged: _handleExtentsChanged,
|
||||||
|
startOffset: scrollOffset,
|
||||||
|
scrollDirection: config.scrollDirection,
|
||||||
|
itemExtent: config.itemExtent,
|
||||||
|
itemsWrap: config.itemsWrap,
|
||||||
|
padding: config.padding,
|
||||||
|
overlayPainter: config.scrollableListPainter,
|
||||||
children: config.children
|
children: config.children
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -61,16 +91,27 @@ class _ScrollableList2State extends ScrollableState<ScrollableList2> {
|
|||||||
class ListViewport extends VirtualViewport {
|
class ListViewport extends VirtualViewport {
|
||||||
ListViewport({
|
ListViewport({
|
||||||
Key key,
|
Key key,
|
||||||
this.startOffset,
|
|
||||||
this.itemExtent,
|
|
||||||
this.onExtentsChanged,
|
this.onExtentsChanged,
|
||||||
|
this.startOffset: 0.0,
|
||||||
|
this.scrollDirection: ScrollDirection.vertical,
|
||||||
|
this.itemExtent,
|
||||||
|
this.itemsWrap: false,
|
||||||
|
this.padding,
|
||||||
|
this.overlayPainter,
|
||||||
this.children
|
this.children
|
||||||
});
|
}) {
|
||||||
|
assert(scrollDirection != null);
|
||||||
|
assert(itemExtent != null);
|
||||||
|
}
|
||||||
|
|
||||||
final double startOffset;
|
|
||||||
final double itemExtent;
|
|
||||||
final ExtentsChangedCallback onExtentsChanged;
|
final ExtentsChangedCallback onExtentsChanged;
|
||||||
final List<Widget> children;
|
final double startOffset;
|
||||||
|
final ScrollDirection scrollDirection;
|
||||||
|
final double itemExtent;
|
||||||
|
final bool itemsWrap;
|
||||||
|
final EdgeDims padding;
|
||||||
|
final Painter overlayPainter;
|
||||||
|
final Iterable<Widget> children;
|
||||||
|
|
||||||
RenderList createRenderObject() => new RenderList(itemExtent: itemExtent);
|
RenderList createRenderObject() => new RenderList(itemExtent: itemExtent);
|
||||||
|
|
||||||
@ -95,21 +136,39 @@ class _ListViewportElement extends VirtualViewportElement<ListViewport> {
|
|||||||
double _repaintOffsetLimit;
|
double _repaintOffsetLimit;
|
||||||
|
|
||||||
void updateRenderObject() {
|
void updateRenderObject() {
|
||||||
|
renderObject.scrollDirection = widget.scrollDirection;
|
||||||
renderObject.itemExtent = widget.itemExtent;
|
renderObject.itemExtent = widget.itemExtent;
|
||||||
|
renderObject.padding = widget.padding;
|
||||||
|
renderObject.overlayPainter = widget.overlayPainter;
|
||||||
super.updateRenderObject();
|
super.updateRenderObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
double _contentExtent;
|
double _contentExtent;
|
||||||
double _containerExtent;
|
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) {
|
void layout(BoxConstraints constraints) {
|
||||||
double contentExtent = widget.itemExtent * widget.children.length;
|
int length = renderObject.virtualChildCount;
|
||||||
double containerExtent = renderObject.size.height;
|
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) {
|
||||||
|
_materializedChildBase = math.min(length, _materializedChildBase);
|
||||||
|
materializedChildLimit = math.min(length, materializedChildLimit);
|
||||||
|
}
|
||||||
|
|
||||||
_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;
|
_materializedChildCount = materializedChildLimit - _materializedChildBase;
|
||||||
|
|
||||||
_repaintOffsetBase = _materializedChildBase * widget.itemExtent;
|
_repaintOffsetBase = _materializedChildBase * widget.itemExtent;
|
||||||
_repaintOffsetLimit = materializedChildLimit * widget.itemExtent;
|
_repaintOffsetLimit = materializedChildLimit * widget.itemExtent;
|
||||||
|
|
||||||
|
@ -11,7 +11,8 @@ typedef void ExtentsChangedCallback(double contentExtent, double containerExtent
|
|||||||
|
|
||||||
abstract class VirtualViewport extends RenderObjectWidget {
|
abstract class VirtualViewport extends RenderObjectWidget {
|
||||||
double get startOffset;
|
double get startOffset;
|
||||||
List<Widget> get children;
|
ScrollDirection get scrollDirection;
|
||||||
|
Iterable<Widget> get children;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderObjectElement<T> {
|
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) {
|
void mount(Element parent, dynamic newSlot) {
|
||||||
super.mount(parent, newSlot);
|
super.mount(parent, newSlot);
|
||||||
|
_iterator = null;
|
||||||
|
_widgets = <Widget>[];
|
||||||
renderObject.callback = layout;
|
renderObject.callback = layout;
|
||||||
updateRenderObject();
|
updateRenderObject();
|
||||||
}
|
}
|
||||||
@ -45,6 +48,10 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
|
|||||||
}
|
}
|
||||||
|
|
||||||
void update(T newWidget) {
|
void update(T newWidget) {
|
||||||
|
if (widget.children != newWidget.children) {
|
||||||
|
_iterator = null;
|
||||||
|
_widgets = <Widget>[];
|
||||||
|
}
|
||||||
super.update(newWidget);
|
super.update(newWidget);
|
||||||
updateRenderObject();
|
updateRenderObject();
|
||||||
if (!renderObject.needsLayout)
|
if (!renderObject.needsLayout)
|
||||||
@ -52,8 +59,23 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _updatePaintOffset() {
|
void _updatePaintOffset() {
|
||||||
renderObject.paintOffset =
|
switch (widget.scrollDirection) {
|
||||||
renderObject.paintOffset = new Offset(0.0, -(widget.startOffset - repaintOffsetBase));
|
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() {
|
void updateRenderObject() {
|
||||||
@ -67,7 +89,7 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
|
|||||||
if (!renderObject.needsLayout) {
|
if (!renderObject.needsLayout) {
|
||||||
if (repaintOffsetBase != null && widget.startOffset < repaintOffsetBase)
|
if (repaintOffsetBase != null && widget.startOffset < repaintOffsetBase)
|
||||||
renderObject.markNeedsLayout();
|
renderObject.markNeedsLayout();
|
||||||
else if (repaintOffsetLimit != null && widget.startOffset + renderObject.size.height > repaintOffsetLimit)
|
else if (repaintOffsetLimit != null && widget.startOffset + _containerExtent > repaintOffsetLimit)
|
||||||
renderObject.markNeedsLayout();
|
renderObject.markNeedsLayout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,15 +102,37 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
|
|||||||
BuildableElement.lockState(_materializeChildren);
|
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() {
|
void _materializeChildren() {
|
||||||
int base = materializedChildBase;
|
int base = materializedChildBase;
|
||||||
int count = materializedChildCount;
|
int count = materializedChildCount;
|
||||||
|
int length = renderObject.virtualChildCount;
|
||||||
assert(base != null);
|
assert(base != null);
|
||||||
assert(count != null);
|
assert(count != null);
|
||||||
|
_populateWidgets(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 = widget.children[childIndex];
|
Widget child = _widgets[childIndex % length];
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ void handleOnDismissed(int item) {
|
|||||||
dismissedItems.add(item);
|
dismissedItems.add(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildDismissableItem(BuildContext context, int item, int index) {
|
Widget buildDismissableItem(int item) {
|
||||||
return new Dismissable(
|
return new Dismissable(
|
||||||
key: new ValueKey<int>(item),
|
key: new ValueKey<int>(item),
|
||||||
direction: dismissDirection,
|
direction: dismissDirection,
|
||||||
@ -38,11 +38,12 @@ Widget buildDismissableItem(BuildContext context, int item, int index) {
|
|||||||
Widget widgetBuilder() {
|
Widget widgetBuilder() {
|
||||||
return new Container(
|
return new Container(
|
||||||
padding: const EdgeDims.all(10.0),
|
padding: const EdgeDims.all(10.0),
|
||||||
child: new ScrollableList<int>(
|
child: new ScrollableList2(
|
||||||
items: <int>[0, 1, 2, 3, 4].where((int i) => !dismissedItems.contains(i)).toList(),
|
|
||||||
itemBuilder: buildDismissableItem,
|
|
||||||
scrollDirection: scrollDirection,
|
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";
|
(key.currentState as StateMarkerState).marker = "marked";
|
||||||
|
|
||||||
tester.pumpWidget(new ScrollableList<int>(
|
tester.pumpWidget(new ScrollableList2(
|
||||||
items: <int>[0],
|
|
||||||
itemExtent: 100.0,
|
itemExtent: 100.0,
|
||||||
itemBuilder: (BuildContext context, int item, int index) {
|
children: <Widget>[
|
||||||
return new Container(
|
new Container(
|
||||||
key: new Key('container'),
|
key: new Key('container'),
|
||||||
height: 100.0,
|
height: 100.0,
|
||||||
child: new StateMarker(key: key)
|
child: new StateMarker(key: key)
|
||||||
);
|
)
|
||||||
}
|
]
|
||||||
));
|
));
|
||||||
|
|
||||||
expect((key.currentState as StateMarkerState).marker, equals("marked"));
|
expect((key.currentState as StateMarkerState).marker, equals("marked"));
|
||||||
|
@ -17,20 +17,18 @@ void main() {
|
|||||||
tester.pumpWidget(new Center(
|
tester.pumpWidget(new Center(
|
||||||
child: new Container(
|
child: new Container(
|
||||||
height: 50.0,
|
height: 50.0,
|
||||||
child: new ScrollableList<int>(
|
child: new ScrollableList2(
|
||||||
key: new GlobalKey(),
|
key: new GlobalKey(),
|
||||||
items: items,
|
itemExtent: 290.0,
|
||||||
itemBuilder: (BuildContext context, int item, int index) {
|
scrollDirection: ScrollDirection.horizontal,
|
||||||
|
children: items.map((int item) {
|
||||||
return new Container(
|
return new Container(
|
||||||
key: new ValueKey<int>(item),
|
|
||||||
child: new GestureDetector(
|
child: new GestureDetector(
|
||||||
onTap: () { tapped.add(item); },
|
onTap: () { tapped.add(item); },
|
||||||
child: new Text('$item')
|
child: new Text('$item')
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
},
|
})
|
||||||
itemExtent: 290.0,
|
|
||||||
scrollDirection: ScrollDirection.horizontal
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
@ -59,20 +57,18 @@ void main() {
|
|||||||
tester.pumpWidget(new Center(
|
tester.pumpWidget(new Center(
|
||||||
child: new Container(
|
child: new Container(
|
||||||
width: 50.0,
|
width: 50.0,
|
||||||
child: new ScrollableList<int>(
|
child: new ScrollableList2(
|
||||||
key: new GlobalKey(),
|
key: new GlobalKey(),
|
||||||
items: items,
|
itemExtent: 290.0,
|
||||||
itemBuilder: (BuildContext context, int item, int index) {
|
scrollDirection: ScrollDirection.vertical,
|
||||||
|
children: items.map((int item) {
|
||||||
return new Container(
|
return new Container(
|
||||||
key: new ValueKey<int>(item),
|
|
||||||
child: new GestureDetector(
|
child: new GestureDetector(
|
||||||
onTap: () { tapped.add(item); },
|
onTap: () { tapped.add(item); },
|
||||||
child: new Text('$item')
|
child: new Text('$item')
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
},
|
})
|
||||||
itemExtent: 290.0,
|
|
||||||
scrollDirection: ScrollDirection.vertical
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
|
@ -13,16 +13,14 @@ Widget buildFrame() {
|
|||||||
return new Center(
|
return new Center(
|
||||||
child: new Container(
|
child: new Container(
|
||||||
height: 50.0,
|
height: 50.0,
|
||||||
child: new ScrollableList<int>(
|
child: new ScrollableList2(
|
||||||
items: items,
|
itemExtent: 290.0,
|
||||||
itemBuilder: (BuildContext context, int item, int index) {
|
scrollDirection: ScrollDirection.horizontal,
|
||||||
|
children: items.map((int item) {
|
||||||
return new Container(
|
return new Container(
|
||||||
key: new ValueKey<int>(item),
|
|
||||||
child: new Text('$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];
|
const List<int> items = const <int>[0, 1, 2, 3, 4, 5];
|
||||||
|
|
||||||
Widget buildFrame() {
|
Widget buildFrame() {
|
||||||
return new ScrollableList<int>(
|
return new ScrollableList2(
|
||||||
items: items,
|
itemExtent: 290.0,
|
||||||
itemBuilder: (BuildContext context, int item, int index) {
|
scrollDirection: ScrollDirection.vertical,
|
||||||
|
children: items.map((int item) {
|
||||||
return new Container(
|
return new Container(
|
||||||
key: new ValueKey<int>(item),
|
|
||||||
child: new Text('$item')
|
child: new Text('$item')
|
||||||
);
|
);
|
||||||
},
|
})
|
||||||
itemExtent: 290.0,
|
|
||||||
scrollDirection: ScrollDirection.vertical
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,9 +12,8 @@ const double itemExtent = 200.0;
|
|||||||
ScrollDirection scrollDirection = ScrollDirection.vertical;
|
ScrollDirection scrollDirection = ScrollDirection.vertical;
|
||||||
GlobalKey scrollableListKey;
|
GlobalKey scrollableListKey;
|
||||||
|
|
||||||
Widget buildItem(BuildContext context, int item, int index) {
|
Widget buildItem(int item) {
|
||||||
return new Container(
|
return new Container(
|
||||||
key: new ValueKey<int>(item),
|
|
||||||
width: itemExtent,
|
width: itemExtent,
|
||||||
height: itemExtent,
|
height: itemExtent,
|
||||||
child: new Text(item.toString())
|
child: new Text(item.toString())
|
||||||
@ -30,13 +29,12 @@ Widget buildFrame() {
|
|||||||
return new Center(
|
return new Center(
|
||||||
child: new Container(
|
child: new Container(
|
||||||
height: itemExtent * 2.0,
|
height: itemExtent * 2.0,
|
||||||
child: new ScrollableList<int>(
|
child: new ScrollableList2(
|
||||||
key: scrollableListKey,
|
key: scrollableListKey,
|
||||||
snapOffsetCallback: snapOffsetCallback,
|
snapOffsetCallback: snapOffsetCallback,
|
||||||
scrollDirection: scrollDirection,
|
scrollDirection: scrollDirection,
|
||||||
items: <int>[0, 1, 2, 3, 4, 5, 7, 8, 9],
|
itemExtent: itemExtent,
|
||||||
itemBuilder: buildItem,
|
children: <int>[0, 1, 2, 3, 4, 5, 7, 8, 9].map(buildItem)
|
||||||
itemExtent: itemExtent
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user