Commit 6fd68597 authored by Adam Barth's avatar Adam Barth

LazyBlock docs and physics

This patch adds dartdoc to LazyBlock. Also, this patch fixes the scrolling
physics of LazyBlock. Previously, we updated a running simulation only when the
change in scroll behavior changed the current scroll offset. Now we update
running simulations every time the behavior changes because the simulation
might depend on quantities other than the current scroll offset.
parent 0bf68cc5
......@@ -418,7 +418,7 @@ class CardCollectionState extends State<CardCollection> {
);
} else {
cardCollection = new LazyBlock(
delegate: new LazyBlockBuilder(_buildCard),
delegate: new LazyBlockBuilder(builder: _buildCard),
snapOffsetCallback: _snapToCenter ? _toSnapOffset : null
);
}
......
......@@ -130,7 +130,7 @@ class CardBuilder extends LazyBlockDelegate {
class OverlayGeometryAppState extends State<OverlayGeometryApp> {
List<CardModel> cardModels;
Map<MarkerType, Point> markers = new Map<MarkerType, Point>();
double markersScrollOffset;
double markersScrollOffset = 0.0;
ScrollListener scrollListener;
@override
......
......@@ -125,9 +125,9 @@ class RenderViewportBase extends RenderBox implements HasMainAxis {
/// The direction in which the child is permitted to be larger than the viewport
///
/// If the viewport is scrollable in a particular direction (e.g., vertically),
/// the child is given layout constraints that are fully unconstrainted in
/// that direction (e.g., the child can be as tall as it wants).
/// The child is given layout constraints that are fully unconstrainted along
/// the main axis (e.g., the child can be as tall as it wants if the main axis
/// is vertical).
@override
Axis get mainAxis => _mainAxis;
Axis _mainAxis;
......
......@@ -1013,9 +1013,9 @@ class Viewport extends SingleChildRenderObjectWidget {
/// The direction in which the child is permitted to be larger than the viewport
///
/// If the viewport is scrollable in a particular direction (e.g., vertically),
/// the child is given layout constraints that are fully unconstrainted in
/// that direction (e.g., the child can be as tall as it wants).
/// The child is given layout constraints that are fully unconstrainted along
/// the main axis (e.g., the child can be as tall as it wants if the main axis
/// is vertical).
final Axis mainAxis;
final ViewportAnchor anchor;
......@@ -1025,6 +1025,7 @@ class Viewport extends SingleChildRenderObjectWidget {
/// Often used to paint scroll bars.
final RenderObjectPainter overlayPainter;
/// Called when the interior or exterior dimensions of the viewport change.
final ViewportDimensionsChangeCallback onPaintOffsetUpdateNeeded;
@override
......
......@@ -162,7 +162,7 @@ abstract class AnimatedWidgetBaseState<T extends ImplicitlyAnimatedWidget> exten
}
/// Subclasses must implement this function by running through the following
/// steps for for each animatable facet in the class:
/// steps for each animatable facet in the class:
///
/// 1. Call the visitor callback with three arguments, the first argument
/// being the current value of the Tween<T> object that represents the
......
......@@ -9,7 +9,10 @@ import 'framework.dart';
import 'scrollable.dart';
import 'scroll_behavior.dart';
/// Provides children for [LazyBlock] and [LazyBlockViewport]
/// Provides children for [LazyBlock] or [LazyBlockViewport].
///
/// See also [LazyBlockBuilder] for an implementation of LazyBlockDelegate based
/// on an [IndexedBuilder] closure.
abstract class LazyBlockDelegate {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
......@@ -17,25 +20,54 @@ abstract class LazyBlockDelegate {
/// Returns a widget representing the item with the given index.
///
/// This function might be called with index parameters in any order. This
/// function should return null for indices that exceed the number of children
/// provided by this delegate. If this function must not return a null value
/// for an index if it previously returned a non-null value for that index or
/// a larger index.
///
/// This function might be called during the build or layout phases of the
/// pipeline.
///
/// The returned widget might or might not be cached by [LazyBlock]. See
/// [shouldRebuild] for details about how to evict the cache.
Widget buildItem(BuildContext context, int index);
/// Whether [LazyBlock] should evict its cache of widgets returned by [buildItem].
///
/// When a [LazyBlock] receives a new configuration, it evicts its cache of
/// widgets if (1) the new configuration has a delegate with a different
/// runtimeType thant he old delegate, or (2) the [shouldRebuild] method of
/// the new delegate returns true when passed the old delgate.
/// When a [LazyBlock] receives a new configuration with a new delegate, it
/// evicts its cache of widgets if (1) the new configuration has a delegate
/// with a different runtimeType than the old delegate, or (2) the
/// [shouldRebuild] method of the new delegate returns true when passed the
/// old delgate.
///
/// When calling this function, [LazyBlock] will always pass an argument that
/// matches the runtimeType of the receiver.
bool shouldRebuild(LazyBlockDelegate oldDelegate);
}
/// Uses an [IndexedBuilder] to provide children for [LazyBlock].
///
/// A LazyBlockBuilder rebuilds the children whenever the [LazyBlock] is
/// rebuilt, similar to the behavior of [Builder].
///
/// See also [LazyBlockViewport].
class LazyBlockBuilder extends LazyBlockDelegate {
LazyBlockBuilder(this.builder);
/// Creates a LazyBlockBuilder based on the given builder.
LazyBlockBuilder({ this.builder }) {
assert(builder != null);
}
/// Returns a widget representing the item with the given index.
///
/// This function might be called with index parameters in any order. This
/// function should return null for indices that exceed the number of children
/// provided by this delegate. If this function must not return a null value
/// for an index if it previously returned a non-null value for that index or
/// a larger index.
///
/// This function might be called during the build or layout phases of the
/// pipeline.
final IndexedBuilder builder;
@override
......@@ -45,20 +77,46 @@ class LazyBlockBuilder extends LazyBlockDelegate {
bool shouldRebuild(LazyBlockDelegate oldDelegate) => true;
}
/// An infinite scrolling list of variable height children.
///
/// [LazyBlock] is a general-purpose scrollable list for a large (or infinite)
/// number of children that might not all have the same height. Rather than
/// materializing all of its children, [LazyBlock] asks its [delegate] to build
/// child widgets lazily to fill its viewport. [LazyBlock] caches the widgets
/// it obtains from the delegate as long as they're visible. (See
/// [LazyBlockDelegate.shouldRebuild] for details about how to evict the cache.)
///
/// [LazyBlock] works by dead reckoning changes to its [scrollOffset] from the
/// top of the first child that is visible in its viewport. If the children
/// above the first visible child change size, the [scrollOffset] might not
/// return to zero when the [LazyBlock] is scrolled all the way back to the
/// start because the height of each child will be subtracted incrementally from
/// the current scroll position. For this reason, making large changes to the
/// [scrollOffset] is expensive because [LazyBlock] computes the size of every
/// child between the old scroll offset and the new scroll offset.
///
/// Prefer [ScrollableList] when all the children have the same height because
/// it can use that property to be more efficient. Prefer [ScrollableViewport]
/// when there is only one child.
class LazyBlock extends Scrollable {
LazyBlock({
Key key,
double initialScrollOffset,
Axis scrollDirection: Axis.vertical,
ScrollListener onScroll,
SnapOffsetCallback snapOffsetCallback,
this.delegate
}) : super(
key: key,
initialScrollOffset: initialScrollOffset,
scrollDirection: scrollDirection,
onScroll: onScroll,
snapOffsetCallback: snapOffsetCallback
);
/// Provides children for this widget.
///
/// See [LazyBlockDelegate] for details.
final LazyBlockDelegate delegate;
@override
......@@ -87,27 +145,70 @@ class _LazyBlockState extends ScrollableState<LazyBlock> {
Widget buildContent(BuildContext context) {
return new LazyBlockViewport(
startOffset: scrollOffset,
direction: config.scrollDirection,
mainAxis: config.scrollDirection,
onExtentsChanged: _handleExtentsChanged,
delegate: config.delegate
);
}
}
/// Signature used by [LazyBlockViewport] to report its interior and exterior dimensions.
typedef void LazyBlockExtentsChangedCallback(double contentExtent, double containerExtent, double minScrollOffset);
/// A viewport on an infinite list of variable height children.
///
/// [LazyBlockViewport] is a a general-purpose viewport for a large (or
/// infinite) number of children that might not all have the same height. Rather
/// than materializing all of its children, [LazyBlockViewport] asks its
/// [delegate] to build child widgets lazily to fill itself. [LazyBlockViewport]
/// caches the widgets it obtains from the delegate as long as they're visible.
/// (See [LazyBlockDelegate.shouldRebuild] for details about how to evict the
/// cache.)
///
/// [LazyBlockViewport] works by dead reckoning changes to its [startOffset]
/// from the top of the first child that is visible in itself. For this reason,
/// making large changes to the [startOffset] is expensive because
/// [LazyBlockViewport] computes the size of every child between the old offset
/// and the new offset.
///
/// Prefer [ListViewport] when all the children have the same height because
/// it can use that property to be more efficient. Prefer [Viewport] when there
/// is only one child.
///
/// For a scrollable version of this widget, see [LazyBlock].
class LazyBlockViewport extends RenderObjectWidget {
LazyBlockViewport({
Key key,
this.startOffset,
this.direction,
this.startOffset: 0.0,
this.mainAxis: Axis.vertical,
this.onExtentsChanged,
this.delegate
}) : super(key: key);
}) : super(key: key) {
assert(delegate != null);
}
/// The offset of the start of the viewport.
///
/// As the start offset increases, children with larger indices are visible
/// in the viewport.
///
/// For vertical viewports, the offset is from the top of the viewport. For
/// horizontal viewports, the offset is from the left of the viewport.
final double startOffset;
final Axis direction;
/// The direction in which the children are permitted to be larger than the viewport
///
/// The children are given layout constraints that are fully unconstrainted
/// along the main axis (e.g., children can be as tall as it wants if the main
/// axis is vertical).
final Axis mainAxis;
/// Called when the interior or exterior dimensions of the viewport change.
final LazyBlockExtentsChangedCallback onExtentsChanged;
/// Provides children for this widget.
///
/// See [LazyBlockDelegate] for details.
final LazyBlockDelegate delegate;
@override
......@@ -264,7 +365,7 @@ class _LazyBlockElement extends RenderObjectElement {
super.mount(parent, newSlot);
renderObject
..callback = _layout
..mainAxis = widget.direction;
..mainAxis = widget.mainAxis;
// Children will get built during layout.
// Paint offset will get updated during layout.
}
......@@ -273,7 +374,7 @@ class _LazyBlockElement extends RenderObjectElement {
void update(LazyBlockViewport newWidget) {
LazyBlockViewport oldWidget = widget;
super.update(newWidget);
renderObject.mainAxis = widget.direction;
renderObject.mainAxis = widget.mainAxis;
LazyBlockDelegate newDelegate = newWidget.delegate;
LazyBlockDelegate oldDelegate = oldWidget.delegate;
if (newDelegate != oldDelegate && (newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRebuild(oldDelegate))) {
......@@ -391,9 +492,9 @@ class _LazyBlockElement extends RenderObjectElement {
_minScrollOffset = currentLogicalOffset;
_startOffsetLowerLimit = double.NEGATIVE_INFINITY;
} else {
// The first element is not visible. Ensure that we have enough headroom
// so we don't hit the min scroll offset prematurely.
_minScrollOffset = currentLogicalOffset - blockExtent * 2.0;
// The first element is not visible. Ensure that we have one blockExtent
// of headroom so we don't hit the min scroll offset prematurely.
_minScrollOffset = currentLogicalOffset - blockExtent;
_startOffsetLowerLimit = currentLogicalOffset;
}
......@@ -430,12 +531,12 @@ class _LazyBlockElement extends RenderObjectElement {
if (currentLogicalOffset < endLogicalOffset) {
// The last element is visible. We need to update our reckoning of where
// the max scroll offset is.
_maxScrollOffset = currentLogicalOffset;
_maxScrollOffset = currentLogicalOffset - blockExtent;
_startOffsetUpperLimit = double.INFINITY;
} else {
// The last element is not visible. Ensure that we have enough headroom
// so we don't hit the max scroll offset prematurely.
_maxScrollOffset = currentLogicalOffset + blockExtent * 2.0;
// The last element is not visible. Ensure that we have one blockExtent
// of headroom so we don't hit the max scroll offset prematurely.
_maxScrollOffset = currentLogicalOffset;
_startOffsetUpperLimit = currentLogicalOffset - blockExtent;
}
......@@ -469,7 +570,7 @@ class _LazyBlockElement extends RenderObjectElement {
LazyBlockExtentsChangedCallback onExtentsChanged = widget.onExtentsChanged;
if (onExtentsChanged != null) {
double contentExtent = _maxScrollOffset - _minScrollOffset;
double contentExtent = _maxScrollOffset - _minScrollOffset + blockExtent;
if (_lastReportedContentExtent != contentExtent ||
_lastReportedContainerExtent != blockExtent ||
_lastReportedMinScrollOffset != _minScrollOffset) {
......@@ -482,7 +583,7 @@ class _LazyBlockElement extends RenderObjectElement {
}
BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
switch (widget.direction) {
switch (widget.mainAxis) {
case Axis.horizontal:
return new BoxConstraints.tightFor(height: constraints.maxHeight);
case Axis.vertical:
......@@ -491,7 +592,7 @@ class _LazyBlockElement extends RenderObjectElement {
}
double _getMainAxisExtent(Size size) {
switch (widget.direction) {
switch (widget.mainAxis) {
case Axis.horizontal:
return size.width;
case Axis.vertical:
......@@ -500,7 +601,7 @@ class _LazyBlockElement extends RenderObjectElement {
}
Offset _getMainAxisOffsetForSize(Size size) {
switch (widget.direction) {
switch (widget.mainAxis) {
case Axis.horizontal:
return new Offset(size.width, 0.0);
case Axis.vertical:
......@@ -517,7 +618,7 @@ class _LazyBlockElement extends RenderObjectElement {
void _updatePaintOffset() {
double physicalStartOffset = widget.startOffset - _firstChildLogicalOffset;
switch (widget.direction) {
switch (widget.mainAxis) {
case Axis.horizontal:
renderObject.paintOffset = new Offset(-physicalStartOffset, 0.0);
break;
......
......@@ -379,9 +379,16 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
return _controller.animateTo(newScrollOffset, duration: duration, curve: curve).then(_endScroll);
}
/// Update any in-progress scrolling physics to account for new scroll behavior.
///
/// The scrolling physics depends on the scroll behavior. When changing the
/// scrolling behavior, call this function to update any in-progress scrolling
/// physics to account for the new scroll behavior. This function preserves
/// the current velocity when updating the physics.
///
/// If there are no in-progress scrolling physics, this function scrolls to
/// the given offset instead.
void didUpdateScrollBehavior(double newScrollOffset) {
if (newScrollOffset == _scrollOffset)
return;
if (_numberOfInProgressScrolls > 0) {
if (_simulation != null) {
double dx = _simulation.dx(_controller.lastElapsedDuration.inMicroseconds / Duration.MICROSECONDS_PER_SECOND);
......
......@@ -126,8 +126,8 @@ class _GridViewportElement extends VirtualViewportElement {
super.updateRenderObject(oldWidget);
}
double _contentExtent;
double _containerExtent;
double _lastReportedContentExtent;
double _lastReportedContainerExtent;
GridSpecification _specification;
@override
......@@ -146,10 +146,10 @@ class _GridViewportElement extends VirtualViewportElement {
super.layout(constraints);
if (contentExtent != _contentExtent || containerExtent != _containerExtent) {
_contentExtent = contentExtent;
_containerExtent = containerExtent;
widget.onExtentsChanged(_contentExtent, _containerExtent);
if (contentExtent != _lastReportedContentExtent || containerExtent != _lastReportedContainerExtent) {
_lastReportedContentExtent = contentExtent;
_lastReportedContainerExtent = containerExtent;
widget.onExtentsChanged(_lastReportedContentExtent, _lastReportedContainerExtent);
}
}
}
......@@ -192,8 +192,8 @@ class _VirtualListViewportElement extends VirtualViewportElement {
super.updateRenderObject(oldWidget);
}
double _contentExtent;
double _containerExtent;
double _lastReportedContentExtent;
double _lastReportedContainerExtent;
@override
void layout(BoxConstraints constraints) {
......@@ -253,10 +253,10 @@ class _VirtualListViewportElement extends VirtualViewportElement {
super.layout(constraints);
if (contentExtent != _contentExtent || containerExtent != _containerExtent) {
_contentExtent = contentExtent;
_containerExtent = containerExtent;
widget.onExtentsChanged(_contentExtent, _containerExtent);
if (contentExtent != _lastReportedContentExtent || containerExtent != _lastReportedContainerExtent) {
_lastReportedContentExtent = contentExtent;
_lastReportedContainerExtent = containerExtent;
widget.onExtentsChanged(_lastReportedContentExtent, _lastReportedContainerExtent);
}
}
}
......
......@@ -23,7 +23,7 @@ Widget buildCard(BuildContext context, int index) {
Widget buildFrame() {
return new LazyBlock(
delegate: new LazyBlockBuilder(buildCard)
delegate: new LazyBlockBuilder(builder: buildCard)
);
}
......
......@@ -100,7 +100,7 @@ class TexturedLinePainter {
List<Vector2> miters = _computeMiterList(vectors, false);
List<Point> vertices = <Point>[];
List<int> indicies = <int>[];
List<int> indices = <int>[];
List<Color> verticeColors = <Color>[];
List<Point> textureCoordinates;
double textureTop;
......@@ -151,8 +151,8 @@ class TexturedLinePainter {
int lastIndex1 = (i - 1) * 2 + 1;
int currentIndex0 = i * 2;
int currentIndex1 = i * 2 + 1;
indicies.addAll(<int>[lastIndex0, lastIndex1, currentIndex0]);
indicies.addAll(<int>[lastIndex1, currentIndex1, currentIndex0]);
indices.addAll(<int>[lastIndex0, lastIndex1, currentIndex0]);
indices.addAll(<int>[lastIndex1, currentIndex1, currentIndex0]);
// Add colors
verticeColors.add(colors[i]);
......@@ -170,7 +170,7 @@ class TexturedLinePainter {
lastMiter = currentMiter;
}
canvas.drawVertices(VertexMode.triangles, vertices, textureCoordinates, verticeColors, TransferMode.modulate, indicies, _cachedPaint);
canvas.drawVertices(VertexMode.triangles, vertices, textureCoordinates, verticeColors, TransferMode.modulate, indices, _cachedPaint);
}
double _xPosForStop(double stop) {
......
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