Merge pull request #1098 from abarth/pageable_list2
Switch PageableList over to using RenderList
This commit is contained in:
commit
d46f0cebb5
@ -29,7 +29,7 @@ class _TabsDemoState extends State<TabsDemo> {
|
||||
Widget build(_) {
|
||||
return new TabBarView<String>(
|
||||
items: _iconNames,
|
||||
itemBuilder: (BuildContext context, String iconName, int index) {
|
||||
itemBuilder: (String iconName) {
|
||||
return new Container(
|
||||
key: new ValueKey<String>(iconName),
|
||||
padding: const EdgeDims.all(12.0),
|
||||
|
@ -273,7 +273,7 @@ class StockHomeState extends State<StockHome> {
|
||||
drawer: _buildDrawer(context),
|
||||
body: new TabBarView<StockHomeTab>(
|
||||
items: <StockHomeTab>[StockHomeTab.market, StockHomeTab.portfolio],
|
||||
itemBuilder: (BuildContext context, StockHomeTab tab, _) {
|
||||
itemBuilder: (StockHomeTab tab) {
|
||||
switch (tab) {
|
||||
case StockHomeTab.market:
|
||||
return _buildStockTab(context, tab, config.symbols);
|
||||
|
@ -41,7 +41,7 @@ class PageableListAppState extends State<PageableListApp> {
|
||||
ScrollDirection scrollDirection = ScrollDirection.horizontal;
|
||||
bool itemsWrap = false;
|
||||
|
||||
Widget buildCard(BuildContext context, CardModel cardModel, int index) {
|
||||
Widget buildCard(CardModel cardModel) {
|
||||
Widget card = new Card(
|
||||
color: cardModel.color,
|
||||
child: new Container(
|
||||
@ -114,10 +114,9 @@ class PageableListAppState extends State<PageableListApp> {
|
||||
}
|
||||
|
||||
Widget _buildBody(BuildContext context) {
|
||||
return new PageableList<CardModel>(
|
||||
items: cardModels,
|
||||
return new PageableList(
|
||||
children: cardModels.map(buildCard),
|
||||
itemsWrap: itemsWrap,
|
||||
itemBuilder: buildCard,
|
||||
scrollDirection: scrollDirection
|
||||
);
|
||||
}
|
||||
|
@ -772,26 +772,30 @@ class _TabBarState<T> extends ScrollableState<TabBar<T>> implements TabBarSelect
|
||||
}
|
||||
}
|
||||
|
||||
class TabBarView<T> extends PageableList<T> {
|
||||
typedef Widget TabItemBuilder<T>(T item);
|
||||
|
||||
class TabBarView<T> extends PageableList {
|
||||
TabBarView({
|
||||
Key key,
|
||||
List<T> items,
|
||||
ItemBuilder<T> itemBuilder
|
||||
}) : super(
|
||||
TabItemBuilder<T> itemBuilder
|
||||
}) : items = items, itemBuilder = itemBuilder, super(
|
||||
key: key,
|
||||
scrollDirection: ScrollDirection.horizontal,
|
||||
items: items,
|
||||
itemBuilder: itemBuilder,
|
||||
children: items.map((T item) => itemBuilder(item)),
|
||||
itemsWrap: false
|
||||
) {
|
||||
assert(items != null);
|
||||
assert(items.length > 1);
|
||||
}
|
||||
|
||||
final List<T> items;
|
||||
final TabItemBuilder<T> itemBuilder;
|
||||
|
||||
_TabBarViewState createState() => new _TabBarViewState<T>();
|
||||
}
|
||||
|
||||
class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> implements TabBarSelectionPerformanceListener {
|
||||
class _TabBarViewState<T> extends PageableListState<TabBarView<T>> implements TabBarSelectionPerformanceListener {
|
||||
|
||||
TabBarSelectionState _selection;
|
||||
List<int> _itemIndices = [0, 1];
|
||||
@ -916,14 +920,10 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> implements
|
||||
return settleScrollOffset();
|
||||
}
|
||||
|
||||
List<Widget> buildItems(BuildContext context, int start, int count) {
|
||||
Widget buildContent(BuildContext context) {
|
||||
TabBarSelectionState<T> newSelection = TabBarSelection.of(context);
|
||||
if (_selection != newSelection)
|
||||
_initSelection(newSelection);
|
||||
return _itemIndices
|
||||
.skip(start)
|
||||
.take(count)
|
||||
.map((int i) => config.itemBuilder(context, config.items[i], i))
|
||||
.toList();
|
||||
return super.buildContent(context);
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,6 @@ class RenderList extends RenderVirtualViewport<ListParentData> implements HasScr
|
||||
paintOffset: paintOffset,
|
||||
callback: callback
|
||||
) {
|
||||
assert(itemExtent != null);
|
||||
addAll(children);
|
||||
}
|
||||
|
||||
@ -75,6 +74,8 @@ class RenderList extends RenderVirtualViewport<ListParentData> implements HasScr
|
||||
}
|
||||
|
||||
double get _preferredExtent {
|
||||
if (itemExtent == null)
|
||||
return double.INFINITY;
|
||||
double extent = itemExtent * virtualChildCount;
|
||||
if (padding != null)
|
||||
extent += _scrollAxisPadding;
|
||||
@ -118,8 +119,16 @@ class RenderList extends RenderVirtualViewport<ListParentData> implements HasScr
|
||||
}
|
||||
|
||||
void performLayout() {
|
||||
size = new Size(constraints.maxWidth,
|
||||
constraints.constrainHeight(_preferredExtent));
|
||||
switch (scrollDirection) {
|
||||
case ScrollDirection.vertical:
|
||||
size = new Size(constraints.maxWidth,
|
||||
constraints.constrainHeight(_preferredExtent));
|
||||
break;
|
||||
case ScrollDirection.horizontal:
|
||||
size = new Size(constraints.constrainWidth(_preferredExtent),
|
||||
constraints.maxHeight);
|
||||
break;
|
||||
}
|
||||
|
||||
if (callback != null)
|
||||
invokeLayoutCallback(callback);
|
||||
@ -136,15 +145,15 @@ class RenderList extends RenderVirtualViewport<ListParentData> implements HasScr
|
||||
switch (scrollDirection) {
|
||||
case ScrollDirection.vertical:
|
||||
itemWidth = math.max(0, size.width - (padding == null ? 0.0 : padding.horizontal));
|
||||
itemHeight = itemExtent;
|
||||
itemHeight = itemExtent ?? size.height;
|
||||
y = padding != null ? padding.top : 0.0;
|
||||
dy = itemExtent;
|
||||
dy = itemHeight;
|
||||
break;
|
||||
case ScrollDirection.horizontal:
|
||||
itemWidth = itemExtent;
|
||||
itemWidth = itemExtent ?? size.width;
|
||||
itemHeight = math.max(0, size.height - (padding == null ? 0.0 : padding.vertical));
|
||||
x = padding != null ? padding.left : 0.0;
|
||||
dx = itemExtent;
|
||||
dx = itemWidth;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -220,80 +220,3 @@ class _HomogeneousViewportElement extends _ViewportBaseElement<HomogeneousViewpo
|
||||
return widget.itemCount != null ? widget.itemCount * widget.itemExtent : double.INFINITY;
|
||||
}
|
||||
}
|
||||
|
||||
class HomogeneousPageViewport extends _ViewportBase {
|
||||
HomogeneousPageViewport({
|
||||
Key key,
|
||||
ListBuilder builder,
|
||||
bool itemsWrap: false,
|
||||
int itemCount, // optional, but you cannot shrink-wrap this class or otherwise use its intrinsic dimensions if you don't specify it
|
||||
ScrollDirection direction: ScrollDirection.vertical,
|
||||
double startOffset: 0.0,
|
||||
Painter overlayPainter
|
||||
}) : super(
|
||||
key: key,
|
||||
builder: builder,
|
||||
itemsWrap: itemsWrap,
|
||||
itemCount: itemCount,
|
||||
direction: direction,
|
||||
startOffset: startOffset,
|
||||
overlayPainter: overlayPainter
|
||||
);
|
||||
|
||||
_HomogeneousPageViewportElement createElement() => new _HomogeneousPageViewportElement(this);
|
||||
}
|
||||
|
||||
class _HomogeneousPageViewportElement extends _ViewportBaseElement<HomogeneousPageViewport> {
|
||||
_HomogeneousPageViewportElement(HomogeneousPageViewport widget) : super(widget);
|
||||
|
||||
void layout(BoxConstraints constraints) {
|
||||
// We enter a build scope (meaning that markNeedsBuild() is forbidden)
|
||||
// because we are in the middle of layout and if we allowed people to set
|
||||
// state, they'd expect to have that state reflected immediately, which, if
|
||||
// we were to try to honour it, would potentially result in assertions
|
||||
// because you can't normally mutate the render object tree during layout.
|
||||
// (If there were a way to limit these writes to descendants of this, it'd
|
||||
// be ok because we are exempt from that assert since we are still actively
|
||||
// doing our own layout.)
|
||||
BuildableElement.lockState(() {
|
||||
double itemExtent = widget.direction == ScrollDirection.vertical ? constraints.maxHeight : constraints.maxWidth;
|
||||
double offset;
|
||||
if (widget.startOffset <= 0.0 && !widget.itemsWrap) {
|
||||
_layoutFirstIndex = 0;
|
||||
offset = -widget.startOffset * itemExtent;
|
||||
} else {
|
||||
_layoutFirstIndex = widget.startOffset.floor();
|
||||
offset = -((widget.startOffset * itemExtent) % itemExtent);
|
||||
}
|
||||
if (itemExtent < double.INFINITY && widget.itemCount != null) {
|
||||
final double contentExtent = itemExtent * widget.itemCount;
|
||||
_layoutItemCount = contentExtent == 0.0 ? 0 : ((contentExtent - offset) / contentExtent).ceil();
|
||||
if (!widget.itemsWrap)
|
||||
_layoutItemCount = math.min(_layoutItemCount, widget.itemCount - _layoutFirstIndex);
|
||||
} else {
|
||||
assert(() {
|
||||
'This HomogeneousPageViewport has no specified number of items (meaning it has infinite items), ' +
|
||||
'and has been placed in an unconstrained environment where all items can be rendered. ' +
|
||||
'It is most likely that you have placed your HomogeneousPageViewport (which is an internal ' +
|
||||
'component of several scrollable widgets) inside either another scrolling box, a flexible ' +
|
||||
'box (Row, Column), or a Stack, without giving it a specific size.';
|
||||
return widget.itemCount != null;
|
||||
});
|
||||
_layoutItemCount = widget.itemCount - _layoutFirstIndex;
|
||||
}
|
||||
_layoutItemCount = math.max(0, _layoutItemCount);
|
||||
_updateChildren();
|
||||
// Update the renderObject configuration
|
||||
renderObject.direction = widget.direction == ScrollDirection.vertical ? BlockDirection.vertical : BlockDirection.horizontal;
|
||||
renderObject.itemExtent = itemExtent;
|
||||
renderObject.minExtent = itemExtent;
|
||||
renderObject.startOffset = offset;
|
||||
renderObject.overlayPainter = widget.overlayPainter;
|
||||
}, building: true);
|
||||
}
|
||||
|
||||
double getTotalExtent(BoxConstraints constraints) {
|
||||
double itemExtent = widget.direction == ScrollDirection.vertical ? constraints.maxHeight : constraints.maxWidth;
|
||||
return widget.itemCount != null ? widget.itemCount * itemExtent : double.INFINITY;
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/animation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
@ -11,8 +10,8 @@ import 'package:flutter/rendering.dart';
|
||||
|
||||
import 'basic.dart';
|
||||
import 'framework.dart';
|
||||
import 'homogeneous_viewport.dart';
|
||||
import 'scrollable.dart';
|
||||
import 'virtual_viewport.dart';
|
||||
|
||||
/// Controls what alignment items use when settling.
|
||||
enum ItemsSnapAlignment {
|
||||
@ -22,7 +21,7 @@ enum ItemsSnapAlignment {
|
||||
|
||||
typedef void PageChangedCallback(int newPage);
|
||||
|
||||
class PageableList<T> extends Scrollable {
|
||||
class PageableList extends Scrollable {
|
||||
PageableList({
|
||||
Key key,
|
||||
initialScrollOffset,
|
||||
@ -32,14 +31,13 @@ class PageableList<T> extends Scrollable {
|
||||
ScrollListener onScrollEnd,
|
||||
SnapOffsetCallback snapOffsetCallback,
|
||||
double snapAlignmentOffset: 0.0,
|
||||
this.items,
|
||||
this.itemBuilder,
|
||||
this.itemsWrap: false,
|
||||
this.itemsSnapAlignment: ItemsSnapAlignment.adjacentItem,
|
||||
this.onPageChanged,
|
||||
this.scrollableListPainter,
|
||||
this.duration: const Duration(milliseconds: 200),
|
||||
this.curve: Curves.ease
|
||||
this.curve: Curves.ease,
|
||||
this.children
|
||||
}) : super(
|
||||
key: key,
|
||||
initialScrollOffset: initialScrollOffset,
|
||||
@ -51,21 +49,19 @@ class PageableList<T> extends Scrollable {
|
||||
snapAlignmentOffset: snapAlignmentOffset
|
||||
);
|
||||
|
||||
final List<T> items;
|
||||
final ItemBuilder<T> itemBuilder;
|
||||
final ItemsSnapAlignment itemsSnapAlignment;
|
||||
final bool itemsWrap;
|
||||
final ItemsSnapAlignment itemsSnapAlignment;
|
||||
final PageChangedCallback onPageChanged;
|
||||
final ScrollableListPainter scrollableListPainter;
|
||||
final Duration duration;
|
||||
final Curve curve;
|
||||
final Iterable<Widget> children;
|
||||
|
||||
PageableListState<T, PageableList<T>> createState() => new PageableListState<T, PageableList<T>>();
|
||||
PageableListState createState() => new PageableListState();
|
||||
}
|
||||
|
||||
class PageableListState<T, Config extends PageableList<T>> extends ScrollableState<Config> {
|
||||
|
||||
int get itemCount => config.items?.length ?? 0;
|
||||
class PageableListState<T extends PageableList> extends ScrollableState<T> {
|
||||
int get itemCount => config.children?.length ?? 0;
|
||||
int _previousItemCount;
|
||||
|
||||
double pixelToScrollOffset(double value) {
|
||||
@ -76,7 +72,12 @@ class PageableListState<T, Config extends PageableList<T>> extends ScrollableSta
|
||||
return pixelScrollExtent == 0.0 ? 0.0 : value / pixelScrollExtent;
|
||||
}
|
||||
|
||||
void didUpdateConfig(Config oldConfig) {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_updateScrollBehavior();
|
||||
}
|
||||
|
||||
void didUpdateConfig(PageableList oldConfig) {
|
||||
super.didUpdateConfig(oldConfig);
|
||||
|
||||
bool scrollBehaviorUpdateNeeded = config.scrollDirection != oldConfig.scrollDirection;
|
||||
@ -94,9 +95,7 @@ class PageableListState<T, Config extends PageableList<T>> extends ScrollableSta
|
||||
}
|
||||
|
||||
void _updateScrollBehavior() {
|
||||
// if you don't call this from build(), you must call it from setState().
|
||||
if (config.scrollableListPainter != null)
|
||||
config.scrollableListPainter.contentExtent = itemCount.toDouble();
|
||||
config.scrollableListPainter?.contentExtent = itemCount.toDouble();
|
||||
scrollTo(scrollBehavior.updateExtents(
|
||||
contentExtent: itemCount.toDouble(),
|
||||
containerExtent: 1.0,
|
||||
@ -111,8 +110,7 @@ class PageableListState<T, Config extends PageableList<T>> extends ScrollableSta
|
||||
|
||||
void dispatchOnScroll() {
|
||||
super.dispatchOnScroll();
|
||||
if (config.scrollableListPainter != null)
|
||||
config.scrollableListPainter.scrollOffset = scrollOffset;
|
||||
config.scrollableListPainter?.scrollOffset = scrollOffset;
|
||||
}
|
||||
|
||||
void dispatchOnScrollEnd() {
|
||||
@ -121,17 +119,12 @@ class PageableListState<T, Config extends PageableList<T>> extends ScrollableSta
|
||||
}
|
||||
|
||||
Widget buildContent(BuildContext context) {
|
||||
if (itemCount != _previousItemCount) {
|
||||
_previousItemCount = itemCount;
|
||||
_updateScrollBehavior();
|
||||
}
|
||||
return new HomogeneousPageViewport(
|
||||
builder: buildItems,
|
||||
return new PageViewport(
|
||||
itemsWrap: config.itemsWrap,
|
||||
itemCount: itemCount,
|
||||
direction: config.scrollDirection,
|
||||
scrollDirection: config.scrollDirection,
|
||||
startOffset: scrollOffset,
|
||||
overlayPainter: config.scrollableListPainter
|
||||
overlayPainter: config.scrollableListPainter,
|
||||
children: config.children
|
||||
);
|
||||
}
|
||||
|
||||
@ -180,18 +173,94 @@ class PageableListState<T, Config extends PageableList<T>> extends ScrollableSta
|
||||
.then(_notifyPageChanged);
|
||||
}
|
||||
|
||||
List<Widget> buildItems(BuildContext context, int start, int count) {
|
||||
final List<Widget> result = new List<Widget>();
|
||||
final int begin = config.itemsWrap ? start : math.max(0, start);
|
||||
final int end = config.itemsWrap ? begin + count : math.min(begin + count, itemCount);
|
||||
for (int i = begin; i < end; ++i)
|
||||
result.add(config.itemBuilder(context, config.items[i % itemCount], i));
|
||||
assert(result.every((Widget item) => item.key != null));
|
||||
return result;
|
||||
}
|
||||
|
||||
void _notifyPageChanged(_) {
|
||||
if (config.onPageChanged != null)
|
||||
config.onPageChanged(itemCount == 0 ? 0 : scrollOffset.floor() % itemCount);
|
||||
}
|
||||
}
|
||||
|
||||
class PageViewport extends VirtualViewport {
|
||||
PageViewport({
|
||||
Key key,
|
||||
this.startOffset: 0.0,
|
||||
this.scrollDirection: ScrollDirection.vertical,
|
||||
this.itemsWrap: false,
|
||||
this.overlayPainter,
|
||||
this.children
|
||||
}) {
|
||||
assert(scrollDirection != null);
|
||||
}
|
||||
|
||||
final double startOffset;
|
||||
final ScrollDirection scrollDirection;
|
||||
final bool itemsWrap;
|
||||
final Painter overlayPainter;
|
||||
final Iterable<Widget> children;
|
||||
|
||||
RenderList createRenderObject() => new RenderList();
|
||||
|
||||
_PageViewportElement createElement() => new _PageViewportElement(this);
|
||||
}
|
||||
|
||||
class _PageViewportElement extends VirtualViewportElement<PageViewport> {
|
||||
_PageViewportElement(PageViewport widget) : super(widget);
|
||||
|
||||
RenderList get renderObject => super.renderObject;
|
||||
|
||||
int get materializedChildBase => _materializedChildBase;
|
||||
int _materializedChildBase;
|
||||
|
||||
int get materializedChildCount => _materializedChildCount;
|
||||
int _materializedChildCount;
|
||||
|
||||
double get startOffsetBase => _repaintOffsetBase;
|
||||
double _repaintOffsetBase;
|
||||
|
||||
double get startOffsetLimit =>_repaintOffsetLimit;
|
||||
double _repaintOffsetLimit;
|
||||
|
||||
double get paintOffset {
|
||||
if (_containerExtent == null)
|
||||
return 0.0;
|
||||
return -(widget.startOffset - startOffsetBase) * _containerExtent;
|
||||
}
|
||||
|
||||
void updateRenderObject() {
|
||||
renderObject.scrollDirection = widget.scrollDirection;
|
||||
renderObject.overlayPainter = widget.overlayPainter;
|
||||
super.updateRenderObject();
|
||||
}
|
||||
|
||||
double _containerExtent;
|
||||
|
||||
double _getContainerExtentFromRenderObject() {
|
||||
switch (widget.scrollDirection) {
|
||||
case ScrollDirection.vertical:
|
||||
return renderObject.size.height;
|
||||
case ScrollDirection.horizontal:
|
||||
return renderObject.size.width;
|
||||
}
|
||||
}
|
||||
|
||||
void layout(BoxConstraints constraints) {
|
||||
int length = renderObject.virtualChildCount;
|
||||
_containerExtent = _getContainerExtentFromRenderObject();
|
||||
|
||||
_materializedChildBase = widget.startOffset.floor();
|
||||
int materializedChildLimit = (widget.startOffset + 1.0).ceil();
|
||||
|
||||
if (!widget.itemsWrap) {
|
||||
_materializedChildBase = _materializedChildBase.clamp(0, length);
|
||||
materializedChildLimit = materializedChildLimit.clamp(0, length);
|
||||
} else if (length == 0) {
|
||||
materializedChildLimit = _materializedChildBase;
|
||||
}
|
||||
|
||||
_materializedChildCount = materializedChildLimit - _materializedChildBase;
|
||||
|
||||
_repaintOffsetBase = _materializedChildBase.toDouble();
|
||||
_repaintOffsetLimit = (materializedChildLimit - 1).toDouble();
|
||||
|
||||
super.layout(constraints);
|
||||
}
|
||||
}
|
||||
|
@ -664,8 +664,6 @@ abstract class ScrollableWidgetListState<T extends ScrollableWidgetList> extends
|
||||
|
||||
}
|
||||
|
||||
typedef Widget ItemBuilder<T>(BuildContext context, T item, int index);
|
||||
|
||||
/// A general scrollable list for a large number of children that might not all
|
||||
/// have the same height. Prefer [ScrollableWidgetList] when all the children
|
||||
/// have the same height because it can use that property to be more efficient.
|
||||
|
@ -116,11 +116,11 @@ class _GridViewportElement extends VirtualViewportElement<GridViewport> {
|
||||
int get materializedChildCount => _materializedChildCount;
|
||||
int _materializedChildCount;
|
||||
|
||||
double get repaintOffsetBase => _repaintOffsetBase;
|
||||
double _repaintOffsetBase;
|
||||
double get startOffsetBase => _startOffsetBase;
|
||||
double _startOffsetBase;
|
||||
|
||||
double get repaintOffsetLimit =>_repaintOffsetLimit;
|
||||
double _repaintOffsetLimit;
|
||||
double get startOffsetLimit =>_startOffsetLimit;
|
||||
double _startOffsetLimit;
|
||||
|
||||
void updateRenderObject() {
|
||||
renderObject.delegate = widget.delegate;
|
||||
@ -141,8 +141,8 @@ class _GridViewportElement extends VirtualViewportElement<GridViewport> {
|
||||
|
||||
_materializedChildBase = (materializedRowBase * _specification.columnCount).clamp(0, renderObject.virtualChildCount);
|
||||
_materializedChildCount = (materializedRowLimit * _specification.columnCount).clamp(0, renderObject.virtualChildCount) - _materializedChildBase;
|
||||
_repaintOffsetBase = _specification.rowOffsets[materializedRowBase];
|
||||
_repaintOffsetLimit = _specification.rowOffsets[materializedRowLimit] - containerExtent;
|
||||
_startOffsetBase = _specification.rowOffsets[materializedRowBase];
|
||||
_startOffsetLimit = _specification.rowOffsets[materializedRowLimit] - containerExtent;
|
||||
|
||||
super.layout(constraints);
|
||||
|
||||
|
@ -129,11 +129,11 @@ class _ListViewportElement extends VirtualViewportElement<ListViewport> {
|
||||
int get materializedChildCount => _materializedChildCount;
|
||||
int _materializedChildCount;
|
||||
|
||||
double get repaintOffsetBase => _repaintOffsetBase;
|
||||
double _repaintOffsetBase;
|
||||
double get startOffsetBase => _startOffsetBase;
|
||||
double _startOffsetBase;
|
||||
|
||||
double get repaintOffsetLimit =>_repaintOffsetLimit;
|
||||
double _repaintOffsetLimit;
|
||||
double get startOffsetLimit =>_startOffsetLimit;
|
||||
double _startOffsetLimit;
|
||||
|
||||
void updateRenderObject() {
|
||||
renderObject.scrollDirection = widget.scrollDirection;
|
||||
@ -156,21 +156,25 @@ class _ListViewportElement extends VirtualViewportElement<ListViewport> {
|
||||
}
|
||||
|
||||
void layout(BoxConstraints constraints) {
|
||||
int length = renderObject.virtualChildCount;
|
||||
final int length = renderObject.virtualChildCount;
|
||||
final double itemExtent = widget.itemExtent;
|
||||
|
||||
double contentExtent = widget.itemExtent * length;
|
||||
double containerExtent = _getContainerExtentFromRenderObject();
|
||||
|
||||
_materializedChildBase = math.max(0, widget.startOffset ~/ widget.itemExtent);
|
||||
int materializedChildLimit = math.max(0, ((widget.startOffset + containerExtent) / widget.itemExtent).ceil());
|
||||
_materializedChildBase = math.max(0, widget.startOffset ~/ itemExtent);
|
||||
int materializedChildLimit = math.max(0, ((widget.startOffset + containerExtent) / itemExtent).ceil());
|
||||
|
||||
if (!widget.itemsWrap) {
|
||||
_materializedChildBase = math.min(length, _materializedChildBase);
|
||||
materializedChildLimit = math.min(length, materializedChildLimit);
|
||||
} else if (length == 0) {
|
||||
materializedChildLimit = _materializedChildBase;
|
||||
}
|
||||
|
||||
_materializedChildCount = materializedChildLimit - _materializedChildBase;
|
||||
_repaintOffsetBase = _materializedChildBase * widget.itemExtent;
|
||||
_repaintOffsetLimit = materializedChildLimit * widget.itemExtent - containerExtent;
|
||||
_startOffsetBase = _materializedChildBase * itemExtent;
|
||||
_startOffsetLimit = materializedChildLimit * itemExtent - containerExtent;
|
||||
|
||||
super.layout(constraints);
|
||||
|
||||
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'basic.dart';
|
||||
import 'framework.dart';
|
||||
|
||||
@ -20,8 +22,10 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
|
||||
|
||||
int get materializedChildBase;
|
||||
int get materializedChildCount;
|
||||
double get repaintOffsetBase;
|
||||
double get repaintOffsetLimit;
|
||||
double get startOffsetBase;
|
||||
double get startOffsetLimit;
|
||||
|
||||
double get paintOffset => -(widget.startOffset - startOffsetBase);
|
||||
|
||||
List<Element> _materializedChildren = const <Element>[];
|
||||
|
||||
@ -61,10 +65,10 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
|
||||
void _updatePaintOffset() {
|
||||
switch (widget.scrollDirection) {
|
||||
case ScrollDirection.vertical:
|
||||
renderObject.paintOffset = new Offset(0.0, -(widget.startOffset - repaintOffsetBase));
|
||||
renderObject.paintOffset = new Offset(0.0, paintOffset);
|
||||
break;
|
||||
case ScrollDirection.horizontal:
|
||||
renderObject.paintOffset = new Offset(-(widget.startOffset - repaintOffsetBase), 0.0);
|
||||
renderObject.paintOffset = new Offset(paintOffset, 0.0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -72,24 +76,25 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
|
||||
void updateRenderObject() {
|
||||
renderObject.virtualChildCount = widget.children.length;
|
||||
|
||||
if (repaintOffsetBase != null) {
|
||||
if (startOffsetBase != null) {
|
||||
_updatePaintOffset();
|
||||
|
||||
// If we don't already need layout, we need to request a layout if the
|
||||
// viewport has shifted to expose new children.
|
||||
if (!renderObject.needsLayout) {
|
||||
if (repaintOffsetBase != null && widget.startOffset < repaintOffsetBase)
|
||||
if (startOffsetBase != null && widget.startOffset < startOffsetBase)
|
||||
renderObject.markNeedsLayout();
|
||||
else if (repaintOffsetLimit != null && widget.startOffset > repaintOffsetLimit)
|
||||
else if (startOffsetLimit != null && widget.startOffset > startOffsetLimit)
|
||||
renderObject.markNeedsLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void layout(BoxConstraints constraints) {
|
||||
assert(repaintOffsetBase != null);
|
||||
assert(repaintOffsetLimit != null);
|
||||
assert(startOffsetBase != null);
|
||||
assert(startOffsetLimit != null);
|
||||
_updatePaintOffset();
|
||||
// TODO(abarth): Set building: true here.
|
||||
BuildableElement.lockState(_materializeChildren);
|
||||
}
|
||||
|
||||
@ -119,11 +124,11 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
|
||||
int length = renderObject.virtualChildCount;
|
||||
assert(base != null);
|
||||
assert(count != null);
|
||||
_populateWidgets(base + count);
|
||||
_populateWidgets(base < 0 ? length : math.min(length, base + count));
|
||||
List<Widget> newWidgets = new List<Widget>(count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
int childIndex = base + i;
|
||||
Widget child = _widgets[childIndex % length];
|
||||
Widget child = _widgets[(childIndex % length).abs()];
|
||||
Key key = new ValueKey(child.key ?? childIndex);
|
||||
newWidgets[i] = new RepaintBoundary(key: key, child: child);
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ final List<GlobalKey> globalKeys = defaultPages.map((_) => new GlobalKey()).toLi
|
||||
int currentPage = null;
|
||||
bool itemsWrap = false;
|
||||
|
||||
Widget buildPage(BuildContext context, int page, int index) {
|
||||
Widget buildPage(int page) {
|
||||
return new Container(
|
||||
key: globalKeys[page],
|
||||
width: pageSize.width,
|
||||
@ -23,9 +23,8 @@ Widget buildPage(BuildContext context, int page, int index) {
|
||||
}
|
||||
|
||||
Widget buildFrame({ List<int> pages: defaultPages }) {
|
||||
final list = new PageableList<int>(
|
||||
items: pages,
|
||||
itemBuilder: buildPage,
|
||||
final list = new PageableList(
|
||||
children: pages.map(buildPage),
|
||||
itemsWrap: itemsWrap,
|
||||
scrollDirection: ScrollDirection.horizontal,
|
||||
onPageChanged: (int page) { currentPage = page; }
|
||||
@ -136,7 +135,7 @@ void main() {
|
||||
testWidgets((WidgetTester tester) {
|
||||
currentPage = null;
|
||||
itemsWrap = true;
|
||||
tester.pumpWidget(buildFrame(pages: null));
|
||||
tester.pumpWidget(buildFrame(pages: <int>[]));
|
||||
expect(currentPage, isNull);
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user