diff --git a/packages/flutter/lib/src/rendering/viewport.dart b/packages/flutter/lib/src/rendering/viewport.dart index 1f3319400d..f6c0245266 100644 --- a/packages/flutter/lib/src/rendering/viewport.dart +++ b/packages/flutter/lib/src/rendering/viewport.dart @@ -9,20 +9,33 @@ import 'package:vector_math/vector_math_64.dart'; import 'box.dart'; import 'object.dart'; +/// The end of the viewport from which the paint offset is computed. enum ViewportAnchor { + /// The start (e.g., top or left, depending on the axis) of the first item + /// should be aligned with the start (e.g., top or left, depending on the + /// axis) of the viewport. start, + + /// The end (e.g., bottom or right, depending on the axis) of the last item + /// should be aligned with the end (e.g., bottom or right, depending on the + /// axis) of the viewport. end, } +/// The interior and exterior dimensions of a viewport. class ViewportDimensions { const ViewportDimensions({ this.contentSize: Size.zero, this.containerSize: Size.zero }); + /// A viewport that has zero size, both inside and outside. static const ViewportDimensions zero = const ViewportDimensions(); + /// The size of the content inside the viewport. final Size contentSize; + + /// The size of the outside of the viewport. final Size containerSize; bool get _debugHasAtLeastOneCommonDimension { @@ -30,6 +43,8 @@ class ViewportDimensions { || contentSize.height == containerSize.height; } + /// Returns the offset at which to paint the content, accounting for the given + /// anchor and the dimensions of the viewport. Offset getAbsolutePaintOffset({ Offset paintOffset, ViewportAnchor anchor }) { assert(_debugHasAtLeastOneCommonDimension); switch (anchor) { @@ -55,7 +70,9 @@ class ViewportDimensions { String toString() => 'ViewportDimensions(container: $containerSize, content: $contentSize)'; } +/// An interface that indicates that an object has a scroll direction. abstract class HasScrollDirection { + /// Whether this object scrolls horizontally or vertically. Axis get scrollDirection; } @@ -119,6 +136,9 @@ class RenderViewportBase extends RenderBox implements HasScrollDirection { markNeedsLayout(); } + /// The end of the viewport from which the paint offset is computed. + /// + /// See [ViewportAnchor] for more detail. ViewportAnchor get scrollAnchor => _scrollAnchor; ViewportAnchor _scrollAnchor; void set scrollAnchor(ViewportAnchor value) { diff --git a/packages/flutter/lib/src/widgets/pageable_list.dart b/packages/flutter/lib/src/widgets/pageable_list.dart index 68722256b2..1b6b2c2af9 100644 --- a/packages/flutter/lib/src/widgets/pageable_list.dart +++ b/packages/flutter/lib/src/widgets/pageable_list.dart @@ -202,7 +202,7 @@ class PageableListState extends ScrollableState { } } -class PageViewport extends VirtualViewport with VirtualViewportIterableMixin { +class PageViewport extends VirtualViewportFromIterable { PageViewport({ this.startOffset: 0.0, this.scrollDirection: Axis.vertical, diff --git a/packages/flutter/lib/src/widgets/scrollable_grid.dart b/packages/flutter/lib/src/widgets/scrollable_grid.dart index a8793129ab..6cdd873220 100644 --- a/packages/flutter/lib/src/widgets/scrollable_grid.dart +++ b/packages/flutter/lib/src/widgets/scrollable_grid.dart @@ -63,7 +63,7 @@ class _ScrollableGridState extends ScrollableState { } } -class GridViewport extends VirtualViewport with VirtualViewportIterableMixin { +class GridViewport extends VirtualViewportFromIterable { GridViewport({ this.startOffset, this.delegate, diff --git a/packages/flutter/lib/src/widgets/scrollable_list.dart b/packages/flutter/lib/src/widgets/scrollable_list.dart index 4a48ec147b..3c019296e5 100644 --- a/packages/flutter/lib/src/widgets/scrollable_list.dart +++ b/packages/flutter/lib/src/widgets/scrollable_list.dart @@ -240,7 +240,7 @@ class _VirtualListViewportElement extends VirtualViewportElement<_VirtualListVie } } -class ListViewport extends _VirtualListViewport with VirtualViewportIterableMixin { +class ListViewport extends _VirtualListViewport with VirtualViewportFromIterable { ListViewport({ ExtentsChangedCallback onExtentsChanged, double scrollOffset: 0.0, @@ -350,7 +350,7 @@ class _ScrollableLazyListState extends ScrollableState { } } -class LazyListViewport extends _VirtualListViewport with VirtualViewportLazyMixin { +class LazyListViewport extends _VirtualListViewport with VirtualViewportFromBuilder { LazyListViewport({ ExtentsChangedCallback onExtentsChanged, double scrollOffset: 0.0, diff --git a/packages/flutter/lib/src/widgets/virtual_viewport.dart b/packages/flutter/lib/src/widgets/virtual_viewport.dart index e55cb9eb01..28857f1239 100644 --- a/packages/flutter/lib/src/widgets/virtual_viewport.dart +++ b/packages/flutter/lib/src/widgets/virtual_viewport.dart @@ -11,7 +11,9 @@ import 'package:flutter/rendering.dart'; typedef void ExtentsChangedCallback(double contentExtent, double containerExtent); +/// An abstract widget whose children are not all materialized. abstract class VirtualViewport extends RenderObjectWidget { + /// The offset from the [ViewportAnchor] at which the viewport should start painting children. double get startOffset; _WidgetProvider _createWidgetProvider(); @@ -24,12 +26,24 @@ abstract class _WidgetProvider { Widget getChild(int i); } +/// Materializes a contiguous subset of its children. +/// +/// This class is a building block for building a widget that has more children +/// than it wishes to display at any given time. For example, [ScrollableList] +/// uses this element to materialize only those children that are visible. abstract class VirtualViewportElement extends RenderObjectElement { VirtualViewportElement(T widget) : super(widget); + /// The index of the first child to materialize. int get materializedChildBase; + + /// The number of children to materializes. int get materializedChildCount; + + /// The least offset for which [materializedChildBase] and [materializedChildCount] are valid. double get startOffsetBase; + + /// The greatest offset for which [materializedChildBase] and [materializedChildCount] are valid. double get startOffsetLimit; /// Returns the pixel offset for a scroll offset, accounting for the scroll @@ -124,6 +138,12 @@ abstract class VirtualViewportElement extends RenderO } } + /// Called by [RenderVirtualViewport] during layout. + /// + /// Subclasses should override this function to compute [materializedChildBase] + /// and [materializedChildCount]. Overrides should call this function to + /// update the [RenderVirtualViewport]'s paint offset and to materialize the + /// children. void layout(BoxConstraints constraints) { assert(startOffsetBase != null); assert(startOffsetLimit != null); @@ -162,7 +182,12 @@ abstract class VirtualViewportElement extends RenderO } } -abstract class VirtualViewportIterableMixin extends VirtualViewport { +/// A VirtualViewport that represents its children using [Iterable]. +/// +/// The iterator is advanced just far enough to obtain widgets for the children +/// that need to be materialized. +abstract class VirtualViewportFromIterable extends VirtualViewport { + /// The children, some of which might be materialized. Iterable get children; _IterableWidgetProvider _createWidgetProvider() => new _IterableWidgetProvider(); @@ -173,7 +198,7 @@ class _IterableWidgetProvider extends _WidgetProvider { Iterator _iterator; List _widgets; - void didUpdateWidget(VirtualViewportIterableMixin oldWidget, VirtualViewportIterableMixin newWidget) { + void didUpdateWidget(VirtualViewportFromIterable oldWidget, VirtualViewportFromIterable newWidget) { if (oldWidget == null || newWidget.children != oldWidget.children) { _iterator = null; _widgets = []; @@ -187,7 +212,7 @@ class _IterableWidgetProvider extends _WidgetProvider { int limit = base < 0 ? _length : math.min(_length, base + count); if (limit <= _widgets.length) return; - VirtualViewportIterableMixin widget = context.widget; + VirtualViewportFromIterable widget = context.widget; if (widget.children is List) { _widgets = widget.children; return; @@ -205,10 +230,21 @@ class _IterableWidgetProvider extends _WidgetProvider { Widget getChild(int i) => _widgets[(i % _length).abs()]; } +/// Signature of a callback that returns the sublist of widgets in the given range. typedef List ItemListBuilder(BuildContext context, int start, int count); -abstract class VirtualViewportLazyMixin extends VirtualViewport { +/// A VirtualViewport that represents its children using [ItemListBuilder]. +/// +/// This widget is less ergonomic than [VirtualViewportFromIterable] but scales to +/// unlimited numbers of children. +abstract class VirtualViewportFromBuilder extends VirtualViewport { + /// The total number of children that can be built. int get itemCount; + + /// A callback to build the subset of widgets that are needed to populate the + /// viewport. Not all of the returned widgets will actually be included in the + /// viewport (e.g., if we need to measure the size of non-visible children to + /// determine which children are visible). ItemListBuilder get itemBuilder; _LazyWidgetProvider _createWidgetProvider() => new _LazyWidgetProvider(); @@ -219,7 +255,7 @@ class _LazyWidgetProvider extends _WidgetProvider { int _base; List _widgets; - void didUpdateWidget(VirtualViewportLazyMixin oldWidget, VirtualViewportLazyMixin newWidget) { + void didUpdateWidget(VirtualViewportFromBuilder oldWidget, VirtualViewportFromBuilder newWidget) { if (_length != newWidget.itemCount || oldWidget?.itemBuilder != newWidget.itemBuilder) { _length = newWidget.itemCount; _base = null; @@ -232,7 +268,7 @@ class _LazyWidgetProvider extends _WidgetProvider { void prepareChildren(VirtualViewportElement context, int base, int count) { if (_widgets != null && _widgets.length == count && _base == base) return; - VirtualViewportLazyMixin widget = context.widget; + VirtualViewportFromBuilder widget = context.widget; _base = base; _widgets = widget.itemBuilder(context, base, count); }