Commit 1773e47b authored by Adam Barth's avatar Adam Barth Committed by GitHub

Remove a number of old scrolling widgets: (#8212)

- ScrollableList
 - ScrollableLazyList
 - LazyBlock
 - MaterialList

Clients should use ListView instead.
parent f93daa4f
...@@ -45,8 +45,7 @@ class OverscrollDemoState extends State<OverscrollDemo> { ...@@ -45,8 +45,7 @@ class OverscrollDemoState extends State<OverscrollDemo> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget body = new MaterialList( // ignore: DEPRECATED_MEMBER_USE Widget body = new Block( // ignore: DEPRECATED_MEMBER_USE
type: MaterialListType.threeLine,
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
scrollableKey: _scrollableKey, scrollableKey: _scrollableKey,
children: _items.map((String item) { children: _items.map((String item) {
...@@ -56,7 +55,7 @@ class OverscrollDemoState extends State<OverscrollDemo> { ...@@ -56,7 +55,7 @@ class OverscrollDemoState extends State<OverscrollDemo> {
title: new Text('This item represents $item.'), title: new Text('This item represents $item.'),
subtitle: new Text('Even more additional list item information appears on line three.') subtitle: new Text('Even more additional list item information appears on line three.')
); );
}) }).toList(),
); );
String indicatorTypeText; String indicatorTypeText;
......
...@@ -53,7 +53,6 @@ export 'src/material/ink_highlight.dart'; ...@@ -53,7 +53,6 @@ export 'src/material/ink_highlight.dart';
export 'src/material/ink_splash.dart'; export 'src/material/ink_splash.dart';
export 'src/material/ink_well.dart'; export 'src/material/ink_well.dart';
export 'src/material/input.dart'; export 'src/material/input.dart';
export 'src/material/list.dart';
export 'src/material/list_item.dart'; export 'src/material/list_item.dart';
export 'src/material/material.dart'; export 'src/material/material.dart';
export 'src/material/mergeable_material.dart'; export 'src/material/mergeable_material.dart';
......
...@@ -34,7 +34,6 @@ export 'src/rendering/flex.dart'; ...@@ -34,7 +34,6 @@ export 'src/rendering/flex.dart';
export 'src/rendering/flow.dart'; export 'src/rendering/flow.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/paragraph.dart'; export 'src/rendering/paragraph.dart';
......
// 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 'package:flutter/widgets.dart';
/// The kind of list items contained in a material design list.
///
/// See also:
///
/// * [ListItem]
/// * [kListItemExtent]
/// * <https://material.google.com/components/lists.html#lists-specs>
enum MaterialListType {
/// A list item that contains a single line of text.
oneLine,
/// A list item that contains a [CircleAvatar] followed by a single line of text.
oneLineWithAvatar,
/// A list item that contains two lines of text.
twoLine,
/// A list item that contains three lines of text.
threeLine
}
/// The vertical extent of the different types of material list items.
///
/// See also:
///
/// * [MaterialListType]
/// * [ListItem]
/// * [kListItemExtent]
/// * <https://material.google.com/components/lists.html#lists-specs>
Map<MaterialListType, double> kListItemExtent = const <MaterialListType, double>{
MaterialListType.oneLine: 48.0,
MaterialListType.oneLineWithAvatar: 56.0,
MaterialListType.twoLine: 72.0,
MaterialListType.threeLine: 88.0,
};
/// A scrollable list containing material list items.
///
/// Material list configures a [ScrollableList] with a number of default values
/// to match material design.
///
/// See also:
///
/// * [SliverList], which shows heterogeneous widgets in a list and makes the
/// list scrollable if necessary.
/// * [ListItem], to show content in a [MaterialList] using material design
/// conventions.
/// * [ScrollableList], on which this widget is based.
/// * [TwoLevelList], for lists that have subsections that can collapse and
/// expand.
/// * [GridView]
/// * <https://material.google.com/components/lists.html>
@Deprecated('use ListView instead')
class MaterialList extends StatelessWidget {
/// Creates a material list.
///
/// By default, has a type of [MaterialListType.twoLine].
MaterialList({
Key key,
this.initialScrollOffset,
this.onScrollStart,
this.onScroll,
this.onScrollEnd,
this.type: MaterialListType.twoLine,
this.children: const <Widget>[],
this.padding: EdgeInsets.zero,
this.scrollableKey
}) : super(key: key);
/// The scroll offset this widget should use when first created.
final double initialScrollOffset;
/// Called whenever this widget starts to scroll.
final ScrollListener onScrollStart;
/// Called whenever this widget's scroll offset changes.
final ScrollListener onScroll;
/// Called whenever this widget stops scrolling.
final ScrollListener onScrollEnd;
/// The kind of [ListItem] contained in this list.
final MaterialListType type;
/// The widgets to display in this list.
final Iterable<Widget> children;
/// The amount of space by which to inset the children inside the viewport.
final EdgeInsets padding;
/// The key to use for the underlying scrollable widget.
final Key scrollableKey;
@override
Widget build(BuildContext context) {
return new ScrollableList(
scrollableKey: scrollableKey,
initialScrollOffset: initialScrollOffset,
scrollDirection: Axis.vertical,
onScrollStart: onScrollStart,
onScroll: onScroll,
onScrollEnd: onScrollEnd,
itemExtent: kListItemExtent[type],
padding: const EdgeInsets.symmetric(vertical: 8.0) + padding,
children: children,
);
}
}
...@@ -10,6 +10,42 @@ import 'debug.dart'; ...@@ -10,6 +10,42 @@ import 'debug.dart';
import 'ink_well.dart'; import 'ink_well.dart';
import 'theme.dart'; import 'theme.dart';
/// The kind of list items contained in a material design list.
///
/// See also:
///
/// * [ListItem]
/// * [kListItemExtent]
/// * <https://material.google.com/components/lists.html#lists-specs>
enum MaterialListType {
/// A list item that contains a single line of text.
oneLine,
/// A list item that contains a [CircleAvatar] followed by a single line of text.
oneLineWithAvatar,
/// A list item that contains two lines of text.
twoLine,
/// A list item that contains three lines of text.
threeLine
}
/// The vertical extent of the different types of material list items.
///
/// See also:
///
/// * [MaterialListType]
/// * [ListItem]
/// * [kListItemExtent]
/// * <https://material.google.com/components/lists.html#lists-specs>
Map<MaterialListType, double> kListItemExtent = const <MaterialListType, double>{
MaterialListType.oneLine: 48.0,
MaterialListType.oneLineWithAvatar: 56.0,
MaterialListType.twoLine: 72.0,
MaterialListType.threeLine: 88.0,
};
/// A single row typically containing an icon and some text. /// A single row typically containing an icon and some text.
/// ///
/// List items are one to three lines of text optionally flanked by icons or /// List items are one to three lines of text optionally flanked by icons or
...@@ -32,7 +68,7 @@ import 'theme.dart'; ...@@ -32,7 +68,7 @@ import 'theme.dart';
/// ///
/// See also: /// See also:
/// ///
/// * [MaterialList], which takes a list of [ListItem] widgets and shows them /// * [ListView], which takes a list of [ListItem] widgets and shows them
/// as a scrolling list. /// as a scrolling list.
/// * [Card], which can be used with [Column] to show a few [ListItem]s. /// * [Card], which can be used with [Column] to show a few [ListItem]s.
/// * [CircleAvatar], which shows an icon representing a person. /// * [CircleAvatar], which shows an icon representing a person.
......
...@@ -10,7 +10,6 @@ import 'icon.dart'; ...@@ -10,7 +10,6 @@ import 'icon.dart';
import 'icons.dart'; import 'icons.dart';
import 'icon_theme.dart'; import 'icon_theme.dart';
import 'icon_theme_data.dart'; import 'icon_theme_data.dart';
import 'list.dart';
import 'list_item.dart'; import 'list_item.dart';
import 'theme.dart'; import 'theme.dart';
import 'theme_data.dart'; import 'theme_data.dart';
......
// 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 'dart:math' as math;
import 'box.dart';
import 'object.dart';
import 'viewport.dart';
/// Parent data for use with [RenderList].
class ListParentData extends ContainerBoxParentDataMixin<RenderBox> { }
/// A linear layout of children intended for use as a virtual viewport.
///
/// Children are layout out in order along the main axis. If [itemExtent] is
/// non-null, each child is required to have exactly [itemExtent] extent in the
/// main axis. If [itemExtent] is null, each child is required to have the same
/// extent in the main axis as the list itself.
///
/// In the cross axis, the render list expands to fill the available space and
/// each child is required to have the same extent in the cross axis as the list
/// itself.
class RenderList extends RenderVirtualViewport<ListParentData> {
/// Creates a render list.
///
/// By default, the list is oriented vertically and anchored at the start.
RenderList({
List<RenderBox> children,
double itemExtent,
EdgeInsets padding,
int virtualChildCount,
Offset paintOffset: Offset.zero,
Axis mainAxis: Axis.vertical,
ViewportAnchor anchor: ViewportAnchor.start,
LayoutCallback<BoxConstraints> callback
}) : _itemExtent = itemExtent,
_padding = padding,
super(
virtualChildCount: virtualChildCount,
paintOffset: paintOffset,
mainAxis: mainAxis,
anchor: anchor,
callback: callback
) {
addAll(children);
}
/// The main-axis extent of each item in the list.
///
/// If [itemExtent] is null, the items are required to match the main-axis
/// extent of the list itself.
double get itemExtent => _itemExtent;
double _itemExtent;
set itemExtent (double newValue) {
if (_itemExtent == newValue)
return;
_itemExtent = newValue;
markNeedsLayout();
}
/// The amount of space by which to inset the children inside the list.
EdgeInsets get padding => _padding;
EdgeInsets _padding;
set padding (EdgeInsets newValue) {
if (_padding == newValue)
return;
_padding = newValue;
markNeedsLayout();
}
@override
void setupParentData(RenderBox child) {
if (child.parentData is! ListParentData)
child.parentData = new ListParentData();
}
double get _preferredExtent {
if (itemExtent == null)
return double.INFINITY;
final int count = virtualChildCount;
if (count == null)
return double.INFINITY;
double extent = itemExtent * count;
if (padding != null)
extent += padding.along(mainAxis);
return extent;
}
double _computeIntrinsicWidth() {
switch (mainAxis) {
case Axis.vertical:
assert(debugThrowIfNotCheckingIntrinsics());
return 0.0;
case Axis.horizontal:
final double width = _preferredExtent;
if (width.isFinite)
return width;
assert(debugThrowIfNotCheckingIntrinsics());
return 0.0;
}
assert(mainAxis != null);
return null;
}
@override
double computeMinIntrinsicWidth(double height) {
return _computeIntrinsicWidth();
}
@override
double computeMaxIntrinsicWidth(double height) {
return _computeIntrinsicWidth();
}
double _computeIntrinsicHeight() {
switch (mainAxis) {
case Axis.vertical:
final double height = _preferredExtent;
if (height.isFinite)
return height;
assert(debugThrowIfNotCheckingIntrinsics());
return 0.0;
case Axis.horizontal:
assert(debugThrowIfNotCheckingIntrinsics());
return 0.0;
}
assert(mainAxis != null);
return null;
}
@override
double computeMinIntrinsicHeight(double width) {
return _computeIntrinsicHeight();
}
@override
double computeMaxIntrinsicHeight(double width) {
return _computeIntrinsicHeight();
}
@override
void performLayout() {
switch (mainAxis) {
case Axis.vertical:
size = new Size(constraints.maxWidth,
constraints.constrainHeight(_preferredExtent));
break;
case Axis.horizontal:
size = new Size(constraints.constrainWidth(_preferredExtent),
constraints.maxHeight);
break;
}
if (callback != null)
invokeLayoutCallback<BoxConstraints>(callback);
double itemWidth;
double itemHeight;
double x = 0.0;
double dx = 0.0;
double y = 0.0;
double dy = 0.0;
switch (mainAxis) {
case Axis.vertical:
itemWidth = math.max(0.0, size.width - (padding == null ? 0.0 : padding.horizontal));
itemHeight = itemExtent ?? size.height;
x = padding != null ? padding.left : 0.0;
dy = itemHeight;
break;
case Axis.horizontal:
itemWidth = itemExtent ?? size.width;
itemHeight = math.max(0.0, size.height - (padding == null ? 0.0 : padding.vertical));
y = padding != null ? padding.top : 0.0;
dx = itemWidth;
break;
}
BoxConstraints innerConstraints =
new BoxConstraints.tightFor(width: itemWidth, height: itemHeight);
RenderBox child = firstChild;
while (child != null) {
child.layout(innerConstraints);
final ListParentData childParentData = child.parentData;
childParentData.offset = new Offset(x, y);
x += dx;
y += dy;
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
}
}
...@@ -1452,12 +1452,6 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1452,12 +1452,6 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
}); });
return result; return result;
} }
@Deprecated(
'If you are using needsLayout for an assert, switch to debugNeedsLayout. '
'If you are using it for actual runtime logic, please contact the Flutter '
'team to let us know what your use case is. We intend to remove this getter.'
)
bool get needsLayout => _needsLayout;
bool _needsLayout = true; bool _needsLayout = true;
RenderObject _relayoutBoundary; RenderObject _relayoutBoundary;
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
import 'dart:ui' as ui show window; import 'dart:ui' as ui show window;
import 'package:flutter/foundation.dart';
import 'package:vector_math/vector_math_64.dart'; import 'package:vector_math/vector_math_64.dart';
import 'box.dart'; import 'box.dart';
...@@ -337,132 +336,3 @@ class RenderViewport extends RenderViewportBase with RenderObjectWithChildMixin< ...@@ -337,132 +336,3 @@ class RenderViewport extends RenderViewportBase with RenderObjectWithChildMixin<
return false; return false;
} }
} }
/// A render object that shows a subset of its children.
///
/// The children of a viewport can layout to a larger size along the viewport's
/// [mainAxis] than the viewport itself. If that happens, only a subset of the
/// children will be visible through the viewport. The subset of children that
/// are visible can be controlled with the [paintOffset].
///
/// See also:
///
/// * [RenderList] (which arranges its children linearly)
/// * [RenderGrid] (which arranges its children into tiles)
/// * [RenderViewport] (which is easier to use with a single child)
abstract class RenderVirtualViewport<T extends ContainerBoxParentDataMixin<RenderBox>>
extends RenderViewportBase with ContainerRenderObjectMixin<RenderBox, T>,
RenderBoxContainerDefaultsMixin<RenderBox, T> {
/// Initializes fields for subclasses.
///
/// The [paintOffset] and [mainAxis] arguments must not be null.
RenderVirtualViewport({
int virtualChildCount,
LayoutCallback<BoxConstraints> callback,
Offset paintOffset: Offset.zero,
Axis mainAxis: Axis.vertical,
ViewportAnchor anchor: ViewportAnchor.start
}) : _virtualChildCount = virtualChildCount,
_callback = callback,
super(paintOffset, mainAxis, anchor);
/// The overall number of children this viewport could potentially display.
///
/// If null, the viewport might display an unbounded number of children.
int get virtualChildCount => _virtualChildCount;
int _virtualChildCount;
set virtualChildCount(int value) {
if (_virtualChildCount == value)
return;
_virtualChildCount = value;
markNeedsLayout();
}
/// Called during [layout] to determine the render object's children.
///
/// Typically the callback will mutate the child list appropriately, for
/// example so the child list contains only visible children.
LayoutCallback<BoxConstraints> get callback => _callback;
LayoutCallback<BoxConstraints> _callback;
set callback(LayoutCallback<BoxConstraints> value) {
if (value == _callback)
return;
_callback = value;
markNeedsLayout();
}
/// Throws an exception if asserts are enabled, unless the
/// [RenderObject.debugCheckingIntrinsics] flag is set.
///
/// This is a convenience function for subclasses to call from their
/// intrinsic-sizing functions if they don't have a good way to generate the
/// numbers.
@protected
bool debugThrowIfNotCheckingIntrinsics() {
assert(() {
if (!RenderObject.debugCheckingIntrinsics) {
throw new FlutterError(
'$runtimeType does not support returning intrinsic dimensions.\n'
'Calculating the intrinsic dimensions would require walking the entire '
'child list, which cannot reliably and efficiently be done for render '
'objects that potentially generate their child list during layout.'
);
}
return true;
});
return true;
}
@override
double computeMinIntrinsicWidth(double height) {
assert(debugThrowIfNotCheckingIntrinsics());
return 0.0;
}
@override
double computeMaxIntrinsicWidth(double height) {
assert(debugThrowIfNotCheckingIntrinsics());
return 0.0;
}
@override
double computeMinIntrinsicHeight(double width) {
assert(debugThrowIfNotCheckingIntrinsics());
return 0.0;
}
@override
double computeMaxIntrinsicHeight(double width) {
assert(debugThrowIfNotCheckingIntrinsics());
return 0.0;
}
@override
bool hitTestChildren(HitTestResult result, { Point position }) {
return defaultHitTestChildren(result, position: position + -_effectivePaintOffset);
}
void _paintContents(PaintingContext context, Offset offset) {
defaultPaint(context, offset + _effectivePaintOffset);
}
@override
void paint(PaintingContext context, Offset offset) {
context.pushClipRect(needsCompositing, offset, Point.origin & size, _paintContents);
}
@override
Rect describeApproximatePaintClip(RenderObject child) => Point.origin & size;
// Workaround for https://github.com/dart-lang/sdk/issues/25232
@override
void applyPaintTransform(RenderBox child, Matrix4 transform) {
super.applyPaintTransform(child, transform);
}
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('virtual child count: $virtualChildCount');
}
}
// Copyright 2016 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.
////////////////////////////////////////////////////////////////////////////////
// DELETE THIS FILE WHEN REMOVING LEGACY SCROLLING CODE
////////////////////////////////////////////////////////////////////////////////
import 'dart:math' as math;
import 'package:flutter/rendering.dart';
import 'package:flutter/foundation.dart';
import 'basic.dart';
import 'framework.dart';
import 'scroll_configuration.dart';
import 'scrollable.dart';
import 'scrollable_list.dart';
import 'scroll_behavior.dart';
/// Provides children for [LazyBlock] or [LazyBlockViewport].
///
/// See also [LazyBlockBuilder] for an implementation of LazyBlockDelegate based
/// on an [IndexedWidgetBuilder] closure.
@Deprecated('use SliverChildDelegate instead')
abstract class LazyBlockDelegate {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const 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 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(@checked LazyBlockDelegate oldDelegate);
/// Returns the estimated total height of the children, in pixels.
///
/// If there's an infinite number of children, this should return
/// [double.INFINITY].
///
/// The provided values can be used to estimate the total extent.
///
/// The `firstIndex` and `lastIndex` values give the integers that were passed
/// to [buildItem] to build the respective widgets.
///
/// The `minOffset` is the offset of the widget with index 0. Unless the
/// `firstIndex` is 0, the `minOffset` is only itself an estimate.
///
/// The `firstStartOffset` is the offset of the widget with `firstIndex`, in
/// the same coordinate space as `minOffset`.
///
/// The `lastEndOffset` is the offset of the widget that would be after
/// `lastIndex`, in the same coordinate space as `minOffset`. (In other words,
/// it's the offset to the end of the `lastIndex` widget.)
///
/// A simple algorithm for this function, which works well when there are many
/// children, the exact child count is known, and the children near the top of
/// the list are more or less representative of the length of the other
/// children, is the following:
///
/// ```dart
/// // childCount is the number of children
/// return (lastEndOffset - minOffset) * childCount / (lastIndex + 1);
/// ```
double estimateTotalExtent(int firstIndex, int lastIndex, double minOffset, double firstStartOffset, double lastEndOffset);
}
/// Signature for callbacks that estimate the total height of a `LazyBlock`'s contents.
///
/// See `LazyBlockDelegate.estimateTotalExtent` for details.
@deprecated
typedef double TotalExtentEstimator(int firstIndex, int lastIndex, double minOffset, double firstStartOffset, double lastEndOffset);
/// Uses an [IndexedWidgetBuilder] to provide children for [LazyBlock].
///
/// A LazyBlockBuilder rebuilds the children whenever the [LazyBlock] is
/// rebuilt, similar to the behavior of [Builder].
///
/// To use a [Scrollbar] with this delegate, you must provide an
/// [estimateTotalExtent] callback.
///
/// See also [LazyBlockViewport].
@Deprecated('use SliverChildBuilderDelegate instead')
class LazyBlockBuilder extends LazyBlockDelegate {
/// Creates a LazyBlockBuilder based on the given builder.
LazyBlockBuilder({ this.builder, this.totalExtentEstimator }) {
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. 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 IndexedWidgetBuilder builder;
/// Returns the estimated total height of the children, in pixels.
///
/// If null, the estimate will be infinite, even if a null child has been
/// returned by [builder].
///
/// See [LazyBlockDelegate.estimateTotalExtent] for details.
final TotalExtentEstimator totalExtentEstimator;
@override
Widget buildItem(BuildContext context, int index) => builder(context, index);
@override
bool shouldRebuild(LazyBlockDelegate oldDelegate) => true;
@override
double estimateTotalExtent(int firstIndex, int lastIndex, double minOffset, double firstStartOffset, double lastEndOffset) {
if (totalExtentEstimator != null)
return totalExtentEstimator(firstIndex, lastIndex, minOffset, firstStartOffset, lastEndOffset);
return double.INFINITY;
}
}
/// Uses a [List<Widget>] to provide children for [LazyBlock].
///
/// See also [LazyBlockViewport].
@Deprecated('use SliverChildListDelegate instead')
class LazyBlockChildren extends LazyBlockDelegate {
/// Creates a LazyBlockChildren that displays the given children.
///
/// The list of children must not be modified after being passed to this
/// constructor.
LazyBlockChildren({ this.children: const <Widget>[] }) {
assert(children != null);
}
/// The widgets to display.
///
/// This list must not be modified after being stored in this field.
final List<Widget> children;
@override
Widget buildItem(BuildContext context, int index) {
assert(index >= 0);
return index < children.length ? children[index] : null;
}
@override
bool shouldRebuild(LazyBlockChildren oldDelegate) {
return children != oldDelegate.children;
}
@override
double estimateTotalExtent(int firstIndex, int lastIndex, double minOffset, double firstStartOffset, double lastEndOffset) {
final int childCount = children.length;
if (childCount == 0)
return 0.0;
return (lastEndOffset - minOffset) * childCount / (lastIndex + 1);
}
}
/// An infinite scrolling list of variably-sized 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 [ScrollableLazyList] when all the children have the same size because
/// it can use that property to be more efficient. Prefer [ScrollableViewport]
/// when there is only one child.
///
/// Consider [Block] if you have a small number of children that will only
/// scroll in unusual circumstances (e.g. when the user's device is smaller than
/// expected).
@Deprecated('use ListView instead')
class LazyBlock extends StatelessWidget {
/// Creates an infinite scrolling list of variable height children.
///
/// The [delegate] argument must not be null.
LazyBlock({
Key key,
@required this.delegate,
this.initialScrollOffset,
this.scrollDirection: Axis.vertical,
this.onScrollStart,
this.onScroll,
this.onScrollEnd,
this.snapOffsetCallback,
this.scrollableKey,
this.padding
}) : super(key: key) {
assert(delegate != null);
}
// Warning: keep the dartdoc comments that follow in sync with the copies in
// Scrollable, ScrollableViewport, ScrollableList, and
// ScrollableLazyList. And see: https://github.com/dart-lang/dartdoc/issues/1161.
/// The scroll offset this widget should use when first created.
final double initialScrollOffset;
/// The axis along which this widget should scroll.
final Axis scrollDirection;
/// Called whenever this widget starts to scroll.
final ScrollListener onScrollStart;
/// Called whenever this widget's scroll offset changes.
final ScrollListener onScroll;
/// Called whenever this widget stops scrolling.
final ScrollListener onScrollEnd;
/// Called to determine the offset to which scrolling should snap,
/// when handling a fling.
///
/// This callback, if set, will be called with the offset that the
/// Scrollable would have scrolled to in the absence of this
/// callback, and a Size describing the size of the Scrollable
/// itself.
///
/// The callback's return value is used as the new scroll offset to
/// aim for.
///
/// If the callback simply returns its first argument (the offset),
/// then it is as if the callback was null.
final SnapOffsetCallback snapOffsetCallback;
/// The key for the Scrollable created by this widget.
final Key scrollableKey;
/// The amount of space by which to inset the children inside the viewport.
final EdgeInsets padding;
/// Provides children for this widget.
///
/// See [LazyBlockDelegate] for details.
final LazyBlockDelegate delegate;
Widget _buildViewport(BuildContext context, ScrollableState state) {
return new LazyBlockViewport(
startOffset: state.scrollOffset,
mainAxis: scrollDirection,
padding: padding,
onExtentsChanged: (int firstIndex, int lastIndex, double firstStartOffset, double lastEndOffset, double minScrollOffset, double containerExtent) {
final BoundedBehavior scrollBehavior = state.scrollBehavior;
state.didUpdateScrollBehavior(scrollBehavior.updateExtents(
contentExtent: delegate.estimateTotalExtent(firstIndex, lastIndex, minScrollOffset, firstStartOffset, lastEndOffset),
containerExtent: containerExtent,
minScrollOffset: minScrollOffset,
scrollOffset: state.scrollOffset
));
state.updateGestureDetector();
},
delegate: delegate
);
}
@override
Widget build(BuildContext context) {
final Widget result = new Scrollable(
key: scrollableKey,
initialScrollOffset: initialScrollOffset,
scrollDirection: scrollDirection,
onScrollStart: onScrollStart,
onScroll: onScroll,
onScrollEnd: onScrollEnd,
snapOffsetCallback: snapOffsetCallback,
builder: _buildViewport
);
return ScrollConfiguration.wrap(context, result);
}
}
/// Signature used by `LazyBlockViewport` to report its interior and exterior dimensions.
///
/// * The `firstIndex` is the index of the child that is visible at the
/// starting edge of the viewport.
/// * The `lastIndex` is the index of the child that is visible at the ending
/// edge of the viewport. This could be the same as the `firstIndex` if the
/// child is bigger than the viewport or if it is the last child.
/// * The `firstStartOffset` is the offset of the starting edge of the child
/// with index `firstIndex`.
/// * The `lastEndOffset` is the offset of the ending edge of the child with
/// index `lastIndex`.
/// * The `minScrollOffset` is the offset at which the starting edge of the
/// first item in the viewport is aligned with the starting edge of the
/// viewport. (As the scroll offset increases, items with larger indices are
/// revealed in the viewport.) Typically the min scroll offset is 0.0, but
/// because [LazyBlockViewport] uses dead reckoning, the min scroll offset
/// might not always be 0.0. For example, if an item that's offscreen changes
/// size, the visible items will retain their current scroll offsets even if
/// the distance to the starting edge of the first item changes.
/// * The `containerExtent` is the exterior dimension of the viewport (i.e.,
/// the amount of the thing inside the viewport that is visible from outside
/// the viewport).
@deprecated
typedef void LazyBlockExtentsChangedCallback(int firstIndex, int lastIndex, double firstStartOffset, double lastEndOffset, double minScrollOffset, double containerExtent);
/// 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].
@deprecated
class LazyBlockViewport extends RenderObjectWidget {
/// Creates a viewport on an infinite list of variable height children.
///
/// The [delegate] argument must not be null.
LazyBlockViewport({
Key key,
@required this.delegate,
this.startOffset: 0.0,
this.mainAxis: Axis.vertical,
this.padding,
this.onExtentsChanged
}) : 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;
/// The direction in which the children are permitted to be larger than the viewport.
///
/// The children are given layout constraints that are fully unconstrained
/// along the main axis (e.g., children can be as tall as they want if the
/// main axis is vertical).
final Axis mainAxis;
/// The amount of space by which to inset the children inside the viewport.
final EdgeInsets padding;
/// 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
_LazyBlockElement createElement() => new _LazyBlockElement(this);
@override
_RenderLazyBlock createRenderObject(BuildContext context) => new _RenderLazyBlock();
}
@deprecated
class _LazyBlockParentData extends ContainerBoxParentDataMixin<RenderBox> { }
@deprecated
class _RenderLazyBlock extends RenderVirtualViewport<_LazyBlockParentData> {
_RenderLazyBlock({
Offset paintOffset: Offset.zero,
Axis mainAxis: Axis.vertical,
LayoutCallback<BoxConstraints> callback
}) : super(
paintOffset: paintOffset,
mainAxis: mainAxis,
callback: callback
);
@override
void setupParentData(RenderBox child) {
if (child.parentData is! _LazyBlockParentData)
child.parentData = new _LazyBlockParentData();
}
bool _debugThrowIfNotCheckingIntrinsics() {
assert(() {
if (!RenderObject.debugCheckingIntrinsics) {
throw new FlutterError(
'LazyBlockViewport does not support returning intrinsic dimensions.\n'
'Calculating the intrinsic dimensions would require walking the entire '
'child list, which defeats the entire point of having a lazily-built '
'list of children.'
);
}
return true;
});
return true;
}
@override
double computeMinIntrinsicWidth(double height) {
assert(_debugThrowIfNotCheckingIntrinsics());
return 0.0;
}
@override
double computeMaxIntrinsicWidth(double height) {
assert(_debugThrowIfNotCheckingIntrinsics());
return 0.0;
}
@override
double computeMinIntrinsicHeight(double width) {
assert(_debugThrowIfNotCheckingIntrinsics());
return 0.0;
}
@override
double computeMaxIntrinsicHeight(double width) {
assert(_debugThrowIfNotCheckingIntrinsics());
return 0.0;
}
@override
bool get sizedByParent => true;
@override
bool get isRepaintBoundary => true;
@override
void performResize() {
size = constraints.biggest;
}
@override
void performLayout() {
if (callback != null)
invokeLayoutCallback(callback);
}
}
@deprecated
class _LazyBlockElement extends RenderObjectElement {
_LazyBlockElement(LazyBlockViewport widget) : super(widget);
@override
LazyBlockViewport get widget => super.widget;
@override
_RenderLazyBlock get renderObject => super.renderObject;
/// The offset of the top of the first item represented in _children from the top of the item with logical index zero.
double _firstChildLogicalOffset = 0.0;
/// The logical index of the first item represented in _children.
int _firstChildLogicalIndex = 0;
/// The explicitly represented items.
List<Element> _children = <Element>[];
/// The minimum scroll offset used by the scroll behavior.
///
/// Not all the items between the minimum and maximum scroll offsets are
/// reprsented explicitly in _children.
double _minScrollOffset = 0.0;
/// The smallest start offset (inclusive) that can be displayed properly with the items currently represented in [_children].
double _startOffsetLowerLimit = 0.0;
/// The largest start offset (exclusive) that can be displayed properly with the items currently represented in [_children].
double _startOffsetUpperLimit = 0.0;
/// True if the children don't fill the viewport.
bool _underflow = false;
int _lastReportedFirstChildLogicalIndex;
int _lastReportedLastChildLogicalIndex;
double _lastReportedFirstChildLogicalOffset;
double _lastReportedLastChildLogicalOffset;
double _lastReportedMinScrollOffset;
double _lastReportedContainerExtent;
@override
void visitChildren(ElementVisitor visitor) {
for (Element child in _children)
visitor(child);
}
@override
void forgetChild(Element child) {
assert(() {
// TODO(ianh): implement forgetChild for LazyBlock
throw new FlutterError(
'LazyBlock does not yet support GlobalKey reparenting of its children.\n'
'As a temporary workaround, wrap the child with the GlobalKey in a '
'Container or other harmless child.'
);
});
}
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
renderObject
..callback = _layout
..mainAxis = widget.mainAxis;
// Children will get built during layout.
// Paint offset will get updated during layout.
}
@override
void update(LazyBlockViewport newWidget) {
LazyBlockViewport oldWidget = widget;
super.update(newWidget);
renderObject.mainAxis = widget.mainAxis;
LazyBlockDelegate newDelegate = newWidget.delegate;
LazyBlockDelegate oldDelegate = oldWidget.delegate;
if (newDelegate != oldDelegate && (newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRebuild(oldDelegate)))
performRebuild();
// If the new start offset can be displayed properly with the items
// currently represented in _children, we just need to update the paint
// offset. Otherwise, we need to trigger a layout in order to change the
// set of explicitly represented children.
double startOffset = widget.startOffset;
if (startOffset >= _startOffsetLowerLimit &&
startOffset < _startOffsetUpperLimit &&
newWidget.padding == oldWidget.padding) {
_updatePaintOffset();
} else {
renderObject.markNeedsLayout();
}
}
@override
void unmount() {
renderObject.callback = null;
super.unmount();
}
Widget _callBuilder(IndexedWidgetBuilder builder, int index, { bool requireNonNull: false }) {
Widget result;
try {
result = builder(this, index);
if (requireNonNull && result == null) {
throw new FlutterError(
'buildItem must not return null after returning non-null.\n'
'If buildItem for a LazyBlockDelegate returns a non-null widget for a given '
'index, it must return non-null widgets for every smaller index as well. The '
'buildItem function for ${widget.delegate.runtimeType} returned null for '
'index $index after having returned a non-null value for index '
'${index - 1}.'
);
}
} catch (e, stack) {
FlutterError.reportError(new FlutterErrorDetails(
exception: e,
stack: stack,
library: 'widgets library',
context: 'while building items for a LazyBlock',
informationCollector: (StringBuffer information) {
information.writeln('The LazyBlock in question was:\n $this');
information.writeln('The delegate that was being used was:\n ${widget.delegate}');
information.write('The index of the offending child widget was: $index');
}
));
result = new ErrorWidget(e);
}
return result;
}
@override
void performRebuild() {
IndexedWidgetBuilder builder = widget.delegate.buildItem;
List<Widget> widgets = <Widget>[];
// If the most recent layout didn't fill the viewport but an additional child
// is now available, add it to the widgets list which will force a layout.
int buildChildCount = _underflow ? _children.length + 1 : _children.length;
for (int i = 0; i < buildChildCount; ++i) {
int logicalIndex = _firstChildLogicalIndex + i;
Widget childWidget = _callBuilder(builder, logicalIndex);
if (childWidget == null)
break;
widgets.add(new RepaintBoundary.wrap(childWidget, logicalIndex));
}
_children = new List<Element>.from(updateChildren(_children, widgets));
super.performRebuild();
}
void _layout(BoxConstraints constraints) {
final double blockExtent = _getMainAxisExtent(renderObject.size);
final IndexedWidgetBuilder builder = widget.delegate.buildItem;
final double startLogicalOffset = widget.startOffset;
final double endLogicalOffset = startLogicalOffset + blockExtent;
final _RenderLazyBlock block = renderObject;
final BoxConstraints innerConstraints = _getInnerConstraints(constraints);
// A high watermark for which children have been through layout this pass.
int firstLogicalIndexNeedingLayout = _firstChildLogicalIndex;
// The index of the current child we're examining. The index is the same one
// used for the builder (as opposed to the physical index in the _children
// list).
int currentLogicalIndex = _firstChildLogicalIndex;
// The offset of the current child we're examining from the start of the
// entire block (in the direction of the main axis). As we compute layout
// information, we use dead reckoning to keep track of where all the
// children are based on this quantity.
double currentLogicalOffset = _firstChildLogicalOffset;
// First, we check if we need to inflate any children before the start of
// the viewport. Because we're dead reckoning from the current viewport, we
// inflate the children in reverse tree order.
if (currentLogicalIndex > 0 && currentLogicalOffset > startLogicalOffset) {
final List<Element> newChildren = <Element>[];
while (currentLogicalIndex > 0 && currentLogicalOffset > startLogicalOffset) {
currentLogicalIndex -= 1;
Element newElement;
owner.buildScope(this, () {
Widget newWidget = _callBuilder(builder, currentLogicalIndex, requireNonNull: true);
newWidget = new RepaintBoundary.wrap(newWidget, currentLogicalIndex);
newElement = inflateWidget(newWidget, null);
});
newChildren.add(newElement);
RenderBox child = block.firstChild;
assert(child == newChildren.last.renderObject);
child.layout(innerConstraints, parentUsesSize: true);
currentLogicalOffset -= _getMainAxisExtent(child.size);
}
final int numberOfNewChildren = newChildren.length;
_children.insertAll(0, newChildren.reversed);
_firstChildLogicalIndex = currentLogicalIndex;
_firstChildLogicalOffset = currentLogicalOffset;
firstLogicalIndexNeedingLayout = currentLogicalIndex + numberOfNewChildren;
} else if (currentLogicalOffset < startLogicalOffset) {
// If we didn't need to inflate more children before the viewport, we
// might need to deactivate children that have left the viewport from the
// top. We repeatedly check whether the first child overlaps the viewport
// and deactivate it if it's outside the viewport.
int currentPhysicalIndex = 0;
while (block.firstChild != null) {
RenderBox child = block.firstChild;
child.layout(innerConstraints, parentUsesSize: true);
firstLogicalIndexNeedingLayout += 1;
double childExtent = _getMainAxisExtent(child.size);
if (currentLogicalOffset + childExtent >= startLogicalOffset)
break;
deactivateChild(_children[currentPhysicalIndex]);
_children[currentPhysicalIndex] = null;
currentPhysicalIndex += 1;
currentLogicalIndex += 1;
currentLogicalOffset += childExtent;
}
if (currentPhysicalIndex > 0) {
_children.removeRange(0, currentPhysicalIndex);
_firstChildLogicalIndex = currentLogicalIndex;
_firstChildLogicalOffset = currentLogicalOffset;
}
}
// We've now established the invariant that the first physical child in the
// block is the first child that ought to be visible in the viewport. Now we
// need to walk forward until we've filled up the viewport. We might have
// already called layout for some of the children we encounter in this phase
// of the algorithm, we we'll need to be careful not to call layout on them again.
if (currentLogicalOffset >= startLogicalOffset) {
// The first element is visible. We need to update our reckoning of where
// the min scroll offset is.
_startOffsetLowerLimit = double.NEGATIVE_INFINITY;
} else {
// The first element is not visible. Ensure that we have one blockExtent
// of headroom so we don't hit the min scroll offset prematurely.
_startOffsetLowerLimit = currentLogicalOffset;
}
// Materialize new children until we fill the viewport or run out of
// children to materialize. If we run out then _underflow is true.
RenderBox child;
while (currentLogicalOffset < endLogicalOffset) {
int physicalIndex = currentLogicalIndex - _firstChildLogicalIndex;
if (physicalIndex >= _children.length) {
assert(physicalIndex == _children.length);
Element newElement;
owner.buildScope(this, () {
Widget newWidget = _callBuilder(builder, currentLogicalIndex);
if (newWidget == null)
return;
newWidget = new RepaintBoundary.wrap(newWidget, currentLogicalIndex);
Element previousChild = _children.isEmpty ? null : _children.last;
newElement = inflateWidget(newWidget, previousChild);
});
if (newElement == null)
break;
_children.add(newElement);
}
child = _getNextWithin(block, child);
assert(child != null);
if (currentLogicalIndex >= firstLogicalIndexNeedingLayout) {
assert(currentLogicalIndex == firstLogicalIndexNeedingLayout);
child.layout(innerConstraints, parentUsesSize: true);
firstLogicalIndexNeedingLayout += 1;
}
currentLogicalOffset += _getMainAxisExtent(child.size);
currentLogicalIndex += 1;
}
// We now have all the physical children we ought to have to fill the
// viewport. The currentLogicalIndex is the index of the first child that
// we don't need.
_underflow = currentLogicalOffset < endLogicalOffset;
if (_underflow) {
// The last element is visible. We can scroll as far as they want, there's
// nothing more to paint.
_startOffsetUpperLimit = double.INFINITY;
} else {
_startOffsetUpperLimit = currentLogicalOffset - blockExtent;
}
// Remove any unneeded children.
int currentPhysicalIndex = currentLogicalIndex - _firstChildLogicalIndex;
final int numberOfRequiredPhysicalChildren = currentPhysicalIndex;
while (currentPhysicalIndex < _children.length) {
deactivateChild(_children[currentPhysicalIndex]);
_children[currentPhysicalIndex] = null;
currentPhysicalIndex += 1;
}
_children.length = numberOfRequiredPhysicalChildren;
// We now have the correct physical children, each of which has gone through
// layout exactly once. We still need to position them correctly. We
// position the first physical child at Offset.zero and use the paintOffset
// on the render object to adjust the final paint location of the children.
Offset currentChildOffset = _initialChildOffset;
child = block.firstChild;
while (child != null) {
final _LazyBlockParentData childParentData = child.parentData;
childParentData.offset = currentChildOffset;
currentChildOffset += _getMainAxisOffsetForSize(child.size);
child = childParentData.nextSibling;
}
_updatePaintOffset();
LazyBlockExtentsChangedCallback onExtentsChanged = widget.onExtentsChanged;
if (onExtentsChanged != null) {
int lastChildLogicalIndex = _firstChildLogicalIndex + _children.length - 1;
if (_lastReportedFirstChildLogicalIndex != _firstChildLogicalIndex ||
_lastReportedLastChildLogicalIndex != lastChildLogicalIndex ||
_lastReportedFirstChildLogicalOffset != _firstChildLogicalIndex ||
_lastReportedLastChildLogicalOffset != currentLogicalOffset ||
_lastReportedMinScrollOffset != _minScrollOffset ||
_lastReportedContainerExtent != blockExtent) {
_lastReportedFirstChildLogicalIndex = _firstChildLogicalIndex;
_lastReportedLastChildLogicalIndex = lastChildLogicalIndex;
_lastReportedFirstChildLogicalOffset = _firstChildLogicalOffset;
_lastReportedLastChildLogicalOffset = currentLogicalOffset;
_lastReportedMinScrollOffset = _minScrollOffset;
_lastReportedContainerExtent = blockExtent;
onExtentsChanged(
_firstChildLogicalIndex,
lastChildLogicalIndex,
_firstChildLogicalOffset,
currentLogicalOffset,
_lastReportedMinScrollOffset,
_lastReportedContainerExtent
);
}
}
}
BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
switch (widget.mainAxis) {
case Axis.horizontal:
double padding = widget.padding?.vertical ?? 0.0;
double height = math.max(0.0, constraints.maxHeight - padding);
return new BoxConstraints.tightFor(height: height);
case Axis.vertical:
double padding = widget.padding?.horizontal ?? 0.0;
double width = math.max(0.0, constraints.maxWidth - padding);
return new BoxConstraints.tightFor(width: width);
}
assert(widget.mainAxis != null);
return null;
}
Offset get _initialChildOffset {
if (widget.padding == null)
return Offset.zero;
return new Offset(widget.padding.left, widget.padding.top);
}
double _getMainAxisExtent(Size size) {
switch (widget.mainAxis) {
case Axis.horizontal:
return size.width;
case Axis.vertical:
return size.height;
}
assert(widget.mainAxis != null);
return null;
}
Offset _getMainAxisOffsetForSize(Size size) {
switch (widget.mainAxis) {
case Axis.horizontal:
return new Offset(size.width, 0.0);
case Axis.vertical:
return new Offset(0.0, size.height);
}
assert(widget.mainAxis != null);
return null;
}
static RenderBox _getNextWithin(_RenderLazyBlock block, RenderBox child) {
if (child == null)
return block.firstChild;
final _LazyBlockParentData childParentData = child.parentData;
return childParentData.nextSibling;
}
void _updatePaintOffset() {
double physicalStartOffset = widget.startOffset - _firstChildLogicalOffset;
switch (widget.mainAxis) {
case Axis.horizontal:
renderObject.paintOffset = new Offset(-physicalStartOffset, 0.0);
break;
case Axis.vertical:
renderObject.paintOffset = new Offset(0.0, -physicalStartOffset);
break;
}
}
@override
void insertChildRenderObject(RenderObject child, Element slot) {
renderObject.insert(child, after: slot?.renderObject);
}
@override
void moveChildRenderObject(RenderObject child, dynamic slot) {
assert(child.parent == renderObject);
renderObject.move(child, after: slot?.renderObject);
}
@override
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.
////////////////////////////////////////////////////////////////////////////////
// DELETE THIS FILE WHEN REMOVING LEGACY SCROLLING CODE
////////////////////////////////////////////////////////////////////////////////
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'framework.dart';
import 'scroll_configuration.dart';
import 'scrollable.dart';
import 'virtual_viewport.dart';
/// A scrollable list of children that have equal size.
///
/// [ScrollableList] differs from [ScrollableLazyList] in that [ScrollableList]
/// uses an [Iterable] list of children. That makes [ScrollableList] suitable
/// for a large (but not extremely large or infinite) list of children.
///
/// [ScrollableList] differs from [Block] and [LazyBlock] in that
/// [ScrollableList] requires each of its children to be the same size. That
/// makes [ScrollableList] more efficient but less flexible than [Block] and
/// [LazyBlock].
///
/// Prefer [ScrollableViewport] when there is only one child.
///
/// See also:
///
/// * [Block], which allows its children to have arbitrary sizes.
/// * [ScrollableLazyList], a more efficient version of [ScrollableList].
/// * [LazyBlock], a more efficient version of [Block].
/// * [ScrollableViewport], which only has one child.
@Deprecated('use ListView instead')
class ScrollableList extends StatelessWidget {
/// Creats a scrollable list of children that have equal size.
///
/// The [scrollDirection], [scrollAnchor], and [itemExtent] arguments must not
/// be null.
ScrollableList({
Key key,
this.initialScrollOffset,
this.scrollDirection: Axis.vertical,
this.scrollAnchor: ViewportAnchor.start,
this.onScrollStart,
this.onScroll,
this.onScrollEnd,
this.snapOffsetCallback,
this.scrollableKey,
@required this.itemExtent,
this.itemsWrap: false,
this.padding,
this.children: const <Widget>[],
}) : super(key: key) {
assert(scrollDirection != null);
assert(scrollAnchor != null);
assert(itemExtent != null);
}
// Warning: keep the dartdoc comments that follow in sync with the copies in
// Scrollable, LazyBlock, ScrollableLazyList, ScrollableViewport.
// And see: https://github.com/dart-lang/dartdoc/issues/1161.
/// The scroll offset this widget should use when first created.
final double initialScrollOffset;
/// The axis along which this widget should scroll.
final Axis scrollDirection;
/// Whether to place first child at the start of the container or
/// the last child at the end of the container, when the scrollable
/// has not been scrolled and has no initial scroll offset.
///
/// For example, if the [scrollDirection] is [Axis.vertical] and
/// there are enough items to overflow the container, then
/// [ViewportAnchor.start] means that the top of the first item
/// should be aligned with the top of the scrollable with the last
/// item below the bottom, and [ViewportAnchor.end] means the bottom
/// of the last item should be aligned with the bottom of the
/// scrollable, with the first item above the top.
///
/// This also affects whether, when an item is added or removed, the
/// displacement will be towards the first item or the last item.
/// Continuing the earlier example, if a new item is inserted in the
/// middle of the list, in the [ViewportAnchor.start] case the items
/// after it (with greater indices, down to the item with the
/// highest index) will be pushed down, while in the
/// [ViewportAnchor.end] case the items before it (with lower
/// indices, up to the item with the index 0) will be pushed up.
final ViewportAnchor scrollAnchor;
/// Called whenever this widget starts to scroll.
final ScrollListener onScrollStart;
/// Called whenever this widget's scroll offset changes.
final ScrollListener onScroll;
/// Called whenever this widget stops scrolling.
final ScrollListener onScrollEnd;
/// Called to determine the offset to which scrolling should snap,
/// when handling a fling.
///
/// This callback, if set, will be called with the offset that the
/// Scrollable would have scrolled to in the absence of this
/// callback, and a Size describing the size of the Scrollable
/// itself.
///
/// The callback's return value is used as the new scroll offset to
/// aim for.
///
/// If the callback simply returns its first argument (the offset),
/// then it is as if the callback was null.
final SnapOffsetCallback snapOffsetCallback;
/// The key for the Scrollable created by this widget.
final Key scrollableKey;
/// The height of each item if [scrollDirection] is Axis.vertical, otherwise the width of each item.
final double itemExtent;
/// Whether the first item should be revealed after scrolling past the last item.
final bool itemsWrap;
/// The amount of space by which to inset the children inside the viewport.
final EdgeInsets padding;
/// The children, some of which might be materialized.
final Iterable<Widget> children;
Widget _buildViewport(BuildContext context, ScrollableState state) {
return new ListViewport(
onExtentsChanged: (double contentExtent, double containerExtent) {
state.handleExtentsChanged(itemsWrap ? double.INFINITY : contentExtent, containerExtent);
},
scrollOffset: state.scrollOffset,
mainAxis: scrollDirection,
anchor: scrollAnchor,
itemExtent: itemExtent,
itemsWrap: itemsWrap,
padding: padding,
children: children
);
}
@override
Widget build(BuildContext context) {
final Widget result = new Scrollable(
key: scrollableKey,
initialScrollOffset: initialScrollOffset,
scrollDirection: scrollDirection,
scrollAnchor: scrollAnchor,
onScrollStart: onScrollStart,
onScroll: onScroll,
onScrollEnd: onScrollEnd,
snapOffsetCallback: snapOffsetCallback,
builder: _buildViewport
);
return ScrollConfiguration.wrap(context, result);
}
}
@deprecated
class _VirtualListViewport extends VirtualViewport {
_VirtualListViewport(
this.onExtentsChanged,
this.scrollOffset,
this.mainAxis,
this.anchor,
this.itemExtent,
this.itemsWrap,
this.padding
) {
assert(mainAxis != null);
assert(anchor != null);
assert(itemExtent != null);
}
/// Called when the interior or exterior dimensions of the viewport change.
final ExtentsChangedCallback onExtentsChanged;
/// The [startOffset] without taking the [padding] into account.
final double scrollOffset;
/// The direction in which the children are permitted to be larger than the viewport.
///
/// The children are given layout constraints that are fully unconstrained
/// along the main axis (e.g., children can be as tall as they want if the
/// main axis is vertical).
final Axis mainAxis;
/// Whether to place first child at the start of the container or the last
/// child at the end of the container, when the viewport has not been offset.
///
/// For example, if the [mainAxis] is [Axis.vertical] and
/// there are enough items to overflow the container, then
/// [ViewportAnchor.start] means that the top of the first item
/// should be aligned with the top of the viewport with the last
/// item below the bottom, and [ViewportAnchor.end] means the bottom
/// of the last item should be aligned with the bottom of the
/// viewport, with the first item above the top.
///
/// This also affects whether, when an item is added or removed, the
/// displacement will be towards the first item or the last item.
/// Continuing the earlier example, if a new item is inserted in the
/// middle of the list, in the [ViewportAnchor.start] case the items
/// after it (with greater indices, down to the item with the
/// highest index) will be pushed down, while in the
/// [ViewportAnchor.end] case the items before it (with lower
/// indices, up to the item with the index 0) will be pushed up.
final ViewportAnchor anchor;
/// The height of each item if [scrollDirection] is Axis.vertical, otherwise the width of each item.
final double itemExtent;
/// Whether the first item should be revealed after scrolling past the last item.
final bool itemsWrap;
/// The amount of space by which to inset the children inside the viewport.
final EdgeInsets padding;
double get _leadingPadding {
switch (mainAxis) {
case Axis.vertical:
switch (anchor) {
case ViewportAnchor.start:
return padding.top;
case ViewportAnchor.end:
return padding.bottom;
}
break;
case Axis.horizontal:
switch (anchor) {
case ViewportAnchor.start:
return padding.left;
case ViewportAnchor.end:
return padding.right;
}
break;
}
assert(mainAxis != null);
return null;
}
@override
double get startOffset {
if (padding == null)
return scrollOffset;
return scrollOffset - _leadingPadding;
}
@override
RenderList createRenderObject(BuildContext context) => new RenderList(itemExtent: itemExtent);
@override
_VirtualListViewportElement createElement() => new _VirtualListViewportElement(this);
}
@deprecated
class _VirtualListViewportElement extends VirtualViewportElement {
_VirtualListViewportElement(VirtualViewport widget) : super(widget);
@override
_VirtualListViewport get widget => super.widget;
@override
RenderList get renderObject => super.renderObject;
@override
int get materializedChildBase => _materializedChildBase;
int _materializedChildBase;
@override
int get materializedChildCount => _materializedChildCount;
int _materializedChildCount;
@override
double get startOffsetBase => _startOffsetBase;
double _startOffsetBase;
@override
double get startOffsetLimit =>_startOffsetLimit;
double _startOffsetLimit;
@override
void updateRenderObject(_VirtualListViewport oldWidget) {
renderObject
..mainAxis = widget.mainAxis
..anchor = widget.anchor
..itemExtent = widget.itemExtent
..padding = widget.padding;
super.updateRenderObject(oldWidget);
}
double _lastReportedContentExtent;
double _lastReportedContainerExtent;
@override
void layout(BoxConstraints constraints) {
final int length = renderObject.virtualChildCount;
final double itemExtent = widget.itemExtent;
final EdgeInsets padding = widget.padding ?? EdgeInsets.zero;
final Size containerSize = renderObject.size;
double containerExtent;
double contentExtent;
switch (widget.mainAxis) {
case Axis.vertical:
containerExtent = containerSize.height;
contentExtent = length == null ? double.INFINITY : widget.itemExtent * length + padding.vertical;
break;
case Axis.horizontal:
containerExtent = renderObject.size.width;
contentExtent = length == null ? double.INFINITY : widget.itemExtent * length + padding.horizontal;
break;
}
if (length == 0) {
_materializedChildBase = 0;
_materializedChildCount = 0;
_startOffsetBase = 0.0;
_startOffsetLimit = double.INFINITY;
} else {
final double startOffset = widget.startOffset;
int startItem = math.max(0, startOffset ~/ itemExtent);
int limitItem = math.max(0, ((startOffset + containerExtent) / itemExtent).ceil());
if (!widget.itemsWrap && length != null) {
startItem = math.min(length, startItem);
limitItem = math.min(length, limitItem);
}
_materializedChildBase = startItem;
_materializedChildCount = limitItem - startItem;
_startOffsetBase = startItem * itemExtent;
_startOffsetLimit = limitItem * itemExtent - containerExtent;
if (widget.anchor == ViewportAnchor.end)
_materializedChildBase = (length - _materializedChildBase - _materializedChildCount) % length;
}
Size materializedContentSize;
switch (widget.mainAxis) {
case Axis.vertical:
materializedContentSize = new Size(containerSize.width, _materializedChildCount * itemExtent);
break;
case Axis.horizontal:
materializedContentSize = new Size(_materializedChildCount * itemExtent, containerSize.height);
break;
}
renderObject.dimensions = new ViewportDimensions(containerSize: containerSize, contentSize: materializedContentSize);
super.layout(constraints);
if (contentExtent != _lastReportedContentExtent || containerExtent != _lastReportedContainerExtent) {
_lastReportedContentExtent = contentExtent;
_lastReportedContainerExtent = containerExtent;
widget.onExtentsChanged(_lastReportedContentExtent, _lastReportedContainerExtent);
}
}
}
/// A virtual viewport onto a list of equally sized children.
///
/// [ListViewport] differs from [LazyListViewport] in that [ListViewport]
/// uses an [Iterable] list of children. That makes [ListViewport] suitable
/// for a large (but not extremely large or infinite) list of children.
///
/// [ListViewport] differs from [LazyBlockViewport] in that [ListViewport]
/// requires each of its children to be the same size. That makes [ListViewport]
/// more efficient but less flexible than [LazyBlockViewport].
///
/// Prefer [Viewport] when there is only one child.
///
/// Used by [ScrollableList].
///
/// See also:
///
/// * [LazyListViewport].
/// * [LazyBlockViewport].
/// * [GridViewport].
@deprecated
class ListViewport extends _VirtualListViewport with VirtualViewportFromIterable {
/// Creates a virtual viewport onto a list of equally sized children.
///
/// The [mainAxis], [anchor], and [itemExtent] arguments must not be null.
ListViewport({
ExtentsChangedCallback onExtentsChanged,
double scrollOffset: 0.0,
Axis mainAxis: Axis.vertical,
ViewportAnchor anchor: ViewportAnchor.start,
@required double itemExtent,
bool itemsWrap: false,
EdgeInsets padding,
this.children: const <Widget>[],
}) : super(
onExtentsChanged,
scrollOffset,
mainAxis,
anchor,
itemExtent,
itemsWrap,
padding
);
@override
final Iterable<Widget> children;
}
/// An infinite scrollable list of children that have equal size.
///
/// [ScrollableLazyList] differs from [ScrollableList] in that
/// [ScrollableLazyList] uses an [ItemListBuilder] to lazily create children.
/// That makes [ScrollableLazyList] suitable for an extremely large or infinite
/// list of children but also makes it more verbose than [ScrollableList].
///
/// [ScrollableLazyList] differs from [LazyBlock] in that [ScrollableLazyList]
/// requires each of its children to be the same size. That makes
/// [ScrollableLazyList] more efficient but less flexible than [LazyBlock].
///
/// See also:
///
/// * [ScrollableList].
/// * [LazyBlock].
@Deprecated('use ListView.builder instead')
class ScrollableLazyList extends StatelessWidget {
/// Creates an infinite scrollable list of children that have equal size.
///
/// The [scrollDirection], [scrollAnchor], [itemExtent], and [itemBuilder]
/// arguments must not be null. The [itemCount] argument must not be null
/// unless the [scrollAnchor] argument is [ViewportAnchor.start].
ScrollableLazyList({
Key key,
this.initialScrollOffset,
this.scrollDirection: Axis.vertical,
this.scrollAnchor: ViewportAnchor.start,
this.onScrollStart,
this.onScroll,
this.onScrollEnd,
this.snapOffsetCallback,
this.scrollableKey,
@required this.itemExtent,
this.itemCount,
@required this.itemBuilder,
this.padding
}) : super(key: key) {
assert(itemExtent != null);
assert(itemBuilder != null);
assert(itemCount != null || scrollAnchor == ViewportAnchor.start);
}
// Warning: keep the dartdoc comments that follow in sync with the copies in
// Scrollable, LazyBlock, ScrollableViewport, and ScrollableList.
// And see: https://github.com/dart-lang/dartdoc/issues/1161.
/// The scroll offset this widget should use when first created.
final double initialScrollOffset;
/// The axis along which this widget should scroll.
final Axis scrollDirection;
/// Whether to place first child at the start of the container or
/// the last child at the end of the container, when the scrollable
/// has not been scrolled and has no initial scroll offset.
///
/// For example, if the [scrollDirection] is [Axis.vertical] and
/// there are enough items to overflow the container, then
/// [ViewportAnchor.start] means that the top of the first item
/// should be aligned with the top of the scrollable with the last
/// item below the bottom, and [ViewportAnchor.end] means the bottom
/// of the last item should be aligned with the bottom of the
/// scrollable, with the first item above the top.
///
/// This also affects whether, when an item is added or removed, the
/// displacement will be towards the first item or the last item.
/// Continuing the earlier example, if a new item is inserted in the
/// middle of the list, in the [ViewportAnchor.start] case the items
/// after it (with greater indices, down to the item with the
/// highest index) will be pushed down, while in the
/// [ViewportAnchor.end] case the items before it (with lower
/// indices, up to the item with the index 0) will be pushed up.
final ViewportAnchor scrollAnchor;
/// Called whenever this widget starts to scroll.
final ScrollListener onScrollStart;
/// Called whenever this widget's scroll offset changes.
final ScrollListener onScroll;
/// Called whenever this widget stops scrolling.
final ScrollListener onScrollEnd;
/// when handling a fling.
///
/// This callback, if set, will be called with the offset that the
/// Scrollable would have scrolled to in the absence of this
/// callback, and a Size describing the size of the Scrollable
/// itself.
///
/// The callback's return value is used as the new scroll offset to
/// aim for.
///
/// If the callback simply returns its first argument (the offset),
/// then it is as if the callback was null.
final SnapOffsetCallback snapOffsetCallback;
/// The key for the Scrollable created by this widget.
final Key scrollableKey;
/// The height of each item if [scrollDirection] is Axis.vertical, otherwise the width of each item.
final double itemExtent;
/// The total number of list items.
final int itemCount;
/// 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
/// (i.e., [itemCount] if non-null). 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 [ScrollableLazyList].
final ItemListBuilder itemBuilder;
/// The amount of space by which to inset the children inside the viewport.
final EdgeInsets padding;
Widget _buildViewport(BuildContext context, ScrollableState state) {
return new LazyListViewport(
onExtentsChanged: state.handleExtentsChanged,
scrollOffset: state.scrollOffset,
mainAxis: scrollDirection,
anchor: scrollAnchor,
itemExtent: itemExtent,
itemCount: itemCount,
itemBuilder: itemBuilder,
padding: padding
);
}
@override
Widget build(BuildContext context) {
final Widget result = new Scrollable(
key: scrollableKey,
initialScrollOffset: initialScrollOffset,
scrollDirection: scrollDirection,
scrollAnchor: scrollAnchor,
onScrollStart: onScrollStart,
onScroll: onScroll,
onScrollEnd: onScrollEnd,
snapOffsetCallback: snapOffsetCallback,
builder: _buildViewport
);
return ScrollConfiguration.wrap(context, result);
}
}
/// A virtual viewport onto an extremely large or infinite list of equally sized children.
///
/// [LazyListViewport] differs from [ListViewport] in that [LazyListViewport]
/// uses an [ItemListBuilder] to lazily create children. That makes
/// [LazyListViewport] suitable for an extremely large or infinite list of
/// children but also makes it more verbose than [ListViewport].
///
/// [LazyListViewport] differs from [LazyBlockViewport] in that
/// [LazyListViewport] requires each of its children to be the same size. That
/// makes [LazyListViewport] more efficient but less flexible than
/// [LazyBlockViewport].
///
/// Used by [ScrollableLazyList].
///
/// See also:
///
/// * [ListViewport].
/// * [LazyBlockViewport].
@deprecated
class LazyListViewport extends _VirtualListViewport with VirtualViewportFromBuilder {
/// Creates a virtual viewport onto an extremely large or infinite list of equally sized children.
///
/// The [mainAxis], [anchor], [itemExtent], and [itemBuilder] arguments must
/// not be null.
LazyListViewport({
ExtentsChangedCallback onExtentsChanged,
double scrollOffset: 0.0,
Axis mainAxis: Axis.vertical,
ViewportAnchor anchor: ViewportAnchor.start,
@required double itemExtent,
EdgeInsets padding,
this.itemCount,
@required this.itemBuilder
}) : super(
onExtentsChanged,
scrollOffset,
mainAxis,
anchor,
itemExtent,
false, // Don't support wrapping yet.
padding
) {
assert(itemBuilder != null);
}
@override
final int itemCount;
@override
final ItemListBuilder itemBuilder;
}
// 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.
////////////////////////////////////////////////////////////////////////////////
// DELETE THIS FILE WHEN REMOVING LEGACY SCROLLING CODE
////////////////////////////////////////////////////////////////////////////////
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'basic.dart';
import 'debug.dart';
import 'framework.dart';
/// Signature for reporting the interior and exterior dimensions of a viewport.
///
/// * The [contentExtent] is the interior dimension of the viewport (i.e., the
/// size of the thing that's being viewed through the viewport).
/// * The [containerExtent] is the exterior dimension of the viewport (i.e.,
/// the amount of the thing inside the viewport that is visible from outside
/// the viewport).
///
/// Used by [ScrollableList.onExtentsChanged], etc.
@deprecated
typedef void ExtentsChangedCallback(double contentExtent, double containerExtent);
/// An abstract widget whose children are not all materialized.
@deprecated
abstract class VirtualViewport extends RenderObjectWidget {
/// The offset from the [ViewportAnchor] at which the viewport should start painting children.
double get startOffset;
_WidgetProvider _createWidgetProvider();
}
@deprecated
abstract class _WidgetProvider {
void didUpdateWidget(@checked VirtualViewport oldWidget, @checked VirtualViewport newWidget);
int get virtualChildCount;
void prepareChildren(VirtualViewportElement context, int base, int count);
Widget getChild(int i);
}
/// An element that 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.
@deprecated
abstract class VirtualViewportElement extends RenderObjectElement {
/// Creates an element that materializes a contiguous subset of its children.
///
/// The [widget] argument must not be null.
VirtualViewportElement(VirtualViewport widget) : super(widget);
@override
VirtualViewport get 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
/// anchor.
double scrollOffsetToPixelOffset(double scrollOffset) {
switch (renderObject.anchor) {
case ViewportAnchor.start:
return -scrollOffset;
case ViewportAnchor.end:
return scrollOffset;
}
assert(renderObject.anchor != null);
return null;
}
/// Returns a two-dimensional representation of the scroll offset, accounting
/// for the scroll direction and scroll anchor.
Offset scrollOffsetToPixelDelta(double scrollOffset) {
switch (renderObject.mainAxis) {
case Axis.horizontal:
return new Offset(scrollOffsetToPixelOffset(scrollOffset), 0.0);
case Axis.vertical:
return new Offset(0.0, scrollOffsetToPixelOffset(scrollOffset));
}
assert(renderObject.mainAxis != null);
return null;
}
List<Element> _materializedChildren = const <Element>[];
@override
RenderVirtualViewport<ContainerBoxParentDataMixin<RenderBox>> get renderObject => super.renderObject;
@override
void visitChildren(ElementVisitor visitor) {
if (_materializedChildren == null)
return;
for (Element child in _materializedChildren)
visitor(child);
}
@override
void forgetChild(Element child) {
assert(() {
// TODO(ianh): implement forgetChild for VirtualViewport
throw new FlutterError(
'$runtimeType does not yet support GlobalKey reparenting of its children.\n'
'As a temporary workaround, wrap the child with the GlobalKey in a '
'Container or other harmless child.'
);
});
}
_WidgetProvider _widgetProvider;
@override
void mount(Element parent, dynamic newSlot) {
_widgetProvider = widget._createWidgetProvider();
_widgetProvider.didUpdateWidget(null, widget);
super.mount(parent, newSlot);
renderObject.callback = layout;
updateRenderObject(null);
}
@override
void unmount() {
renderObject.callback = null;
super.unmount();
}
@override
void update(VirtualViewport newWidget) {
VirtualViewport oldWidget = widget;
_widgetProvider.didUpdateWidget(oldWidget, newWidget);
super.update(newWidget);
updateRenderObject(oldWidget);
if (!renderObject.needsLayout) // ignore: DEPRECATED_MEMBER_USE, this code will all be going away once the scrolling refactor is done
_materializeChildren();
}
void _updatePaintOffset() {
renderObject.paintOffset = scrollOffsetToPixelDelta(widget.startOffset - startOffsetBase);
}
/// Copies the configuration described by [widget] to this element's [renderObject].
@protected
@mustCallSuper
void updateRenderObject(@checked VirtualViewport oldWidget) {
renderObject.virtualChildCount = _widgetProvider.virtualChildCount;
if (startOffsetBase != null) {
_updatePaintOffset();
// If we don't already need layout, we need to request a layout if the
// viewport has shifted to expose new children.
if (!renderObject.needsLayout) { // ignore: DEPRECATED_MEMBER_USE, this code will all be going away once the scrolling refactor is done
final double startOffset = widget.startOffset;
bool shouldLayout = false;
if (startOffsetBase != null) {
if (startOffset < startOffsetBase)
shouldLayout = true;
else if (startOffset == startOffsetBase && oldWidget?.startOffset != startOffsetBase)
shouldLayout = true;
}
if (startOffsetLimit != null) {
if (startOffset > startOffsetLimit)
shouldLayout = true;
else if (startOffset == startOffsetLimit && oldWidget?.startOffset != startOffsetLimit)
shouldLayout = true;
}
if (shouldLayout)
renderObject.markNeedsLayout();
}
}
}
/// 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);
_updatePaintOffset();
owner.buildScope(this, _materializeChildren);
}
void _materializeChildren() {
int base = materializedChildBase;
int count = materializedChildCount;
assert(base != null);
assert(count != null);
_widgetProvider.prepareChildren(this, base, count);
List<Widget> newWidgets = new List<Widget>(count);
for (int i = 0; i < count; ++i) {
int childIndex = base + i;
Widget child = _widgetProvider.getChild(childIndex);
newWidgets[i] = new RepaintBoundary.wrap(child, childIndex);
}
assert(!debugChildrenHaveDuplicateKeys(widget, newWidgets));
_materializedChildren = updateChildren(_materializedChildren, newWidgets.toList());
}
@override
void insertChildRenderObject(RenderObject child, Element slot) {
renderObject.insert(child, after: slot?.renderObject);
}
@override
void moveChildRenderObject(RenderObject child, Element slot) {
assert(child.parent == renderObject);
renderObject.move(child, after: slot?.renderObject);
}
@override
void removeChildRenderObject(RenderObject child) {
assert(child.parent == renderObject);
renderObject.remove(child);
}
}
/// A VirtualViewport that represents its children using [Iterable<Widget>].
///
/// The iterator is advanced just far enough to obtain widgets for the children
/// that need to be materialized.
@deprecated
abstract class VirtualViewportFromIterable extends VirtualViewport {
/// The children, some of which might be materialized.
Iterable<Widget> get children;
@override
_IterableWidgetProvider _createWidgetProvider() => new _IterableWidgetProvider();
}
@deprecated
class _IterableWidgetProvider extends _WidgetProvider {
int _length;
Iterator<Widget> _iterator;
List<Widget> _widgets;
@override
void didUpdateWidget(VirtualViewportFromIterable oldWidget, VirtualViewportFromIterable newWidget) {
if (oldWidget == null || newWidget.children != oldWidget.children) {
_iterator = null;
_widgets = <Widget>[];
_length = newWidget.children.length;
}
}
@override
int get virtualChildCount => _length;
@override
void prepareChildren(VirtualViewportElement context, int base, int count) {
int limit = base < 0 ? _length : math.min(_length, base + count);
if (limit <= _widgets.length)
return;
VirtualViewportFromIterable widget = context.widget;
if (widget.children is List<Widget>) {
_widgets = widget.children;
return;
}
_iterator ??= widget.children.iterator;
while (_widgets.length < limit) {
bool moved = _iterator.moveNext();
assert(moved);
Widget current = _iterator.current;
assert(current != null);
_widgets.add(current);
}
}
@override
Widget getChild(int i) => _widgets[(i % _length).abs()];
}
/// Signature of a callback that returns the sublist of widgets in the given range.
///
/// Used by [ScrollableLazyList.itemBuilder], etc.
@deprecated
typedef List<Widget> ItemListBuilder(BuildContext context, int start, int count);
/// A VirtualViewport that represents its children using [ItemListBuilder].
///
/// This widget is less ergonomic than [VirtualViewportFromIterable] but scales to
/// unlimited numbers of children.
@deprecated
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;
@override
_LazyWidgetProvider _createWidgetProvider() => new _LazyWidgetProvider();
}
@deprecated
class _LazyWidgetProvider extends _WidgetProvider {
int _length;
int _base;
List<Widget> _widgets;
@override
void didUpdateWidget(VirtualViewportFromBuilder oldWidget, VirtualViewportFromBuilder newWidget) {
// TODO(abarth): We shouldn't check the itemBuilder closure for equality with.
// instead, we should use the widget's identity to decide whether to rebuild.
if (_length != newWidget.itemCount || oldWidget?.itemBuilder != newWidget.itemBuilder) {
_length = newWidget.itemCount;
_base = null;
_widgets = null;
}
}
@override
int get virtualChildCount => _length;
@override
void prepareChildren(VirtualViewportElement context, int base, int count) {
if (_widgets != null && _widgets.length == count && _base == base)
return;
VirtualViewportFromBuilder widget = context.widget;
_base = base;
_widgets = widget.itemBuilder(context, base, count);
}
@override
Widget getChild(int i) {
final int childCount = virtualChildCount;
final int index = childCount != null ? (i % childCount).abs() : i;
return _widgets[index - _base];
}
}
...@@ -29,7 +29,6 @@ export 'src/widgets/heroes.dart'; ...@@ -29,7 +29,6 @@ export 'src/widgets/heroes.dart';
export 'src/widgets/image.dart'; export 'src/widgets/image.dart';
export 'src/widgets/implicit_animations.dart'; export 'src/widgets/implicit_animations.dart';
export 'src/widgets/layout_builder.dart'; export 'src/widgets/layout_builder.dart';
export 'src/widgets/lazy_block.dart';
export 'src/widgets/locale_query.dart'; export 'src/widgets/locale_query.dart';
export 'src/widgets/media_query.dart'; export 'src/widgets/media_query.dart';
export 'src/widgets/modal_barrier.dart'; export 'src/widgets/modal_barrier.dart';
...@@ -55,7 +54,6 @@ export 'src/widgets/scroll_position.dart'; ...@@ -55,7 +54,6 @@ export 'src/widgets/scroll_position.dart';
export 'src/widgets/scroll_simulation.dart'; export 'src/widgets/scroll_simulation.dart';
export 'src/widgets/scroll_view.dart'; export 'src/widgets/scroll_view.dart';
export 'src/widgets/scrollable.dart'; export 'src/widgets/scrollable.dart';
export 'src/widgets/scrollable_list.dart';
export 'src/widgets/semantics_debugger.dart'; export 'src/widgets/semantics_debugger.dart';
export 'src/widgets/single_child_scroll_view.dart'; export 'src/widgets/single_child_scroll_view.dart';
export 'src/widgets/size_changed_layout_notifier.dart'; export 'src/widgets/size_changed_layout_notifier.dart';
...@@ -70,6 +68,5 @@ export 'src/widgets/title.dart'; ...@@ -70,6 +68,5 @@ export 'src/widgets/title.dart';
export 'src/widgets/transitions.dart'; export 'src/widgets/transitions.dart';
export 'src/widgets/unique_widget.dart'; export 'src/widgets/unique_widget.dart';
export 'src/widgets/viewport.dart'; export 'src/widgets/viewport.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