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(_) {
|
Widget build(_) {
|
||||||
return new TabBarView<String>(
|
return new TabBarView<String>(
|
||||||
items: _iconNames,
|
items: _iconNames,
|
||||||
itemBuilder: (BuildContext context, String iconName, int index) {
|
itemBuilder: (String iconName) {
|
||||||
return new Container(
|
return new Container(
|
||||||
key: new ValueKey<String>(iconName),
|
key: new ValueKey<String>(iconName),
|
||||||
padding: const EdgeDims.all(12.0),
|
padding: const EdgeDims.all(12.0),
|
||||||
|
@ -273,7 +273,7 @@ class StockHomeState extends State<StockHome> {
|
|||||||
drawer: _buildDrawer(context),
|
drawer: _buildDrawer(context),
|
||||||
body: new TabBarView<StockHomeTab>(
|
body: new TabBarView<StockHomeTab>(
|
||||||
items: <StockHomeTab>[StockHomeTab.market, StockHomeTab.portfolio],
|
items: <StockHomeTab>[StockHomeTab.market, StockHomeTab.portfolio],
|
||||||
itemBuilder: (BuildContext context, StockHomeTab tab, _) {
|
itemBuilder: (StockHomeTab tab) {
|
||||||
switch (tab) {
|
switch (tab) {
|
||||||
case StockHomeTab.market:
|
case StockHomeTab.market:
|
||||||
return _buildStockTab(context, tab, config.symbols);
|
return _buildStockTab(context, tab, config.symbols);
|
||||||
|
@ -41,7 +41,7 @@ class PageableListAppState extends State<PageableListApp> {
|
|||||||
ScrollDirection scrollDirection = ScrollDirection.horizontal;
|
ScrollDirection scrollDirection = ScrollDirection.horizontal;
|
||||||
bool itemsWrap = false;
|
bool itemsWrap = false;
|
||||||
|
|
||||||
Widget buildCard(BuildContext context, CardModel cardModel, int index) {
|
Widget buildCard(CardModel cardModel) {
|
||||||
Widget card = new Card(
|
Widget card = new Card(
|
||||||
color: cardModel.color,
|
color: cardModel.color,
|
||||||
child: new Container(
|
child: new Container(
|
||||||
@ -114,10 +114,9 @@ class PageableListAppState extends State<PageableListApp> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBody(BuildContext context) {
|
Widget _buildBody(BuildContext context) {
|
||||||
return new PageableList<CardModel>(
|
return new PageableList(
|
||||||
items: cardModels,
|
children: cardModels.map(buildCard),
|
||||||
itemsWrap: itemsWrap,
|
itemsWrap: itemsWrap,
|
||||||
itemBuilder: buildCard,
|
|
||||||
scrollDirection: scrollDirection
|
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({
|
TabBarView({
|
||||||
Key key,
|
Key key,
|
||||||
List<T> items,
|
List<T> items,
|
||||||
ItemBuilder<T> itemBuilder
|
TabItemBuilder<T> itemBuilder
|
||||||
}) : super(
|
}) : items = items, itemBuilder = itemBuilder, super(
|
||||||
key: key,
|
key: key,
|
||||||
scrollDirection: ScrollDirection.horizontal,
|
scrollDirection: ScrollDirection.horizontal,
|
||||||
items: items,
|
children: items.map((T item) => itemBuilder(item)),
|
||||||
itemBuilder: itemBuilder,
|
|
||||||
itemsWrap: false
|
itemsWrap: false
|
||||||
) {
|
) {
|
||||||
assert(items != null);
|
assert(items != null);
|
||||||
assert(items.length > 1);
|
assert(items.length > 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final List<T> items;
|
||||||
|
final TabItemBuilder<T> itemBuilder;
|
||||||
|
|
||||||
_TabBarViewState createState() => new _TabBarViewState<T>();
|
_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;
|
TabBarSelectionState _selection;
|
||||||
List<int> _itemIndices = [0, 1];
|
List<int> _itemIndices = [0, 1];
|
||||||
@ -916,14 +920,10 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> implements
|
|||||||
return settleScrollOffset();
|
return settleScrollOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> buildItems(BuildContext context, int start, int count) {
|
Widget buildContent(BuildContext context) {
|
||||||
TabBarSelectionState<T> newSelection = TabBarSelection.of(context);
|
TabBarSelectionState<T> newSelection = TabBarSelection.of(context);
|
||||||
if (_selection != newSelection)
|
if (_selection != newSelection)
|
||||||
_initSelection(newSelection);
|
_initSelection(newSelection);
|
||||||
return _itemIndices
|
return super.buildContent(context);
|
||||||
.skip(start)
|
|
||||||
.take(count)
|
|
||||||
.map((int i) => config.itemBuilder(context, config.items[i], i))
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,6 @@ class RenderList extends RenderVirtualViewport<ListParentData> implements HasScr
|
|||||||
paintOffset: paintOffset,
|
paintOffset: paintOffset,
|
||||||
callback: callback
|
callback: callback
|
||||||
) {
|
) {
|
||||||
assert(itemExtent != null);
|
|
||||||
addAll(children);
|
addAll(children);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,6 +74,8 @@ class RenderList extends RenderVirtualViewport<ListParentData> implements HasScr
|
|||||||
}
|
}
|
||||||
|
|
||||||
double get _preferredExtent {
|
double get _preferredExtent {
|
||||||
|
if (itemExtent == null)
|
||||||
|
return double.INFINITY;
|
||||||
double extent = itemExtent * virtualChildCount;
|
double extent = itemExtent * virtualChildCount;
|
||||||
if (padding != null)
|
if (padding != null)
|
||||||
extent += _scrollAxisPadding;
|
extent += _scrollAxisPadding;
|
||||||
@ -118,8 +119,16 @@ class RenderList extends RenderVirtualViewport<ListParentData> implements HasScr
|
|||||||
}
|
}
|
||||||
|
|
||||||
void performLayout() {
|
void performLayout() {
|
||||||
|
switch (scrollDirection) {
|
||||||
|
case ScrollDirection.vertical:
|
||||||
size = new Size(constraints.maxWidth,
|
size = new Size(constraints.maxWidth,
|
||||||
constraints.constrainHeight(_preferredExtent));
|
constraints.constrainHeight(_preferredExtent));
|
||||||
|
break;
|
||||||
|
case ScrollDirection.horizontal:
|
||||||
|
size = new Size(constraints.constrainWidth(_preferredExtent),
|
||||||
|
constraints.maxHeight);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (callback != null)
|
if (callback != null)
|
||||||
invokeLayoutCallback(callback);
|
invokeLayoutCallback(callback);
|
||||||
@ -136,15 +145,15 @@ class RenderList extends RenderVirtualViewport<ListParentData> implements HasScr
|
|||||||
switch (scrollDirection) {
|
switch (scrollDirection) {
|
||||||
case ScrollDirection.vertical:
|
case ScrollDirection.vertical:
|
||||||
itemWidth = math.max(0, size.width - (padding == null ? 0.0 : padding.horizontal));
|
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;
|
y = padding != null ? padding.top : 0.0;
|
||||||
dy = itemExtent;
|
dy = itemHeight;
|
||||||
break;
|
break;
|
||||||
case ScrollDirection.horizontal:
|
case ScrollDirection.horizontal:
|
||||||
itemWidth = itemExtent;
|
itemWidth = itemExtent ?? size.width;
|
||||||
itemHeight = math.max(0, size.height - (padding == null ? 0.0 : padding.vertical));
|
itemHeight = math.max(0, size.height - (padding == null ? 0.0 : padding.vertical));
|
||||||
x = padding != null ? padding.left : 0.0;
|
x = padding != null ? padding.left : 0.0;
|
||||||
dx = itemExtent;
|
dx = itemWidth;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,80 +220,3 @@ class _HomogeneousViewportElement extends _ViewportBaseElement<HomogeneousViewpo
|
|||||||
return widget.itemCount != null ? widget.itemCount * widget.itemExtent : double.INFINITY;
|
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.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:math' as math;
|
|
||||||
|
|
||||||
import 'package:flutter/animation.dart';
|
import 'package:flutter/animation.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
@ -11,8 +10,8 @@ import 'package:flutter/rendering.dart';
|
|||||||
|
|
||||||
import 'basic.dart';
|
import 'basic.dart';
|
||||||
import 'framework.dart';
|
import 'framework.dart';
|
||||||
import 'homogeneous_viewport.dart';
|
|
||||||
import 'scrollable.dart';
|
import 'scrollable.dart';
|
||||||
|
import 'virtual_viewport.dart';
|
||||||
|
|
||||||
/// Controls what alignment items use when settling.
|
/// Controls what alignment items use when settling.
|
||||||
enum ItemsSnapAlignment {
|
enum ItemsSnapAlignment {
|
||||||
@ -22,7 +21,7 @@ enum ItemsSnapAlignment {
|
|||||||
|
|
||||||
typedef void PageChangedCallback(int newPage);
|
typedef void PageChangedCallback(int newPage);
|
||||||
|
|
||||||
class PageableList<T> extends Scrollable {
|
class PageableList extends Scrollable {
|
||||||
PageableList({
|
PageableList({
|
||||||
Key key,
|
Key key,
|
||||||
initialScrollOffset,
|
initialScrollOffset,
|
||||||
@ -32,14 +31,13 @@ class PageableList<T> extends Scrollable {
|
|||||||
ScrollListener onScrollEnd,
|
ScrollListener onScrollEnd,
|
||||||
SnapOffsetCallback snapOffsetCallback,
|
SnapOffsetCallback snapOffsetCallback,
|
||||||
double snapAlignmentOffset: 0.0,
|
double snapAlignmentOffset: 0.0,
|
||||||
this.items,
|
|
||||||
this.itemBuilder,
|
|
||||||
this.itemsWrap: false,
|
this.itemsWrap: false,
|
||||||
this.itemsSnapAlignment: ItemsSnapAlignment.adjacentItem,
|
this.itemsSnapAlignment: ItemsSnapAlignment.adjacentItem,
|
||||||
this.onPageChanged,
|
this.onPageChanged,
|
||||||
this.scrollableListPainter,
|
this.scrollableListPainter,
|
||||||
this.duration: const Duration(milliseconds: 200),
|
this.duration: const Duration(milliseconds: 200),
|
||||||
this.curve: Curves.ease
|
this.curve: Curves.ease,
|
||||||
|
this.children
|
||||||
}) : super(
|
}) : super(
|
||||||
key: key,
|
key: key,
|
||||||
initialScrollOffset: initialScrollOffset,
|
initialScrollOffset: initialScrollOffset,
|
||||||
@ -51,21 +49,19 @@ class PageableList<T> extends Scrollable {
|
|||||||
snapAlignmentOffset: snapAlignmentOffset
|
snapAlignmentOffset: snapAlignmentOffset
|
||||||
);
|
);
|
||||||
|
|
||||||
final List<T> items;
|
|
||||||
final ItemBuilder<T> itemBuilder;
|
|
||||||
final ItemsSnapAlignment itemsSnapAlignment;
|
|
||||||
final bool itemsWrap;
|
final bool itemsWrap;
|
||||||
|
final ItemsSnapAlignment itemsSnapAlignment;
|
||||||
final PageChangedCallback onPageChanged;
|
final PageChangedCallback onPageChanged;
|
||||||
final ScrollableListPainter scrollableListPainter;
|
final ScrollableListPainter scrollableListPainter;
|
||||||
final Duration duration;
|
final Duration duration;
|
||||||
final Curve curve;
|
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> {
|
class PageableListState<T extends PageableList> extends ScrollableState<T> {
|
||||||
|
int get itemCount => config.children?.length ?? 0;
|
||||||
int get itemCount => config.items?.length ?? 0;
|
|
||||||
int _previousItemCount;
|
int _previousItemCount;
|
||||||
|
|
||||||
double pixelToScrollOffset(double value) {
|
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;
|
return pixelScrollExtent == 0.0 ? 0.0 : value / pixelScrollExtent;
|
||||||
}
|
}
|
||||||
|
|
||||||
void didUpdateConfig(Config oldConfig) {
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_updateScrollBehavior();
|
||||||
|
}
|
||||||
|
|
||||||
|
void didUpdateConfig(PageableList oldConfig) {
|
||||||
super.didUpdateConfig(oldConfig);
|
super.didUpdateConfig(oldConfig);
|
||||||
|
|
||||||
bool scrollBehaviorUpdateNeeded = config.scrollDirection != oldConfig.scrollDirection;
|
bool scrollBehaviorUpdateNeeded = config.scrollDirection != oldConfig.scrollDirection;
|
||||||
@ -94,9 +95,7 @@ class PageableListState<T, Config extends PageableList<T>> extends ScrollableSta
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _updateScrollBehavior() {
|
void _updateScrollBehavior() {
|
||||||
// if you don't call this from build(), you must call it from setState().
|
config.scrollableListPainter?.contentExtent = itemCount.toDouble();
|
||||||
if (config.scrollableListPainter != null)
|
|
||||||
config.scrollableListPainter.contentExtent = itemCount.toDouble();
|
|
||||||
scrollTo(scrollBehavior.updateExtents(
|
scrollTo(scrollBehavior.updateExtents(
|
||||||
contentExtent: itemCount.toDouble(),
|
contentExtent: itemCount.toDouble(),
|
||||||
containerExtent: 1.0,
|
containerExtent: 1.0,
|
||||||
@ -111,8 +110,7 @@ class PageableListState<T, Config extends PageableList<T>> extends ScrollableSta
|
|||||||
|
|
||||||
void dispatchOnScroll() {
|
void dispatchOnScroll() {
|
||||||
super.dispatchOnScroll();
|
super.dispatchOnScroll();
|
||||||
if (config.scrollableListPainter != null)
|
config.scrollableListPainter?.scrollOffset = scrollOffset;
|
||||||
config.scrollableListPainter.scrollOffset = scrollOffset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void dispatchOnScrollEnd() {
|
void dispatchOnScrollEnd() {
|
||||||
@ -121,17 +119,12 @@ class PageableListState<T, Config extends PageableList<T>> extends ScrollableSta
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget buildContent(BuildContext context) {
|
Widget buildContent(BuildContext context) {
|
||||||
if (itemCount != _previousItemCount) {
|
return new PageViewport(
|
||||||
_previousItemCount = itemCount;
|
|
||||||
_updateScrollBehavior();
|
|
||||||
}
|
|
||||||
return new HomogeneousPageViewport(
|
|
||||||
builder: buildItems,
|
|
||||||
itemsWrap: config.itemsWrap,
|
itemsWrap: config.itemsWrap,
|
||||||
itemCount: itemCount,
|
scrollDirection: config.scrollDirection,
|
||||||
direction: config.scrollDirection,
|
|
||||||
startOffset: scrollOffset,
|
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);
|
.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(_) {
|
void _notifyPageChanged(_) {
|
||||||
if (config.onPageChanged != null)
|
if (config.onPageChanged != null)
|
||||||
config.onPageChanged(itemCount == 0 ? 0 : scrollOffset.floor() % itemCount);
|
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
|
/// 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. Prefer [ScrollableWidgetList] when all the children
|
||||||
/// have the same height because it can use that property to be more efficient.
|
/// 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 get materializedChildCount => _materializedChildCount;
|
||||||
int _materializedChildCount;
|
int _materializedChildCount;
|
||||||
|
|
||||||
double get repaintOffsetBase => _repaintOffsetBase;
|
double get startOffsetBase => _startOffsetBase;
|
||||||
double _repaintOffsetBase;
|
double _startOffsetBase;
|
||||||
|
|
||||||
double get repaintOffsetLimit =>_repaintOffsetLimit;
|
double get startOffsetLimit =>_startOffsetLimit;
|
||||||
double _repaintOffsetLimit;
|
double _startOffsetLimit;
|
||||||
|
|
||||||
void updateRenderObject() {
|
void updateRenderObject() {
|
||||||
renderObject.delegate = widget.delegate;
|
renderObject.delegate = widget.delegate;
|
||||||
@ -141,8 +141,8 @@ class _GridViewportElement extends VirtualViewportElement<GridViewport> {
|
|||||||
|
|
||||||
_materializedChildBase = (materializedRowBase * _specification.columnCount).clamp(0, renderObject.virtualChildCount);
|
_materializedChildBase = (materializedRowBase * _specification.columnCount).clamp(0, renderObject.virtualChildCount);
|
||||||
_materializedChildCount = (materializedRowLimit * _specification.columnCount).clamp(0, renderObject.virtualChildCount) - _materializedChildBase;
|
_materializedChildCount = (materializedRowLimit * _specification.columnCount).clamp(0, renderObject.virtualChildCount) - _materializedChildBase;
|
||||||
_repaintOffsetBase = _specification.rowOffsets[materializedRowBase];
|
_startOffsetBase = _specification.rowOffsets[materializedRowBase];
|
||||||
_repaintOffsetLimit = _specification.rowOffsets[materializedRowLimit] - containerExtent;
|
_startOffsetLimit = _specification.rowOffsets[materializedRowLimit] - containerExtent;
|
||||||
|
|
||||||
super.layout(constraints);
|
super.layout(constraints);
|
||||||
|
|
||||||
|
@ -129,11 +129,11 @@ class _ListViewportElement extends VirtualViewportElement<ListViewport> {
|
|||||||
int get materializedChildCount => _materializedChildCount;
|
int get materializedChildCount => _materializedChildCount;
|
||||||
int _materializedChildCount;
|
int _materializedChildCount;
|
||||||
|
|
||||||
double get repaintOffsetBase => _repaintOffsetBase;
|
double get startOffsetBase => _startOffsetBase;
|
||||||
double _repaintOffsetBase;
|
double _startOffsetBase;
|
||||||
|
|
||||||
double get repaintOffsetLimit =>_repaintOffsetLimit;
|
double get startOffsetLimit =>_startOffsetLimit;
|
||||||
double _repaintOffsetLimit;
|
double _startOffsetLimit;
|
||||||
|
|
||||||
void updateRenderObject() {
|
void updateRenderObject() {
|
||||||
renderObject.scrollDirection = widget.scrollDirection;
|
renderObject.scrollDirection = widget.scrollDirection;
|
||||||
@ -156,21 +156,25 @@ class _ListViewportElement extends VirtualViewportElement<ListViewport> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void layout(BoxConstraints constraints) {
|
void layout(BoxConstraints constraints) {
|
||||||
int length = renderObject.virtualChildCount;
|
final int length = renderObject.virtualChildCount;
|
||||||
|
final double itemExtent = widget.itemExtent;
|
||||||
|
|
||||||
double contentExtent = widget.itemExtent * length;
|
double contentExtent = widget.itemExtent * length;
|
||||||
double containerExtent = _getContainerExtentFromRenderObject();
|
double containerExtent = _getContainerExtentFromRenderObject();
|
||||||
|
|
||||||
_materializedChildBase = math.max(0, widget.startOffset ~/ widget.itemExtent);
|
_materializedChildBase = math.max(0, widget.startOffset ~/ itemExtent);
|
||||||
int materializedChildLimit = math.max(0, ((widget.startOffset + containerExtent) / widget.itemExtent).ceil());
|
int materializedChildLimit = math.max(0, ((widget.startOffset + containerExtent) / itemExtent).ceil());
|
||||||
|
|
||||||
if (!widget.itemsWrap) {
|
if (!widget.itemsWrap) {
|
||||||
_materializedChildBase = math.min(length, _materializedChildBase);
|
_materializedChildBase = math.min(length, _materializedChildBase);
|
||||||
materializedChildLimit = math.min(length, materializedChildLimit);
|
materializedChildLimit = math.min(length, materializedChildLimit);
|
||||||
|
} else if (length == 0) {
|
||||||
|
materializedChildLimit = _materializedChildBase;
|
||||||
}
|
}
|
||||||
|
|
||||||
_materializedChildCount = materializedChildLimit - _materializedChildBase;
|
_materializedChildCount = materializedChildLimit - _materializedChildBase;
|
||||||
_repaintOffsetBase = _materializedChildBase * widget.itemExtent;
|
_startOffsetBase = _materializedChildBase * itemExtent;
|
||||||
_repaintOffsetLimit = materializedChildLimit * widget.itemExtent - containerExtent;
|
_startOffsetLimit = materializedChildLimit * itemExtent - containerExtent;
|
||||||
|
|
||||||
super.layout(constraints);
|
super.layout(constraints);
|
||||||
|
|
||||||
|
@ -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 'basic.dart';
|
import 'basic.dart';
|
||||||
import 'framework.dart';
|
import 'framework.dart';
|
||||||
|
|
||||||
@ -20,8 +22,10 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
|
|||||||
|
|
||||||
int get materializedChildBase;
|
int get materializedChildBase;
|
||||||
int get materializedChildCount;
|
int get materializedChildCount;
|
||||||
double get repaintOffsetBase;
|
double get startOffsetBase;
|
||||||
double get repaintOffsetLimit;
|
double get startOffsetLimit;
|
||||||
|
|
||||||
|
double get paintOffset => -(widget.startOffset - startOffsetBase);
|
||||||
|
|
||||||
List<Element> _materializedChildren = const <Element>[];
|
List<Element> _materializedChildren = const <Element>[];
|
||||||
|
|
||||||
@ -61,10 +65,10 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
|
|||||||
void _updatePaintOffset() {
|
void _updatePaintOffset() {
|
||||||
switch (widget.scrollDirection) {
|
switch (widget.scrollDirection) {
|
||||||
case ScrollDirection.vertical:
|
case ScrollDirection.vertical:
|
||||||
renderObject.paintOffset = new Offset(0.0, -(widget.startOffset - repaintOffsetBase));
|
renderObject.paintOffset = new Offset(0.0, paintOffset);
|
||||||
break;
|
break;
|
||||||
case ScrollDirection.horizontal:
|
case ScrollDirection.horizontal:
|
||||||
renderObject.paintOffset = new Offset(-(widget.startOffset - repaintOffsetBase), 0.0);
|
renderObject.paintOffset = new Offset(paintOffset, 0.0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,24 +76,25 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
|
|||||||
void updateRenderObject() {
|
void updateRenderObject() {
|
||||||
renderObject.virtualChildCount = widget.children.length;
|
renderObject.virtualChildCount = widget.children.length;
|
||||||
|
|
||||||
if (repaintOffsetBase != null) {
|
if (startOffsetBase != null) {
|
||||||
_updatePaintOffset();
|
_updatePaintOffset();
|
||||||
|
|
||||||
// If we don't already need layout, we need to request a layout if the
|
// If we don't already need layout, we need to request a layout if the
|
||||||
// viewport has shifted to expose new children.
|
// viewport has shifted to expose new children.
|
||||||
if (!renderObject.needsLayout) {
|
if (!renderObject.needsLayout) {
|
||||||
if (repaintOffsetBase != null && widget.startOffset < repaintOffsetBase)
|
if (startOffsetBase != null && widget.startOffset < startOffsetBase)
|
||||||
renderObject.markNeedsLayout();
|
renderObject.markNeedsLayout();
|
||||||
else if (repaintOffsetLimit != null && widget.startOffset > repaintOffsetLimit)
|
else if (startOffsetLimit != null && widget.startOffset > startOffsetLimit)
|
||||||
renderObject.markNeedsLayout();
|
renderObject.markNeedsLayout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void layout(BoxConstraints constraints) {
|
void layout(BoxConstraints constraints) {
|
||||||
assert(repaintOffsetBase != null);
|
assert(startOffsetBase != null);
|
||||||
assert(repaintOffsetLimit != null);
|
assert(startOffsetLimit != null);
|
||||||
_updatePaintOffset();
|
_updatePaintOffset();
|
||||||
|
// TODO(abarth): Set building: true here.
|
||||||
BuildableElement.lockState(_materializeChildren);
|
BuildableElement.lockState(_materializeChildren);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,11 +124,11 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
|
|||||||
int length = renderObject.virtualChildCount;
|
int length = renderObject.virtualChildCount;
|
||||||
assert(base != null);
|
assert(base != null);
|
||||||
assert(count != null);
|
assert(count != null);
|
||||||
_populateWidgets(base + count);
|
_populateWidgets(base < 0 ? length : math.min(length, 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 = _widgets[childIndex % length];
|
Widget child = _widgets[(childIndex % length).abs()];
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ final List<GlobalKey> globalKeys = defaultPages.map((_) => new GlobalKey()).toLi
|
|||||||
int currentPage = null;
|
int currentPage = null;
|
||||||
bool itemsWrap = false;
|
bool itemsWrap = false;
|
||||||
|
|
||||||
Widget buildPage(BuildContext context, int page, int index) {
|
Widget buildPage(int page) {
|
||||||
return new Container(
|
return new Container(
|
||||||
key: globalKeys[page],
|
key: globalKeys[page],
|
||||||
width: pageSize.width,
|
width: pageSize.width,
|
||||||
@ -23,9 +23,8 @@ Widget buildPage(BuildContext context, int page, int index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget buildFrame({ List<int> pages: defaultPages }) {
|
Widget buildFrame({ List<int> pages: defaultPages }) {
|
||||||
final list = new PageableList<int>(
|
final list = new PageableList(
|
||||||
items: pages,
|
children: pages.map(buildPage),
|
||||||
itemBuilder: buildPage,
|
|
||||||
itemsWrap: itemsWrap,
|
itemsWrap: itemsWrap,
|
||||||
scrollDirection: ScrollDirection.horizontal,
|
scrollDirection: ScrollDirection.horizontal,
|
||||||
onPageChanged: (int page) { currentPage = page; }
|
onPageChanged: (int page) { currentPage = page; }
|
||||||
@ -136,7 +135,7 @@ void main() {
|
|||||||
testWidgets((WidgetTester tester) {
|
testWidgets((WidgetTester tester) {
|
||||||
currentPage = null;
|
currentPage = null;
|
||||||
itemsWrap = true;
|
itemsWrap = true;
|
||||||
tester.pumpWidget(buildFrame(pages: null));
|
tester.pumpWidget(buildFrame(pages: <int>[]));
|
||||||
expect(currentPage, isNull);
|
expect(currentPage, isNull);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user