Commit 99bca282 authored by Adam Barth's avatar Adam Barth

Introduce ScrollableList2

ScrollableList2 uses the same pattern as ScrollableGrid, which requires the
client to allocate widgets for every list item but doesn't inflate them unless
they're actually needed for the view. It improves on the original
ScrollableList by not requiring a rebuild of the whole visible portion of the
list when scrolling. In fact, small scrolls can often be handled entirely by
repainting.
parent 888dc770
...@@ -72,13 +72,14 @@ class MediaQueryExample extends StatelessComponent { ...@@ -72,13 +72,14 @@ class MediaQueryExample extends StatelessComponent {
items.add(new AdaptiveItem("Item $i")); items.add(new AdaptiveItem("Item $i"));
if (MediaQuery.of(context).size.width < _gridViewBreakpoint) { if (MediaQuery.of(context).size.width < _gridViewBreakpoint) {
return new Block( return new ScrollableList2(
items.map((AdaptiveItem item) => item.toListItem()).toList() itemExtent: 50.0,
children: items.map((AdaptiveItem item) => item.toListItem()).toList()
); );
} else { } else {
return new ScrollableGrid( return new ScrollableGrid(
children: items.map((AdaptiveItem item) => item.toCard()).toList(), delegate: new MaxTileWidthGridDelegate(maxTileWidth: _maxTileWidth),
delegate: new MaxTileWidthGridDelegate(maxTileWidth: _maxTileWidth) children: items.map((AdaptiveItem item) => item.toCard()).toList()
); );
} }
} }
......
...@@ -18,6 +18,7 @@ export 'src/rendering/flex.dart'; ...@@ -18,6 +18,7 @@ export 'src/rendering/flex.dart';
export 'src/rendering/grid.dart'; export 'src/rendering/grid.dart';
export 'src/rendering/image.dart'; export 'src/rendering/image.dart';
export 'src/rendering/layer.dart'; export 'src/rendering/layer.dart';
export 'src/rendering/list.dart';
export 'src/rendering/node.dart'; export 'src/rendering/node.dart';
export 'src/rendering/object.dart'; export 'src/rendering/object.dart';
export 'src/rendering/overflow.dart'; export 'src/rendering/overflow.dart';
......
...@@ -6,8 +6,7 @@ import 'dart:typed_data'; ...@@ -6,8 +6,7 @@ import 'dart:typed_data';
import 'box.dart'; import 'box.dart';
import 'object.dart'; import 'object.dart';
import 'viewport.dart';
import 'package:vector_math/vector_math_64.dart';
bool _debugIsMonotonic(List<double> offsets) { bool _debugIsMonotonic(List<double> offsets) {
bool result = true; bool result = true;
...@@ -314,8 +313,7 @@ class GridParentData extends ContainerBoxParentDataMixin<RenderBox> { ...@@ -314,8 +313,7 @@ class GridParentData extends ContainerBoxParentDataMixin<RenderBox> {
/// ///
/// Additionally, grid layout materializes all of its children, which makes it /// Additionally, grid layout materializes all of its children, which makes it
/// most useful for grids containing a moderate number of tiles. /// most useful for grids containing a moderate number of tiles.
class RenderGrid extends RenderBox with ContainerRenderObjectMixin<RenderBox, GridParentData>, class RenderGrid extends RenderVirtualViewport<GridParentData> {
RenderBoxContainerDefaultsMixin<RenderBox, GridParentData> {
RenderGrid({ RenderGrid({
List<RenderBox> children, List<RenderBox> children,
GridDelegate delegate, GridDelegate delegate,
...@@ -323,11 +321,11 @@ class RenderGrid extends RenderBox with ContainerRenderObjectMixin<RenderBox, Gr ...@@ -323,11 +321,11 @@ class RenderGrid extends RenderBox with ContainerRenderObjectMixin<RenderBox, Gr
int virtualChildCount, int virtualChildCount,
Offset paintOffset: Offset.zero, Offset paintOffset: Offset.zero,
LayoutCallback callback LayoutCallback callback
}) : _delegate = delegate, }) : _delegate = delegate, _virtualChildBase = virtualChildBase, super(
_virtualChildBase = virtualChildBase, virtualChildCount: virtualChildCount,
_virtualChildCount = virtualChildCount, paintOffset: paintOffset,
_paintOffset = paintOffset, callback: callback
_callback = callback { ) {
assert(delegate != null); assert(delegate != null);
addAll(children); addAll(children);
} }
...@@ -360,49 +358,6 @@ class RenderGrid extends RenderBox with ContainerRenderObjectMixin<RenderBox, Gr ...@@ -360,49 +358,6 @@ class RenderGrid extends RenderBox with ContainerRenderObjectMixin<RenderBox, Gr
markNeedsLayout(); markNeedsLayout();
} }
/// The total number of virtual children in the grid.
///
/// When asking the delegate for the grid specification, the grid will use
/// this number of children, which can be larger than the actual number of
/// children of this render object.
///
/// If the this value is null, the grid will use the actual child count of
/// this render object.
int get virtualChildCount => _virtualChildCount ?? childCount;
int _virtualChildCount;
void set virtualChildCount(int value) {
if (_virtualChildCount == value)
return;
_virtualChildCount = value;
markNeedsLayout();
}
/// The offset at which to paint the first tile.
///
/// Note: you can modify this property from within [callback], if necessary.
Offset get paintOffset => _paintOffset;
Offset _paintOffset;
void set paintOffset(Offset value) {
assert(value != null);
if (value == _paintOffset)
return;
_paintOffset = value;
markNeedsPaint();
}
/// Called during [layout] to determine the grid's children.
///
/// Typically the callback will mutate the child list appropriately, for
/// example so the child list contains only visible children.
LayoutCallback get callback => _callback;
LayoutCallback _callback;
void set callback(LayoutCallback value) {
if (value == _callback)
return;
_callback = value;
markNeedsLayout();
}
void setupParentData(RenderBox child) { void setupParentData(RenderBox child) {
if (child.parentData is! GridParentData) if (child.parentData is! GridParentData)
child.parentData = new GridParentData(); child.parentData = new GridParentData();
...@@ -447,17 +402,13 @@ class RenderGrid extends RenderBox with ContainerRenderObjectMixin<RenderBox, Gr ...@@ -447,17 +402,13 @@ class RenderGrid extends RenderBox with ContainerRenderObjectMixin<RenderBox, Gr
} }
} }
bool _hasVisualOverflow = false;
void performLayout() { void performLayout() {
_updateGridSpecification(); _updateGridSpecification();
Size gridSize = _specification.gridSize; Size gridSize = _specification.gridSize;
size = constraints.constrain(gridSize); size = constraints.constrain(gridSize);
if (gridSize.width > size.width || gridSize.height > size.height)
_hasVisualOverflow = true;
if (_callback != null) if (callback != null)
invokeLayoutCallback(_callback); invokeLayoutCallback(callback);
double gridTopPadding = _specification.padding.top; double gridTopPadding = _specification.padding.top;
double gridLeftPadding = _specification.padding.left; double gridLeftPadding = _specification.padding.left;
...@@ -492,29 +443,10 @@ class RenderGrid extends RenderBox with ContainerRenderObjectMixin<RenderBox, Gr ...@@ -492,29 +443,10 @@ class RenderGrid extends RenderBox with ContainerRenderObjectMixin<RenderBox, Gr
tileTop + placement.padding.top tileTop + placement.padding.top
); );
++childIndex; childIndex += 1;
assert(child.parentData == childParentData); assert(child.parentData == childParentData);
child = childParentData.nextSibling; child = childParentData.nextSibling;
} }
} }
void applyPaintTransform(RenderBox child, Matrix4 transform) {
super.applyPaintTransform(child, transform.translate(paintOffset));
}
bool hitTestChildren(HitTestResult result, { Point position }) {
return defaultHitTestChildren(result, position: position + -paintOffset);
}
void _paintContents(PaintingContext context, Offset offset) {
defaultPaint(context, offset + paintOffset);
}
void paint(PaintingContext context, Offset offset) {
if (_hasVisualOverflow)
context.pushClipRect(needsCompositing, offset, Point.origin & size, _paintContents);
else
_paintContents(context, offset);
}
} }
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'box.dart';
import 'object.dart';
import 'viewport.dart';
/// Parent data for use with [RenderList].
class ListParentData extends ContainerBoxParentDataMixin<RenderBox> { }
class RenderList extends RenderVirtualViewport<ListParentData> {
RenderList({
List<RenderBox> children,
double itemExtent,
int virtualChildCount,
Offset paintOffset: Offset.zero,
LayoutCallback callback
}) : _itemExtent = itemExtent, super(
virtualChildCount: virtualChildCount,
paintOffset: paintOffset,
callback: callback
) {
assert(itemExtent != null);
addAll(children);
}
double get itemExtent => _itemExtent;
double _itemExtent;
void set itemExtent (double newValue) {
assert(newValue != null);
if (_itemExtent == newValue)
return;
_itemExtent = newValue;
markNeedsLayout();
}
void setupParentData(RenderBox child) {
if (child.parentData is! ListParentData)
child.parentData = new ListParentData();
}
double get _preferredMainAxisExtent => itemExtent * virtualChildCount;
double getMinIntrinsicWidth(BoxConstraints constraints) {
assert(constraints.isNormalized);
return constraints.constrainWidth(0.0);
}
double getMaxIntrinsicWidth(BoxConstraints constraints) {
assert(constraints.isNormalized);
return constraints.constrainWidth(0.0);
}
double getMinIntrinsicHeight(BoxConstraints constraints) {
assert(constraints.isNormalized);
return constraints.constrainHeight(_preferredMainAxisExtent);
}
double getMaxIntrinsicHeight(BoxConstraints constraints) {
assert(constraints.isNormalized);
return constraints.constrainHeight(_preferredMainAxisExtent);
}
void performLayout() {
double height = _preferredMainAxisExtent;
size = new Size(constraints.maxWidth, constraints.constrainHeight(height));
if (callback != null)
invokeLayoutCallback(callback);
BoxConstraints innerConstraints =
new BoxConstraints.tightFor(width: size.width, height: itemExtent);
int childIndex = 0;
RenderBox child = firstChild;
while (child != null) {
child.layout(innerConstraints);
final ListParentData childParentData = child.parentData;
childParentData.offset = new Offset(0.0, childIndex * itemExtent);
childIndex += 1;
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
}
}
...@@ -178,3 +178,66 @@ class RenderViewport extends RenderBox with RenderObjectWithChildMixin<RenderBox ...@@ -178,3 +178,66 @@ class RenderViewport extends RenderBox with RenderObjectWithChildMixin<RenderBox
return false; return false;
} }
} }
abstract class RenderVirtualViewport<T extends ContainerBoxParentDataMixin<RenderBox>>
extends RenderBox with ContainerRenderObjectMixin<RenderBox, T>,
RenderBoxContainerDefaultsMixin<RenderBox, T> {
RenderVirtualViewport({
int virtualChildCount,
Offset paintOffset,
LayoutCallback callback
}) : _virtualChildCount = virtualChildCount,
_paintOffset = paintOffset,
_callback = callback;
int get virtualChildCount => _virtualChildCount ?? childCount;
int _virtualChildCount;
void set virtualChildCount(int value) {
if (_virtualChildCount == value)
return;
_virtualChildCount = value;
markNeedsLayout();
}
/// The offset at which to paint the first item.
///
/// Note: you can modify this property from within [callback], if necessary.
Offset get paintOffset => _paintOffset;
Offset _paintOffset;
void set paintOffset(Offset value) {
assert(value != null);
if (value == _paintOffset)
return;
_paintOffset = value;
markNeedsPaint();
}
/// Called during [layout] to determine the grid's children.
///
/// Typically the callback will mutate the child list appropriately, for
/// example so the child list contains only visible children.
LayoutCallback get callback => _callback;
LayoutCallback _callback;
void set callback(LayoutCallback value) {
if (value == _callback)
return;
_callback = value;
markNeedsLayout();
}
void applyPaintTransform(RenderBox child, Matrix4 transform) {
super.applyPaintTransform(child, transform.translate(paintOffset));
}
bool hitTestChildren(HitTestResult result, { Point position }) {
return defaultHitTestChildren(result, position: position + -paintOffset);
}
void _paintContents(PaintingContext context, Offset offset) {
defaultPaint(context, offset + paintOffset);
}
void paint(PaintingContext context, Offset offset) {
context.pushClipRect(needsCompositing, offset, Point.origin & size, _paintContents);
}
}
...@@ -4,9 +4,9 @@ ...@@ -4,9 +4,9 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'basic.dart';
import 'framework.dart'; import 'framework.dart';
import 'scrollable.dart'; import 'scrollable.dart';
import 'virtual_viewport.dart';
import 'package:flutter/animation.dart'; import 'package:flutter/animation.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
...@@ -38,11 +38,11 @@ class ScrollableGrid extends Scrollable { ...@@ -38,11 +38,11 @@ class ScrollableGrid extends Scrollable {
final GridDelegate delegate; final GridDelegate delegate;
final List<Widget> children; final List<Widget> children;
ScrollableState createState() => new _ScrollableGrid(); ScrollableState createState() => new _ScrollableGridState();
} }
class _ScrollableGrid extends ScrollableState<ScrollableGrid> { class _ScrollableGridState extends ScrollableState<ScrollableGrid> {
ScrollBehavior createScrollBehavior() => new OverscrollWhenScrollableBehavior(); ScrollBehavior createScrollBehavior() => new OverscrollBehavior();
ExtentScrollBehavior get scrollBehavior => super.scrollBehavior; ExtentScrollBehavior get scrollBehavior => super.scrollBehavior;
void _handleExtentsChanged(double contentExtent, double containerExtent) { void _handleExtentsChanged(double contentExtent, double containerExtent) {
...@@ -65,9 +65,7 @@ class _ScrollableGrid extends ScrollableState<ScrollableGrid> { ...@@ -65,9 +65,7 @@ class _ScrollableGrid extends ScrollableState<ScrollableGrid> {
} }
} }
typedef void ExtentsChangedCallback(double contentExtent, double containerExtent); class GridViewport extends VirtualViewport {
class GridViewport extends RenderObjectWidget {
GridViewport({ GridViewport({
Key key, Key key,
this.startOffset, this.startOffset,
...@@ -87,6 +85,7 @@ class GridViewport extends RenderObjectWidget { ...@@ -87,6 +85,7 @@ class GridViewport extends RenderObjectWidget {
} }
// TODO(abarth): This function should go somewhere more general. // TODO(abarth): This function should go somewhere more general.
// See https://github.com/dart-lang/collection/pull/16
int _lowerBound(List sortedList, var value, { int begin: 0 }) { int _lowerBound(List sortedList, var value, { int begin: 0 }) {
int current = begin; int current = begin;
int count = sortedList.length - current; int count = sortedList.length - current;
...@@ -103,82 +102,31 @@ int _lowerBound(List sortedList, var value, { int begin: 0 }) { ...@@ -103,82 +102,31 @@ int _lowerBound(List sortedList, var value, { int begin: 0 }) {
return current; return current;
} }
class _GridViewportElement extends RenderObjectElement<GridViewport> { class _GridViewportElement extends VirtualViewportElement<GridViewport> {
_GridViewportElement(GridViewport widget) : super(widget); _GridViewportElement(GridViewport widget) : super(widget);
double _contentExtent; RenderGrid get renderObject => super.renderObject;
double _containerExtent;
int get materializedChildBase => _materializedChildBase;
int _materializedChildBase; int _materializedChildBase;
int _materializedChildCount;
List<Element> _materializedChildren = const <Element>[]; int get materializedChildCount => _materializedChildCount;
int _materializedChildCount;
GridSpecification _specification; double get repaintOffsetBase => _repaintOffsetBase;
double _repaintOffsetBase; double _repaintOffsetBase;
double _repaintOffsetLimit;
RenderGrid get renderObject => super.renderObject;
void visitChildren(ElementVisitor visitor) {
if (_materializedChildren == null)
return;
for (Element child in _materializedChildren)
visitor(child);
}
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
renderObject.callback = layout;
_updateRenderObject();
}
void unmount() {
renderObject.callback = null;
super.unmount();
}
void update(GridViewport newWidget) { double get repaintOffsetLimit =>_repaintOffsetLimit;
super.update(newWidget); double _repaintOffsetLimit;
_updateRenderObject();
if (!renderObject.needsLayout)
_materializeChildren();
}
void _updatePaintOffset() {
renderObject.paintOffset = new Offset(0.0, -(widget.startOffset - _repaintOffsetBase));
}
void _updateRenderObject() { void updateRenderObject() {
renderObject.delegate = widget.delegate; renderObject.delegate = widget.delegate;
renderObject.virtualChildCount = widget.children.length; super.updateRenderObject();
if (_specification != null) {
_updatePaintOffset();
// If we don't already need layout, we need to request a layout if the
// viewport has shifted to expose a new row.
if (!renderObject.needsLayout) {
if (_repaintOffsetBase != null && widget.startOffset < _repaintOffsetBase)
renderObject.markNeedsLayout();
else if (_repaintOffsetLimit != null && widget.startOffset + _containerExtent > _repaintOffsetLimit)
renderObject.markNeedsLayout();
}
}
} }
void _materializeChildren() { double _contentExtent;
assert(_materializedChildBase != null); double _containerExtent;
assert(_materializedChildCount != null); GridSpecification _specification;
List<Widget> newWidgets = new List<Widget>(_materializedChildCount);
for (int i = 0; i < _materializedChildCount; ++i) {
int childIndex = _materializedChildBase + i;
Widget child = widget.children[childIndex];
Key key = new ValueKey(child.key ?? childIndex);
newWidgets[i] = new RepaintBoundary(key: key, child: child);
}
_materializedChildren = updateChildren(_materializedChildren, newWidgets);
}
void layout(BoxConstraints constraints) { void layout(BoxConstraints constraints) {
_specification = renderObject.specification; _specification = renderObject.specification;
...@@ -188,13 +136,12 @@ class _GridViewportElement extends RenderObjectElement<GridViewport> { ...@@ -188,13 +136,12 @@ class _GridViewportElement extends RenderObjectElement<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; _materializedChildBase = (materializedRowBase * _specification.columnCount).clamp(0, widget.children.length);
_materializedChildCount = math.min(widget.children.length, materializedRowLimit * _specification.columnCount) - _materializedChildBase; _materializedChildCount = (materializedRowLimit * _specification.columnCount).clamp(0, widget.children.length) - _materializedChildBase;
_repaintOffsetBase = _specification.rowOffsets[materializedRowBase]; _repaintOffsetBase = _specification.rowOffsets[materializedRowBase];
_repaintOffsetLimit = _specification.rowOffsets[materializedRowLimit]; _repaintOffsetLimit = _specification.rowOffsets[materializedRowLimit];
_updatePaintOffset();
BuildableElement.lockState(_materializeChildren); super.layout(constraints);
if (contentExtent != _contentExtent || containerExtent != _containerExtent) { if (contentExtent != _contentExtent || containerExtent != _containerExtent) {
_contentExtent = contentExtent; _contentExtent = contentExtent;
...@@ -202,20 +149,4 @@ class _GridViewportElement extends RenderObjectElement<GridViewport> { ...@@ -202,20 +149,4 @@ class _GridViewportElement extends RenderObjectElement<GridViewport> {
widget.onExtentsChanged(_contentExtent, _containerExtent); widget.onExtentsChanged(_contentExtent, _containerExtent);
} }
} }
void insertChildRenderObject(RenderObject child, Element slot) {
RenderObject nextSibling = slot?.renderObject;
renderObject.add(child, before: nextSibling);
}
void moveChildRenderObject(RenderObject child, Element slot) {
assert(child.parent == renderObject);
RenderObject nextSibling = slot?.renderObject;
renderObject.move(child, before: nextSibling);
}
void removeChildRenderObject(RenderObject child) {
assert(child.parent == renderObject);
renderObject.remove(child);
}
} }
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'framework.dart';
import 'scrollable.dart';
import 'virtual_viewport.dart';
import 'package:flutter/animation.dart';
import 'package:flutter/rendering.dart';
class ScrollableList2 extends Scrollable {
ScrollableList2({
Key key,
double initialScrollOffset,
ScrollListener onScroll,
SnapOffsetCallback snapOffsetCallback,
double snapAlignmentOffset: 0.0,
this.itemExtent,
this.children
}) : super(
key: key,
initialScrollOffset: initialScrollOffset,
// TODO(abarth): Support horizontal offsets.
scrollDirection: ScrollDirection.vertical,
onScroll: onScroll,
snapOffsetCallback: snapOffsetCallback,
snapAlignmentOffset: snapAlignmentOffset
);
final double itemExtent;
final List<Widget> children;
ScrollableState createState() => new _ScrollableList2State();
}
class _ScrollableList2State extends ScrollableState<ScrollableList2> {
ScrollBehavior createScrollBehavior() => new OverscrollBehavior();
ExtentScrollBehavior get scrollBehavior => super.scrollBehavior;
void _handleExtentsChanged(double contentExtent, double containerExtent) {
setState(() {
scrollTo(scrollBehavior.updateExtents(
contentExtent: contentExtent,
containerExtent: containerExtent,
scrollOffset: scrollOffset
));
});
}
Widget buildContent(BuildContext context) {
return new ListViewport(
startOffset: scrollOffset,
itemExtent: config.itemExtent,
onExtentsChanged: _handleExtentsChanged,
children: config.children
);
}
}
class ListViewport extends VirtualViewport {
ListViewport({
Key key,
this.startOffset,
this.itemExtent,
this.onExtentsChanged,
this.children
});
final double startOffset;
final double itemExtent;
final ExtentsChangedCallback onExtentsChanged;
final List<Widget> children;
RenderList createRenderObject() => new RenderList(itemExtent: itemExtent);
_ListViewportElement createElement() => new _ListViewportElement(this);
}
class _ListViewportElement extends VirtualViewportElement<ListViewport> {
_ListViewportElement(ListViewport widget) : super(widget);
RenderList get renderObject => super.renderObject;
int get materializedChildBase => _materializedChildBase;
int _materializedChildBase;
int get materializedChildCount => _materializedChildCount;
int _materializedChildCount;
double get repaintOffsetBase => _repaintOffsetBase;
double _repaintOffsetBase;
double get repaintOffsetLimit =>_repaintOffsetLimit;
double _repaintOffsetLimit;
void updateRenderObject() {
renderObject.itemExtent = widget.itemExtent;
super.updateRenderObject();
}
double _contentExtent;
double _containerExtent;
void layout(BoxConstraints constraints) {
double contentExtent = widget.itemExtent * widget.children.length;
double containerExtent = renderObject.size.height;
_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;
_repaintOffsetBase = _materializedChildBase * widget.itemExtent;
_repaintOffsetLimit = materializedChildLimit * widget.itemExtent;
super.layout(constraints);
if (contentExtent != _contentExtent || containerExtent != _containerExtent) {
_contentExtent = contentExtent;
_containerExtent = containerExtent;
widget.onExtentsChanged(_contentExtent, _containerExtent);
}
}
}
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'basic.dart';
import 'framework.dart';
import 'package:flutter/rendering.dart';
typedef void ExtentsChangedCallback(double contentExtent, double containerExtent);
abstract class VirtualViewport extends RenderObjectWidget {
double get startOffset;
List<Widget> get children;
}
abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderObjectElement<T> {
VirtualViewportElement(T widget) : super(widget);
int get materializedChildBase;
int get materializedChildCount;
double get repaintOffsetBase;
double get repaintOffsetLimit;
List<Element> _materializedChildren = const <Element>[];
RenderVirtualViewport get renderObject => super.renderObject;
void visitChildren(ElementVisitor visitor) {
if (_materializedChildren == null)
return;
for (Element child in _materializedChildren)
visitor(child);
}
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
renderObject.callback = layout;
updateRenderObject();
}
void unmount() {
renderObject.callback = null;
super.unmount();
}
void update(T newWidget) {
super.update(newWidget);
updateRenderObject();
if (!renderObject.needsLayout)
_materializeChildren();
}
void _updatePaintOffset() {
renderObject.paintOffset =
renderObject.paintOffset = new Offset(0.0, -(widget.startOffset - repaintOffsetBase));
}
void updateRenderObject() {
renderObject.virtualChildCount = widget.children.length;
if (repaintOffsetBase != 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)
renderObject.markNeedsLayout();
else if (repaintOffsetLimit != null && widget.startOffset + renderObject.size.height > repaintOffsetLimit)
renderObject.markNeedsLayout();
}
}
}
void layout(BoxConstraints constraints) {
assert(repaintOffsetBase != null);
assert(repaintOffsetLimit != null);
_updatePaintOffset();
BuildableElement.lockState(_materializeChildren);
}
void _materializeChildren() {
int base = materializedChildBase;
int count = materializedChildCount;
assert(base != null);
assert(count != null);
List<Widget> newWidgets = new List<Widget>(count);
for (int i = 0; i < count; ++i) {
int childIndex = base + i;
Widget child = widget.children[childIndex];
Key key = new ValueKey(child.key ?? childIndex);
newWidgets[i] = new RepaintBoundary(key: key, child: child);
}
_materializedChildren = updateChildren(_materializedChildren, newWidgets);
}
void insertChildRenderObject(RenderObject child, Element slot) {
RenderObject nextSibling = slot?.renderObject;
renderObject.add(child, before: nextSibling);
}
void moveChildRenderObject(RenderObject child, Element slot) {
assert(child.parent == renderObject);
RenderObject nextSibling = slot?.renderObject;
renderObject.move(child, before: nextSibling);
}
void removeChildRenderObject(RenderObject child) {
assert(child.parent == renderObject);
renderObject.remove(child);
}
}
...@@ -33,10 +33,12 @@ export 'src/widgets/placeholder.dart'; ...@@ -33,10 +33,12 @@ export 'src/widgets/placeholder.dart';
export 'src/widgets/routes.dart'; export 'src/widgets/routes.dart';
export 'src/widgets/scrollable.dart'; export 'src/widgets/scrollable.dart';
export 'src/widgets/scrollable_grid.dart'; export 'src/widgets/scrollable_grid.dart';
export 'src/widgets/scrollable_list.dart';
export 'src/widgets/statistics_overlay.dart'; export 'src/widgets/statistics_overlay.dart';
export 'src/widgets/status_transitions.dart'; export 'src/widgets/status_transitions.dart';
export 'src/widgets/title.dart'; export 'src/widgets/title.dart';
export 'src/widgets/transitions.dart'; export 'src/widgets/transitions.dart';
export 'src/widgets/unique_component.dart'; export 'src/widgets/unique_component.dart';
export 'src/widgets/virtual_viewport.dart';
export 'package:vector_math/vector_math_64.dart' show Matrix4; export 'package:vector_math/vector_math_64.dart' show Matrix4;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment