Commit 098af183 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Add SliverList (#7727)

Add SliverList

A SliverList is a linear layout of box children in a viewport that all
have a common, fixed extent along the scroll axis. The layout is similar
to a SliverBlock but more efficient.
parent 6e30cae8
......@@ -47,6 +47,8 @@ export 'src/rendering/shifted_box.dart';
export 'src/rendering/sliver.dart';
export 'src/rendering/sliver_app_bar.dart';
export 'src/rendering/sliver_block.dart';
export 'src/rendering/sliver_list.dart';
export 'src/rendering/sliver_multi_box_adaptor.dart';
export 'src/rendering/sliver_padding.dart';
export 'src/rendering/stack.dart';
export 'src/rendering/table.dart';
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Copyright 2017 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/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:meta/meta.dart';
import 'package:vector_math/vector_math_64.dart';
import 'box.dart';
import 'binding.dart';
import 'object.dart';
import 'sliver.dart';
import 'sliver_multi_box_adaptor.dart';
class SliverBlockParentData extends SliverLogicalParentData with ContainerParentDataMixin<RenderBox> {
int index;
@override
String toString() => 'index=$index; ${super.toString()}';
}
// ///
// ///
// /// The contract for adding and removing children from this render object is
// /// more strict than for normal render objects:
// ///
// /// - Children can be removed except during a layout pass if they have already
// /// been laid out during that layout pass.
// /// - Children cannot be added except during a call to [allowAdditionsFor], and
// /// then only if there is no child correspending to that index (or the child
// /// child corresponding to that index was first removed).
// ///
// /// ## Writing a RenderSliverBlock subclass
// ///
// /// There are three methods to override:
// ///
// /// - [createChild], which must create the child for the given index, and then
// /// insert it in the given location in the child list.
// ///
// /// - [removeChild], which is called when the given child is cleaned up,
// /// and which should remove the given child from the child list.
// ///
// /// - [estimateScrollOffsetExtent], which should return the total extent (e.g.
// /// the height, if this is a vertical block) of all the children.
abstract class RenderSliverBlock extends RenderSliver
with ContainerRenderObjectMixin<RenderBox, SliverBlockParentData>,
RenderSliverHelpers {
@override
void setupParentData(RenderObject child) {
if (child.parentData is! SliverBlockParentData)
child.parentData = new SliverBlockParentData();
}
/// Called during layout when a new child is needed. The child should be
/// inserted into the child list in the appropriate position, after the
/// `after` child (at the start of the list if `after` is null). Its index and
/// scroll offsets will automatically be set appropriately.
///
/// The `index` argument gives the index of the child to show. The first child
/// that will be requested will have index zero, and this should be the child
/// that is aligned with the zero scroll offset. Subsequent requests will be
/// for indices adjacent to previously requested indices. It is possible for
/// negative indices to be requested. For example: if the user scrolls from
/// child 0 to child 10, and then those children get much smaller, and then
/// the user scrolls back up again, this method will eventually be asked to
/// produce a child for index -1.
///
/// If no child corresponds to `index`, then do nothing.
///
/// Which child is indicated by index zero depends on the [GrowthDirection]
/// specified in the [constraints]. For example if the children are the
/// alphabet, then if [constraints.growthDirection] is
/// [GrowthDirection.forward] then index zero is A, and index 25 is Z. On the
/// other hand if [constraints.growthDirection] is [GrowthDirection.reverse]
/// then index zero is Z, and index 25 is A.
///
/// During a call to [createChild] it is valid to remove other children from
/// the [RenderSliverBlock] object if they were not created during this frame
/// and have not yet been updated during this frame. It is not valid to add
/// any children to this render object.
@protected
void createChild(int index, { @required RenderBox after });
/// Remove the given child from the child list.
///
/// Called by [collectGarbage], which itself is called from [performLayout],
/// after the layout algorithm has finished and the non-visible children are
/// to be removed.
///
/// The default implementation calls [remove], which removes the child from
/// the child list.
///
/// The index of the given child can be obtained using the [indexOf] method,
/// which reads it from the [SliverBlockParentData.index] field of the child's
/// [parentData].
@protected
void removeChild(RenderBox child) {
remove(child);
}
int _currentlyUpdatingChildIndex;
@protected
void allowAdditionsFor(int index, VoidCallback callback) {
assert(_currentlyUpdatingChildIndex == null);
assert(index != null);
_currentlyUpdatingChildIndex = index;
try {
callback();
} finally {
_currentlyUpdatingChildIndex = null;
}
}
@override
void adoptChild(RenderObject child) {
assert(_currentlyUpdatingChildIndex != null);
super.adoptChild(child);
final SliverBlockParentData childParentData = child.parentData;
childParentData.index = _currentlyUpdatingChildIndex;
}
@override
void insert(RenderBox child, { RenderBox after }) {
super.insert(child, after: after);
assert(firstChild != null);
assert(() {
int index = indexOf(firstChild);
RenderBox child = childAfter(firstChild);
while (child != null) {
assert(indexOf(child) > index);
index = indexOf(child);
child = childAfter(child);
}
return true;
});
}
/// Called to estimate the total scrollable extents of this object.
///
/// Must return the total distance from the start of the child with the
/// earliest possible index to the end of the child with the last possible
/// index.
@protected
double estimateScrollOffsetExtent({
int firstIndex,
int lastIndex,
double leadingScrollOffset,
double trailingScrollOffset,
});
/// Called during layout to create and add the child with index 0 and scroll
/// offset 0.0.
///
/// Calls [createChild] to actually create and add the child.
///
/// Returns false if createChild did not add any child, otherwise returns
/// true.
///
/// Does not layout the new child.
///
/// When this is called, there are no children, so no children can be removed
/// during the call to createChild. No child should be added during that call
/// either, except for the one that is created and returned by [createChild].
@protected
bool addInitialChild() {
assert(_currentlyUpdatingChildIndex == null);
assert(firstChild == null);
bool result;
invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) {
assert(constraints == this.constraints);
allowAdditionsFor(0, () {
createChild(0, after: null);
if (firstChild != null) {
assert(firstChild == lastChild);
assert(indexOf(firstChild) == 0);
final SliverBlockParentData firstChildParentData = firstChild.parentData;
firstChildParentData.scrollOffset = 0.0;
result = true;
} else {
result = false;
}
});
});
return result;
}
/// Called during layout to create, add, and layout the child before
/// [firstChild].
///
/// Calls [createChild] to actually create and add the child.
///
/// Returns the new child.
///
/// The child that was previously the first child, as well as any subsequent
/// children, may be removed by this call if they have not yet been laid out
/// during this layout pass. No child should be added during that call except
/// for the one that is created and returned by [createChild].
@protected
RenderBox insertAndLayoutLeadingChild(BoxConstraints childConstraints) {
assert(_currentlyUpdatingChildIndex == null);
final int index = indexOf(firstChild) - 1;
final double endScrollOffset = offsetOf(firstChild);
invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) {
assert(constraints == this.constraints);
allowAdditionsFor(index, () {
createChild(index, after: null);
});
});
if (indexOf(firstChild) == index) {
firstChild.layout(childConstraints, parentUsesSize: true);
SliverBlockParentData firstChildParentData = firstChild.parentData;
firstChildParentData.scrollOffset = endScrollOffset - paintExtentOf(firstChild);
return firstChild;
}
return null;
}
/// Called during layout to create, add, and layout the child after
/// the given child.
///
/// Calls [createChild] to actually create and add the child.
///
/// Returns the new child. It is the responsibility of the caller to configure
/// the child's scroll offset.
///
/// Children after the `after` child may be removed in the process. Only the
/// new child may be added.
@protected
RenderBox insertAndLayoutChild(BoxConstraints childConstraints, { @required RenderBox after }) {
assert(_currentlyUpdatingChildIndex == null);
assert(after != null);
final int index = indexOf(after) + 1;
invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) {
assert(constraints == this.constraints);
allowAdditionsFor(index, () {
createChild(index, after: after);
});
});
final RenderBox child = childAfter(after);
if (child != null && indexOf(child) == index) {
assert(indexOf(child) == index);
child.layout(childConstraints, parentUsesSize: true);
return child;
}
return null;
}
/// Called after layout with the number of children that can be garbage
/// collected at the head and tail of the child list.
@protected
void collectGarbage(int leadingGarbage, int trailingGarbage) {
assert(_currentlyUpdatingChildIndex == null);
assert(childCount >= leadingGarbage + trailingGarbage);
invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) {
while (leadingGarbage > 0) {
removeChild(firstChild);
leadingGarbage -= 1;
}
while (trailingGarbage > 0) {
removeChild(lastChild);
trailingGarbage -= 1;
}
});
}
/// Returns the index of the given child, as given by the
/// [SliverBlockParentData.index] field of the child's [parentData].
@protected
int indexOf(RenderBox child) {
assert(child != null);
final SliverBlockParentData childParentData = child.parentData;
assert(childParentData.index != null);
return childParentData.index;
}
/// Returns the scroll offset of the given child, as given by the
/// [SliverBlockParentData.scrollOffset] field of the child's [parentData].
@protected
double offsetOf(RenderBox child) {
assert(child != null);
final SliverBlockParentData childParentData = child.parentData;
assert(childParentData.scrollOffset != null);
return childParentData.scrollOffset;
}
/// Returns the dimension of the given child in the main axis, as given by the
/// child's [RenderBox.size] property. This is only valid after layout.
@protected
double paintExtentOf(RenderBox child) {
assert(child != null);
assert(child.hasSize);
switch (constraints.axis) {
case Axis.horizontal:
return child.size.width;
case Axis.vertical:
return child.size.height;
}
return null;
}
class RenderSliverBlock extends RenderSliverMultiBoxAdaptor {
RenderSliverBlock({
@required RenderSliverBoxChildManager childManager
}) : super(childManager: childManager);
@override
void performLayout() {
assert(_currentlyUpdatingChildIndex == null);
assert(childManager.debugAssertChildListLocked());
double scrollOffset = constraints.scrollOffset;
assert(scrollOffset >= 0.0);
double remainingPaintExtent = constraints.remainingPaintExtent;
......@@ -332,11 +47,7 @@ abstract class RenderSliverBlock extends RenderSliver
if (firstChild == null) {
if (!addInitialChild()) {
// There are no children.
geometry = new SliverGeometry(
scrollExtent: 0.0,
paintExtent: 0.0,
maxPaintExtent: 0.0,
);
geometry = SliverGeometry.zero;
return;
}
}
......@@ -350,9 +61,11 @@ abstract class RenderSliverBlock extends RenderSliver
// Find the last child that is at or before the scrollOffset.
RenderBox earliestUsefulChild = firstChild;
while (offsetOf(earliestUsefulChild) > scrollOffset) {
for (double earliestScrollOffset = offsetOf(earliestUsefulChild);
earliestScrollOffset > scrollOffset;
earliestScrollOffset = offsetOf(earliestUsefulChild)) {
// We have to add children before the earliestUsefulChild.
earliestUsefulChild = insertAndLayoutLeadingChild(childConstraints);
earliestUsefulChild = insertAndLayoutLeadingChild(childConstraints, parentUsesSize: true);
if (earliestUsefulChild == null) {
// We ran out of children before reaching the scroll offset.
// We must inform our parent that this sliver cannot fulfill
......@@ -362,6 +75,8 @@ abstract class RenderSliverBlock extends RenderSliver
);
return;
}
final SliverMultiBoxAdaptorParentData childParentData = earliestUsefulChild.parentData;
childParentData.scrollOffset = earliestScrollOffset - paintExtentOf(firstChild);
assert(earliestUsefulChild == firstChild);
leadingChildWithLayout = earliestUsefulChild;
trailingChildWithLayout ??= earliestUsefulChild;
......@@ -405,7 +120,10 @@ abstract class RenderSliverBlock extends RenderSliver
if (!inLayoutRange) {
if (child == null || indexOf(child) != index) {
// We are missing a child. Insert it (and lay it out) if possible.
child = insertAndLayoutChild(childConstraints, after: trailingChildWithLayout);
child = insertAndLayoutChild(childConstraints,
after: trailingChildWithLayout,
parentUsesSize: true,
);
if (child == null) {
// We have run out of children.
return false;
......@@ -417,7 +135,7 @@ abstract class RenderSliverBlock extends RenderSliver
trailingChildWithLayout = child;
}
assert(child != null);
final SliverBlockParentData childParentData = child.parentData;
final SliverMultiBoxAdaptorParentData childParentData = child.parentData;
childParentData.scrollOffset = endScrollOffset;
assert(childParentData.index == index);
endScrollOffset = offsetOf(child) + paintExtentOf(child);
......@@ -465,22 +183,12 @@ abstract class RenderSliverBlock extends RenderSliver
collectGarbage(leadingGarbage, trailingGarbage);
assert(firstChild != null);
assert(() {
int index = indexOf(firstChild);
RenderBox child = childAfter(firstChild);
while (child != null) {
index += 1;
assert(indexOf(child) == index);
child = childAfter(child);
}
return true;
});
assert(debugAssertChildListIsNonEmptyAndContiguous());
double estimatedTotalExtent;
if (reachedEnd) {
estimatedTotalExtent = endScrollOffset;
} else {
estimatedTotalExtent = estimateScrollOffsetExtent(
estimatedTotalExtent = childManager.estimateScrollOffsetExtent(
firstIndex: indexOf(firstChild),
lastIndex: indexOf(lastChild),
leadingScrollOffset: offsetOf(firstChild),
......@@ -488,7 +196,7 @@ abstract class RenderSliverBlock extends RenderSliver
);
assert(estimatedTotalExtent >= endScrollOffset - offsetOf(firstChild));
}
double paintedExtent = calculatePaintOffset(
final double paintedExtent = calculatePaintOffset(
constraints,
from: offsetOf(firstChild),
to: endScrollOffset,
......@@ -501,79 +209,6 @@ abstract class RenderSliverBlock extends RenderSliver
hasVisualOverflow: endScrollOffset > targetEndScrollOffset || constraints.scrollOffset > 0.0,
);
assert(_currentlyUpdatingChildIndex == null);
}
@override
bool hitTestChildren(HitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) {
RenderBox child = lastChild;
while (child != null) {
if (hitTestBoxChild(result, child, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition))
return true;
child = childBefore(child);
}
return false;
}
@override
double childPosition(RenderBox child) {
return offsetOf(child) - constraints.scrollOffset;
}
@override
void applyPaintTransform(RenderObject child, Matrix4 transform) {
applyPaintTransformForBoxChild(child, transform);
}
@override
void paint(PaintingContext context, Offset offset) {
if (firstChild == null)
return;
// offset is to the top-left corner, regardless of our axis direction.
// originOffset gives us the delta from the real origin to the origin in the axis direction.
Offset unitOffset, originOffset;
bool addExtent;
switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
case AxisDirection.up:
unitOffset = const Offset(0.0, -1.0);
originOffset = offset + new Offset(0.0, geometry.paintExtent);
addExtent = true;
break;
case AxisDirection.right:
unitOffset = const Offset(1.0, 0.0);
originOffset = offset;
addExtent = false;
break;
case AxisDirection.down:
unitOffset = const Offset(0.0, 1.0);
originOffset = offset;
addExtent = false;
break;
case AxisDirection.left:
unitOffset = const Offset(-1.0, 0.0);
originOffset = offset + new Offset(geometry.paintExtent, 0.0);
addExtent = true;
break;
}
assert(unitOffset != null);
assert(addExtent != null);
RenderBox child = firstChild;
while (child != null) {
Offset childOffset = originOffset + unitOffset * childPosition(child);
if (addExtent)
childOffset += unitOffset * paintExtentOf(child);
context.paintChild(child, childOffset);
child = childAfter(child);
}
}
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
if (firstChild != null) {
description.add('currently live children: ${indexOf(firstChild)} to ${indexOf(lastChild)}');
} else {
description.add('no children current live');
}
assert(childManager.debugAssertChildListLocked());
}
}
// Copyright 2017 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 'package:meta/meta.dart';
import 'box.dart';
import 'sliver.dart';
import 'sliver_multi_box_adaptor.dart';
class RenderSliverList extends RenderSliverMultiBoxAdaptor {
RenderSliverList({
@required RenderSliverBoxChildManager childManager,
double itemExtent,
}) : _itemExtent = itemExtent, super(childManager: childManager);
/// The main-axis extent of each item in the list.
double get itemExtent => _itemExtent;
double _itemExtent;
set itemExtent (double newValue) {
assert(newValue != null);
if (_itemExtent == newValue)
return;
_itemExtent = newValue;
markNeedsLayout();
}
double _indexToScrollOffset(int index) => _itemExtent * index;
@override
void performLayout() {
assert(childManager.debugAssertChildListLocked());
final double scrollOffset = constraints.scrollOffset;
assert(scrollOffset >= 0.0);
final double remainingPaintExtent = constraints.remainingPaintExtent;
assert(remainingPaintExtent >= 0.0);
final double targetEndScrollOffset = scrollOffset + remainingPaintExtent;
BoxConstraints childConstraints = constraints.asBoxConstraints(
minExtent: itemExtent,
maxExtent: itemExtent,
);
final int firstIndex = math.max(0, scrollOffset ~/ _itemExtent);
final int targetLastIndex = math.max(0, (targetEndScrollOffset / itemExtent).ceil());
if (firstChild != null) {
final int oldFirstIndex = indexOf(firstChild);
final int oldLastIndex = indexOf(lastChild);
final int leadingGarbage = (firstIndex - oldFirstIndex).clamp(0, childCount);
final int trailingGarbage = (oldLastIndex - targetLastIndex).clamp(0, childCount);
if (leadingGarbage + trailingGarbage > 0)
collectGarbage(leadingGarbage, trailingGarbage);
}
if (firstChild == null) {
if (!addInitialChild(index: firstIndex, scrollOffset: _indexToScrollOffset(firstIndex))) {
// There are no children.
geometry = SliverGeometry.zero;
return;
}
}
RenderBox trailingChildWithLayout;
for (int index = indexOf(firstChild) - 1; index >= firstIndex; --index) {
final RenderBox child = insertAndLayoutLeadingChild(childConstraints);
final SliverMultiBoxAdaptorParentData childParentData = child.parentData;
childParentData.scrollOffset = _indexToScrollOffset(index);
assert(childParentData.index == index);
trailingChildWithLayout ??= child;
}
assert(offsetOf(firstChild) <= scrollOffset);
if (trailingChildWithLayout == null) {
firstChild.layout(childConstraints);
trailingChildWithLayout = firstChild;
}
while (indexOf(trailingChildWithLayout) < targetLastIndex) {
RenderBox child = childAfter(trailingChildWithLayout);
if (child == null) {
child = insertAndLayoutChild(childConstraints, after: trailingChildWithLayout);
if (child == null) {
// We have run out of children.
break;
}
} else {
child.layout(childConstraints);
}
trailingChildWithLayout = child;
assert(child != null);
final SliverMultiBoxAdaptorParentData childParentData = child.parentData;
childParentData.scrollOffset = _indexToScrollOffset(childParentData.index);
}
final int lastIndex = indexOf(lastChild);
final double leadingScrollOffset = _indexToScrollOffset(firstIndex);
final double trailingScrollOffset = _indexToScrollOffset(lastIndex + 1);
assert(debugAssertChildListIsNonEmptyAndContiguous());
assert(indexOf(firstChild) == firstIndex);
assert(lastIndex <= targetLastIndex);
final double estimatedTotalExtent = childManager.estimateScrollOffsetExtent(
firstIndex: firstIndex,
lastIndex: lastIndex,
leadingScrollOffset: leadingScrollOffset,
trailingScrollOffset: trailingScrollOffset,
);
final double paintedExtent = calculatePaintOffset(
constraints,
from: leadingScrollOffset,
to: trailingScrollOffset,
);
geometry = new SliverGeometry(
scrollExtent: estimatedTotalExtent,
paintExtent: paintedExtent,
maxPaintExtent: estimatedTotalExtent,
// Conservative to avoid flickering away the clip during scroll.
hasVisualOverflow: lastIndex >= targetLastIndex || constraints.scrollOffset > 0.0,
);
assert(childManager.debugAssertChildListLocked());
}
}
// 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.
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:meta/meta.dart';
import 'package:vector_math/vector_math_64.dart';
import 'box.dart';
import 'binding.dart';
import 'object.dart';
import 'sliver.dart';
/// A delegate used by [RenderSliverMultiBoxAdaptor] to manage its children.
///
/// [RenderSliverMultiBoxAdaptor] objects reify their children lazily to avoid
/// spending resources on children that are not visible in the viewport. This
/// delegate lets these objects create and remove children as well as estimate
/// the total scroll offset extent occupied by the full child list.
abstract class RenderSliverBoxChildManager {
/// Called during layout when a new child is needed. The child should be
/// inserted into the child list in the appropriate position, after the
/// `after` child (at the start of the list if `after` is null). Its index and
/// scroll offsets will automatically be set appropriately.
///
/// The `index` argument gives the index of the child to show. It is possible
/// for negative indices to be requested. For example: if the user scrolls
/// from child 0 to child 10, and then those children get much smaller, and
/// then the user scrolls back up again, this method will eventually be asked
/// to produce a child for index -1.
///
/// If no child corresponds to `index`, then do nothing.
///
/// Which child is indicated by index zero depends on the [GrowthDirection]
/// specified in the [RenderSliverMultiBoxAdaptor.constraints]. For example
/// if the children are the alphabet, then if
/// [SliverConstraints.growthDirection] is [GrowthDirection.forward] then
/// index zero is A, and index 25 is Z. On the other hand if
/// [SliverConstraints.growthDirection] is [GrowthDirection.reverse]
/// then index zero is Z, and index 25 is A.
///
/// During a call to [createChild] it is valid to remove other children from
/// the [RenderSliverMultiBoxAdaptor] object if they were not created during
/// this frame and have not yet been updated during this frame. It is not
/// valid to add any other children to this render object.
void createChild(int index, { @required RenderBox after });
/// Remove the given child from the child list.
///
/// Called by [RenderSliverMultiBoxAdaptor.collectGarbage], which itself is
/// called from [RenderSliverMultiBoxAdaptor.performLayout].
///
/// The index of the given child can be obtained using the
/// [RenderSliverMultiBoxAdaptor.indexOf] method, which reads it from the
/// [SliverMultiBoxAdaptorParentData.index] field of the child's [parentData].
void removeChild(RenderBox child);
/// Called to estimate the total scrollable extents of this object.
///
/// Must return the total distance from the start of the child with the
/// earliest possible index to the end of the child with the last possible
/// index.
double estimateScrollOffsetExtent({
int firstIndex,
int lastIndex,
double leadingScrollOffset,
double trailingScrollOffset,
});
/// Called during [RenderSliverMultiBoxAdaptor.adoptChild].
///
/// Subclasses must ensure that the [SliverMultiBoxAdaptorParentData.index]
/// field of the child's [parentData] accurately reflects the child's index in
/// the child list after this function returns.
void didAdoptChild(RenderBox child);
/// In debug mode, asserts that this manager is not expecting any
/// modifications to the [RenderSliverMultiBoxAdaptor]'s child list.
///
/// This function always returns true.
///
/// The manager is not required to track whether it is expecting modifications
/// to the [RenderSliverMultiBoxAdaptor]'s child list and can simply return
/// true without making any assertions.
bool debugAssertChildListLocked() => true;
}
class SliverMultiBoxAdaptorParentData extends SliverLogicalParentData with ContainerParentDataMixin<RenderBox> {
int index;
@override
String toString() => 'index=$index; ${super.toString()}';
}
// /// The contract for adding and removing children from this render object is
// /// more strict than for normal render objects:
// ///
// /// - Children can be removed except during a layout pass if they have already
// /// been laid out during that layout pass.
// /// - Children cannot be added except during a call to [childManager], and
// /// then only if there is no child correspending to that index (or the child
// /// child corresponding to that index was first removed).
abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
with ContainerRenderObjectMixin<RenderBox, SliverMultiBoxAdaptorParentData>,
RenderSliverHelpers {
RenderSliverMultiBoxAdaptor({
@required RenderSliverBoxChildManager childManager
}) : _childManager = childManager {
assert(childManager != null);
}
@override
void setupParentData(RenderObject child) {
if (child.parentData is! SliverMultiBoxAdaptorParentData)
child.parentData = new SliverMultiBoxAdaptorParentData();
}
@protected
RenderSliverBoxChildManager get childManager => _childManager;
final RenderSliverBoxChildManager _childManager;
@override
void adoptChild(RenderObject child) {
super.adoptChild(child);
childManager.didAdoptChild(child);
}
bool _debugAssertChildListLocked() => childManager.debugAssertChildListLocked();
@override
void insert(RenderBox child, { RenderBox after }) {
super.insert(child, after: after);
assert(firstChild != null);
assert(() {
int index = indexOf(firstChild);
RenderBox child = childAfter(firstChild);
while (child != null) {
assert(indexOf(child) > index);
index = indexOf(child);
child = childAfter(child);
}
return true;
});
}
/// Called during layout to create and add the child with the given index and
/// scroll offset.
///
/// Calls [RenderSliverBoxChildManager.createChild] to actually create and add
/// the child.
///
/// Returns false if createChild did not add any child, otherwise returns
/// true.
///
/// Does not layout the new child.
///
/// When this is called, there are no children, so no children can be removed
/// during the call to createChild. No child should be added during that call
/// either, except for the one that is created and returned by createChild.
@protected
bool addInitialChild({ int index: 0, double scrollOffset: 0.0 }) {
assert(_debugAssertChildListLocked());
assert(firstChild == null);
bool result;
invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) {
assert(constraints == this.constraints);
_childManager.createChild(index, after: null);
if (firstChild != null) {
assert(firstChild == lastChild);
assert(indexOf(firstChild) == index);
final SliverMultiBoxAdaptorParentData firstChildParentData = firstChild.parentData;
firstChildParentData.scrollOffset = scrollOffset;
result = true;
} else {
result = false;
}
});
return result;
}
/// Called during layout to create, add, and layout the child before
/// [firstChild].
///
/// Calls [RenderSliverBoxChildManager.createChild] to actually create and add
/// the child.
///
/// Returns the new child or null if no child is created.
///
/// The child that was previously the first child, as well as any subsequent
/// children, may be removed by this call if they have not yet been laid out
/// during this layout pass. No child should be added during that call except
/// for the one that is created and returned by createChild.
@protected
RenderBox insertAndLayoutLeadingChild(BoxConstraints childConstraints, {
bool parentUsesSize: false,
}) {
assert(_debugAssertChildListLocked());
final int index = indexOf(firstChild) - 1;
invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) {
assert(constraints == this.constraints);
_childManager.createChild(index, after: null);
});
if (indexOf(firstChild) == index) {
firstChild.layout(childConstraints, parentUsesSize: parentUsesSize);
return firstChild;
}
return null;
}
/// Called during layout to create, add, and layout the child after
/// the given child.
///
/// Calls [RenderSliverBoxChildManager.createChild] to actually create and add
/// the child.
///
/// Returns the new child. It is the responsibility of the caller to configure
/// the child's scroll offset.
///
/// Children after the `after` child may be removed in the process. Only the
/// new child may be added.
@protected
RenderBox insertAndLayoutChild(BoxConstraints childConstraints, {
@required RenderBox after,
bool parentUsesSize: false,
}) {
assert(_debugAssertChildListLocked());
assert(after != null);
final int index = indexOf(after) + 1;
invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) {
assert(constraints == this.constraints);
_childManager.createChild(index, after: after);
});
final RenderBox child = childAfter(after);
if (child != null && indexOf(child) == index) {
assert(indexOf(child) == index);
child.layout(childConstraints, parentUsesSize: parentUsesSize);
return child;
}
return null;
}
/// Called after layout with the number of children that can be garbage
/// collected at the head and tail of the child list.
@protected
void collectGarbage(int leadingGarbage, int trailingGarbage) {
assert(_debugAssertChildListLocked());
assert(childCount >= leadingGarbage + trailingGarbage);
invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) {
while (leadingGarbage > 0) {
_childManager.removeChild(firstChild);
leadingGarbage -= 1;
}
while (trailingGarbage > 0) {
_childManager.removeChild(lastChild);
trailingGarbage -= 1;
}
});
}
/// Returns the index of the given child, as given by the
/// [SliverMultiBoxAdaptorParentData.index] field of the child's [parentData].
int indexOf(RenderBox child) {
assert(child != null);
final SliverMultiBoxAdaptorParentData childParentData = child.parentData;
assert(childParentData.index != null);
return childParentData.index;
}
/// Returns the scroll offset of the given child, as given by the
/// [SliverMultiBoxAdaptorParentData.scrollOffset] field of the child's [parentData].
double offsetOf(RenderBox child) {
assert(child != null);
final SliverMultiBoxAdaptorParentData childParentData = child.parentData;
assert(childParentData.scrollOffset != null);
return childParentData.scrollOffset;
}
/// Returns the dimension of the given child in the main axis, as given by the
/// child's [RenderBox.size] property. This is only valid after layout.
@protected
double paintExtentOf(RenderBox child) {
assert(child != null);
assert(child.hasSize);
switch (constraints.axis) {
case Axis.horizontal:
return child.size.width;
case Axis.vertical:
return child.size.height;
}
return null;
}
@override
bool hitTestChildren(HitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) {
RenderBox child = lastChild;
while (child != null) {
if (hitTestBoxChild(result, child, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition))
return true;
child = childBefore(child);
}
return false;
}
@override
double childPosition(RenderBox child) {
return offsetOf(child) - constraints.scrollOffset;
}
@override
void applyPaintTransform(RenderObject child, Matrix4 transform) {
applyPaintTransformForBoxChild(child, transform);
}
@override
void paint(PaintingContext context, Offset offset) {
if (firstChild == null)
return;
// offset is to the top-left corner, regardless of our axis direction.
// originOffset gives us the delta from the real origin to the origin in the axis direction.
Offset unitOffset, originOffset;
bool addExtent;
switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
case AxisDirection.up:
unitOffset = const Offset(0.0, -1.0);
originOffset = offset + new Offset(0.0, geometry.paintExtent);
addExtent = true;
break;
case AxisDirection.right:
unitOffset = const Offset(1.0, 0.0);
originOffset = offset;
addExtent = false;
break;
case AxisDirection.down:
unitOffset = const Offset(0.0, 1.0);
originOffset = offset;
addExtent = false;
break;
case AxisDirection.left:
unitOffset = const Offset(-1.0, 0.0);
originOffset = offset + new Offset(geometry.paintExtent, 0.0);
addExtent = true;
break;
}
assert(unitOffset != null);
assert(addExtent != null);
RenderBox child = firstChild;
while (child != null) {
Offset childOffset = originOffset + unitOffset * childPosition(child);
if (addExtent)
childOffset += unitOffset * paintExtentOf(child);
context.paintChild(child, childOffset);
child = childAfter(child);
}
}
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
if (firstChild != null) {
description.add('currently live children: ${indexOf(firstChild)} to ${indexOf(lastChild)}');
} else {
description.add('no children current live');
}
}
bool debugAssertChildListIsNonEmptyAndContiguous() {
assert(() {
assert(firstChild != null);
int index = indexOf(firstChild);
RenderBox child = childAfter(firstChild);
while (child != null) {
index += 1;
assert(indexOf(child) == index);
child = childAfter(child);
}
return true;
});
return true;
}
}
// 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.
import 'package:flutter/rendering.dart';
import 'framework.dart';
import 'basic.dart';
import 'scrollable.dart';
import 'sliver.dart';
class ScrollView extends StatelessWidget {
ScrollView({
Key key,
this.padding,
this.scrollDirection: Axis.vertical,
this.anchor: 0.0,
this.initialScrollOffset: 0.0,
this.itemExtent,
this.center,
this.children,
}) : super(key: key);
final EdgeInsets padding;
final Axis scrollDirection;
final double anchor;
final double initialScrollOffset;
final double itemExtent;
final Key center;
final List<Widget> children;
AxisDirection _getDirection(BuildContext context) {
// TODO(abarth): Consider reading direction.
switch (scrollDirection) {
case Axis.horizontal:
return AxisDirection.right;
case Axis.vertical:
return AxisDirection.down;
}
return null;
}
@override
Widget build(BuildContext context) {
final SliverChildListDelegate delegate = new SliverChildListDelegate(children);
Widget sliver;
if (itemExtent == null) {
sliver = new SliverBlock(delegate: delegate);
} else {
sliver = new SliverList(
delegate: delegate,
itemExtent: itemExtent,
);
}
if (padding != null)
sliver = new SliverPadding(padding: padding, child: sliver);
return new ScrollableViewport2(
axisDirection: _getDirection(context),
anchor: anchor,
initialScrollOffset: initialScrollOffset,
center: center,
slivers: <Widget>[ sliver ],
);
}
}
......@@ -6,71 +6,18 @@ import 'dart:collection' show SplayTreeMap, HashMap;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'framework.dart';
import 'basic.dart';
import 'scrollable.dart';
class ScrollView extends StatelessWidget {
ScrollView({
Key key,
this.padding,
this.scrollDirection: Axis.vertical,
this.anchor: 0.0,
this.initialScrollOffset: 0.0,
this.center,
this.children,
}) : super(key: key);
final EdgeInsets padding;
final Axis scrollDirection;
final double anchor;
final double initialScrollOffset;
final Key center;
final List<Widget> children;
AxisDirection _getDirection(BuildContext context) {
// TODO(abarth): Consider reading direction.
switch (scrollDirection) {
case Axis.horizontal:
return AxisDirection.right;
case Axis.vertical:
return AxisDirection.down;
}
return null;
}
@override
Widget build(BuildContext context) {
Widget sliver = new SliverBlock(delegate: new SliverBlockChildListDelegate(children));
if (padding != null)
sliver = new SliverPadding(padding: padding, child: sliver);
return new ScrollableViewport2(
axisDirection: _getDirection(context),
anchor: anchor,
initialScrollOffset: initialScrollOffset,
center: center,
slivers: <Widget>[ sliver ],
);
}
}
abstract class SliverBlockDelegate {
abstract class SliverChildDelegate {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const SliverBlockDelegate();
const SliverChildDelegate();
Widget build(BuildContext context, int index);
bool shouldRebuild(@checked SliverBlockDelegate oldDelegate);
bool shouldRebuild(@checked SliverChildDelegate oldDelegate);
int get childCount;
......@@ -102,10 +49,10 @@ abstract class SliverBlockDelegate {
// /// always visible (and thus there is nothing to be gained by building it on
// /// demand). For example, the body of a dialog box might fit both of these
// /// conditions.
class SliverBlockChildListDelegate extends SliverBlockDelegate {
class SliverChildListDelegate extends SliverChildDelegate {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const SliverBlockChildListDelegate(this.children);
const SliverChildListDelegate(this.children);
final List<Widget> children;
......@@ -118,7 +65,7 @@ class SliverBlockChildListDelegate extends SliverBlockDelegate {
}
@override
bool shouldRebuild(@checked SliverBlockChildListDelegate oldDelegate) {
bool shouldRebuild(@checked SliverChildListDelegate oldDelegate) {
return children != oldDelegate.children;
}
......@@ -126,21 +73,21 @@ class SliverBlockChildListDelegate extends SliverBlockDelegate {
int get childCount => children.length;
}
class SliverBlock extends RenderObjectWidget {
SliverBlock({
abstract class SliverMultiBoxAdaptorWidget extends RenderObjectWidget {
SliverMultiBoxAdaptorWidget({
Key key,
@required this.delegate,
}) : super(key: key) {
assert(delegate != null);
}
final SliverBlockDelegate delegate;
final SliverChildDelegate delegate;
@override
_SliverBlockElement createElement() => new _SliverBlockElement(this);
SliverMultiBoxAdaptorElement createElement() => new SliverMultiBoxAdaptorElement(this);
@override
_RenderSliverBlockForWidgets createRenderObject(BuildContext context) => new _RenderSliverBlockForWidgets();
RenderSliverMultiBoxAdaptor createRenderObject(BuildContext context);
@override
void debugFillDescription(List<String> description) {
......@@ -149,33 +96,55 @@ class SliverBlock extends RenderObjectWidget {
}
}
class _SliverBlockElement extends RenderObjectElement {
_SliverBlockElement(SliverBlock widget) : super(widget);
class SliverBlock extends SliverMultiBoxAdaptorWidget {
SliverBlock({
Key key,
@required SliverChildDelegate delegate,
}) : super(key: key, delegate: delegate);
@override
SliverBlock get widget => super.widget;
RenderSliverBlock createRenderObject(BuildContext context) {
final SliverMultiBoxAdaptorElement element = context;
return new RenderSliverBlock(childManager: element);
}
}
@override
_RenderSliverBlockForWidgets get renderObject => super.renderObject;
class SliverList extends SliverMultiBoxAdaptorWidget {
SliverList({
Key key,
@required SliverChildDelegate delegate,
@required this.itemExtent,
}) : super(key: key, delegate: delegate);
final double itemExtent;
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
renderObject._element = this;
RenderSliverList createRenderObject(BuildContext context) {
final SliverMultiBoxAdaptorElement element = context;
return new RenderSliverList(childManager: element, itemExtent: itemExtent);
}
@override
void unmount() {
super.unmount();
renderObject._element = null;
void updateRenderObject(BuildContext context, RenderSliverList renderObject) {
renderObject.itemExtent = itemExtent;
}
}
class SliverMultiBoxAdaptorElement extends RenderObjectElement implements RenderSliverBoxChildManager {
SliverMultiBoxAdaptorElement(SliverMultiBoxAdaptorWidget widget) : super(widget);
@override
SliverMultiBoxAdaptorWidget get widget => super.widget;
@override
void update(SliverBlock newWidget) {
final SliverBlock oldWidget = widget;
RenderSliverMultiBoxAdaptor get renderObject => super.renderObject;
@override
void update(SliverMultiBoxAdaptorWidget newWidget) {
final SliverMultiBoxAdaptorWidget oldWidget = widget;
super.update(newWidget);
final SliverBlockDelegate newDelegate = newWidget.delegate;
final SliverBlockDelegate oldDelegate = oldWidget.delegate;
final SliverChildDelegate newDelegate = newWidget.delegate;
final SliverChildDelegate oldDelegate = oldWidget.delegate;
if (newDelegate != oldDelegate &&
(newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRebuild(oldDelegate)))
performRebuild();
......@@ -184,15 +153,13 @@ class _SliverBlockElement extends RenderObjectElement {
Map<int, Element> _childElements = new SplayTreeMap<int, Element>();
Map<int, Widget> _childWidgets = new HashMap<int, Widget>();
RenderBox _currentBeforeChild;
bool _debugOpenToChanges = false;
@override
void performRebuild() {
_childWidgets.clear();
super.performRebuild();
_currentBeforeChild = null;
assert(!_debugOpenToChanges);
assert(() { _debugOpenToChanges = true; return true; });
assert(_currentlyUpdatingChildIndex == null);
try {
// The "toList()" below is to get a copy of the array so that we can
// mutate _childElements within the loop. Basically we just update all the
......@@ -202,10 +169,8 @@ class _SliverBlockElement extends RenderObjectElement {
// delegate's results until the next time we need to rebuild the whole
// block widget.)
for (int index in _childElements.keys.toList()) {
Element newChild;
renderObject._rebuild(index, () {
newChild = updateChild(_childElements[index], _build(index), index);
});
_currentlyUpdatingChildIndex = index;
Element newChild = updateChild(_childElements[index], _build(index), index);
if (newChild != null) {
_childElements[index] = newChild;
_currentBeforeChild = newChild.renderObject;
......@@ -214,7 +179,7 @@ class _SliverBlockElement extends RenderObjectElement {
}
}
} finally {
assert(() { _debugOpenToChanges = false; return true; });
_currentlyUpdatingChildIndex = null;
}
}
......@@ -227,17 +192,19 @@ class _SliverBlockElement extends RenderObjectElement {
});
}
void _createChild(int index, bool insertFirst) {
assert(!_debugOpenToChanges);
@override
void createChild(int index, { @required RenderBox after }) {
assert(_currentlyUpdatingChildIndex == null);
owner.buildScope(this, () {
final bool insertFirst = after == null;
assert(insertFirst || _childElements[index-1] != null);
assert(() { _debugOpenToChanges = true; return true; });
_currentBeforeChild = insertFirst ? null : _childElements[index-1].renderObject;
Element newChild;
try {
_currentlyUpdatingChildIndex = index;
newChild = updateChild(_childElements[index], _build(index), index);
} finally {
assert(() { _debugOpenToChanges = false; return true; });
_currentlyUpdatingChildIndex = null;
}
if (newChild != null) {
_childElements[index] = newChild;
......@@ -255,29 +222,63 @@ class _SliverBlockElement extends RenderObjectElement {
_childElements.remove(child.slot);
}
void _removeChild(int index) {
assert(!_debugOpenToChanges);
@override
void removeChild(RenderBox child) {
final int index = renderObject.indexOf(child);
assert(_currentlyUpdatingChildIndex == null);
assert(index >= 0);
owner.buildScope(this, () {
assert(_childElements.containsKey(index));
assert(() { _debugOpenToChanges = true; return true; });
try {
Element result = updateChild(_childElements[index], null, index);
_currentlyUpdatingChildIndex = index;
final Element result = updateChild(_childElements[index], null, index);
assert(result == null);
} finally {
assert(() { _debugOpenToChanges = false; return true; });
_currentlyUpdatingChildIndex = null;
}
_childElements.remove(index);
assert(!_childElements.containsKey(index));
});
}
@override
double estimateScrollOffsetExtent({
int firstIndex,
int lastIndex,
double leadingScrollOffset,
double trailingScrollOffset,
}) {
assert(lastIndex >= firstIndex);
return widget.delegate.estimateScrollOffsetExtent(
firstIndex,
lastIndex,
leadingScrollOffset,
trailingScrollOffset
);
}
int _currentlyUpdatingChildIndex;
@override
bool debugAssertChildListLocked() {
assert(_currentlyUpdatingChildIndex == null);
return true;
}
@override
void didAdoptChild(RenderBox child) {
assert(_currentlyUpdatingChildIndex != null);
final SliverMultiBoxAdaptorParentData childParentData = child.parentData;
childParentData.index = _currentlyUpdatingChildIndex;
}
@override
void insertChildRenderObject(@checked RenderObject child, int slot) {
assert(_debugOpenToChanges);
assert(slot != null);
assert(_currentlyUpdatingChildIndex == slot);
renderObject.insert(child, after: _currentBeforeChild);
assert(() {
SliverBlockParentData childParentData = child.parentData;
SliverMultiBoxAdaptorParentData childParentData = child.parentData;
assert(slot == childParentData.index);
return true;
});
......@@ -292,7 +293,7 @@ class _SliverBlockElement extends RenderObjectElement {
@override
void removeChildRenderObject(@checked RenderObject child) {
assert(_debugOpenToChanges);
assert(_currentlyUpdatingChildIndex != null);
renderObject.remove(child);
}
......@@ -304,35 +305,3 @@ class _SliverBlockElement extends RenderObjectElement {
_childElements.values.toList().forEach(visitor);
}
}
class _RenderSliverBlockForWidgets extends RenderSliverBlock {
_SliverBlockElement _element;
@override
void createChild(int index, { @required RenderBox after }) {
assert(_element != null);
_element._createChild(index, after == null);
}
@override
void removeChild(RenderBox child) {
assert(_element != null);
_element._removeChild(indexOf(child));
}
void _rebuild(int index, VoidCallback callback) {
allowAdditionsFor(index, callback);
}
@override
double estimateScrollOffsetExtent({
int firstIndex,
int lastIndex,
double leadingScrollOffset,
double trailingScrollOffset,
}) {
assert(lastIndex >= firstIndex);
assert(_element != null);
return _element.widget.delegate.estimateScrollOffsetExtent(firstIndex, lastIndex, leadingScrollOffset, trailingScrollOffset);
}
}
......@@ -14,7 +14,6 @@ export 'src/widgets/app_bar.dart';
export 'src/widgets/banner.dart';
export 'src/widgets/basic.dart';
export 'src/widgets/binding.dart';
export 'src/widgets/block.dart';
export 'src/widgets/clamp_overscrolls.dart';
export 'src/widgets/container.dart';
export 'src/widgets/debug.dart';
......@@ -51,12 +50,14 @@ export 'src/widgets/scroll_behavior.dart';
export 'src/widgets/scroll_configuration.dart';
export 'src/widgets/scroll_notification.dart';
export 'src/widgets/scroll_simulation.dart';
export 'src/widgets/scroll_view.dart';
export 'src/widgets/scrollable.dart';
export 'src/widgets/scrollable_grid.dart';
export 'src/widgets/scrollable_list.dart';
export 'src/widgets/semantics_debugger.dart';
export 'src/widgets/single_child_scroll_view.dart';
export 'src/widgets/size_changed_layout_notifier.dart';
export 'src/widgets/sliver.dart';
export 'src/widgets/status_transitions.dart';
export 'src/widgets/table.dart';
export 'src/widgets/text.dart';
......
......@@ -8,19 +8,38 @@ import 'package:test/test.dart';
import 'rendering_tester.dart';
class RenderSliverBlockTest extends RenderSliverBlock {
RenderSliverBlockTest({
class TestRenderSliverBoxChildManager extends RenderSliverBoxChildManager {
TestRenderSliverBoxChildManager({
this.children,
});
RenderSliverBlock _renderObject;
List<RenderBox> children;
RenderSliverBlock createRenderObject() {
assert(_renderObject == null);
_renderObject = new RenderSliverBlock(childManager: this);
return _renderObject;
}
int _currentlyUpdatingChildIndex;
@override
void createChild(int index, { @required RenderBox after }) {
assert(index >= 0);
if (index < 0 || index >= children.length)
return null;
insert(children[index], after: after);
try {
_currentlyUpdatingChildIndex = index;
_renderObject.insert(children[index], after: after);
} finally {
_currentlyUpdatingChildIndex = null;
}
}
@override
void removeChild(RenderBox child) {
_renderObject.remove(child);
}
@override
......@@ -33,25 +52,33 @@ class RenderSliverBlockTest extends RenderSliverBlock {
assert(lastIndex >= firstIndex);
return children.length * (trailingScrollOffset - leadingScrollOffset) / (lastIndex - firstIndex + 1);
}
@override
void didAdoptChild(RenderBox child) {
assert(_currentlyUpdatingChildIndex != null);
final SliverMultiBoxAdaptorParentData childParentData = child.parentData;
childParentData.index = _currentlyUpdatingChildIndex;
}
}
void main() {
test('RenderSliverBlock basic test - down', () {
RenderObject inner;
RenderBox a, b, c, d, e;
TestRenderSliverBoxChildManager childManager = new TestRenderSliverBoxChildManager(
children: <RenderBox>[
a = new RenderSizedBox(const Size(100.0, 400.0)),
b = new RenderSizedBox(const Size(100.0, 400.0)),
c = new RenderSizedBox(const Size(100.0, 400.0)),
d = new RenderSizedBox(const Size(100.0, 400.0)),
e = new RenderSizedBox(const Size(100.0, 400.0)),
],
);
RenderViewport2 root = new RenderViewport2(
axisDirection: AxisDirection.down,
offset: new ViewportOffset.zero(),
children: <RenderSliver>[
inner = new RenderSliverBlockTest(
children: <RenderBox>[
a = new RenderSizedBox(const Size(100.0, 400.0)),
b = new RenderSizedBox(const Size(100.0, 400.0)),
c = new RenderSizedBox(const Size(100.0, 400.0)),
d = new RenderSizedBox(const Size(100.0, 400.0)),
e = new RenderSizedBox(const Size(100.0, 400.0)),
],
),
inner = childManager.createRenderObject(),
],
);
layout(root);
......@@ -112,19 +139,20 @@ void main() {
test('RenderSliverBlock basic test - up', () {
RenderObject inner;
RenderBox a, b, c, d, e;
TestRenderSliverBoxChildManager childManager = new TestRenderSliverBoxChildManager(
children: <RenderBox>[
a = new RenderSizedBox(const Size(100.0, 400.0)),
b = new RenderSizedBox(const Size(100.0, 400.0)),
c = new RenderSizedBox(const Size(100.0, 400.0)),
d = new RenderSizedBox(const Size(100.0, 400.0)),
e = new RenderSizedBox(const Size(100.0, 400.0)),
],
);
RenderViewport2 root = new RenderViewport2(
axisDirection: AxisDirection.up,
offset: new ViewportOffset.zero(),
children: <RenderSliver>[
inner = new RenderSliverBlockTest(
children: <RenderBox>[
a = new RenderSizedBox(const Size(100.0, 400.0)),
b = new RenderSizedBox(const Size(100.0, 400.0)),
c = new RenderSizedBox(const Size(100.0, 400.0)),
d = new RenderSizedBox(const Size(100.0, 400.0)),
e = new RenderSizedBox(const Size(100.0, 400.0)),
],
),
inner = childManager.createRenderObject(),
],
);
layout(root);
......
......@@ -121,7 +121,7 @@ void main() {
});
testWidgets('SliverBlockChildListDelegate.estimateScrollOffsetExtent hits end', (WidgetTester tester) async {
SliverBlockChildListDelegate delegate = new SliverBlockChildListDelegate(<Widget>[
SliverChildListDelegate delegate = new SliverChildListDelegate(<Widget>[
new Container(),
new Container(),
new Container(),
......
// 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_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
void main() {
testWidgets('ScrollView itemExtent control test', (WidgetTester tester) async {
await tester.pumpWidget(
new ScrollView(
itemExtent: 200.0,
children: new List<Widget>.generate(20, (int i) {
return new Container(
child: new Text('$i'),
);
}),
),
);
RenderBox box = tester.renderObject<RenderBox>(find.byType(Container).first);
expect(box.size.height, equals(200.0));
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsOneWidget);
expect(find.text('2'), findsOneWidget);
expect(find.text('3'), findsOneWidget);
expect(find.text('4'), findsNothing);
await tester.scroll(find.byType(ScrollView), const Offset(0.0, -250.0));
await tester.pump();
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
expect(find.text('2'), findsOneWidget);
expect(find.text('3'), findsOneWidget);
expect(find.text('4'), findsOneWidget);
expect(find.text('5'), findsOneWidget);
expect(find.text('6'), findsNothing);
await tester.scroll(find.byType(ScrollView), const Offset(0.0, 200.0));
await tester.pump();
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsOneWidget);
expect(find.text('2'), findsOneWidget);
expect(find.text('3'), findsOneWidget);
expect(find.text('4'), findsOneWidget);
expect(find.text('5'), findsNothing);
});
testWidgets('ScrollView large scroll jump', (WidgetTester tester) async {
List<int> log = <int>[];
await tester.pumpWidget(
new ScrollView(
itemExtent: 200.0,
children: new List<Widget>.generate(20, (int i) {
return new Builder(
builder: (BuildContext context) {
log.add(i);
return new Container(
child: new Text('$i'),
);
}
);
}),
),
);
expect(log, equals(<int>[0, 1, 2, 3]));
log.clear();
Scrollable2State state = tester.state(find.byType(Scrollable2));
AbsoluteScrollPosition position = state.position;
position.jumpTo(2025.0);
expect(log, isEmpty);
await tester.pump();
expect(log, equals(<int>[10, 11, 12, 13, 14]));
log.clear();
position.jumpTo(975.0);
expect(log, isEmpty);
await tester.pump();
expect(log, equals(<int>[4, 5, 6, 7, 8]));
log.clear();
});
}
......@@ -28,7 +28,7 @@ Future<Null> test(WidgetTester tester, double offset, List<int> keys) {
offset: new ViewportOffset.fixed(offset),
slivers: <Widget>[
new SliverBlock(
delegate: new SliverBlockChildListDelegate(keys.map((int key) {
delegate: new SliverChildListDelegate(keys.map((int key) {
return new SizedBox(key: new GlobalObjectKey(key), height: 100.0, child: new GenerationText(key));
}).toList()),
),
......
......@@ -13,7 +13,7 @@ Future<Null> test(WidgetTester tester, double offset) {
offset: new ViewportOffset.fixed(offset),
slivers: <Widget>[
new SliverBlock(
delegate: new SliverBlockChildListDelegate(<Widget>[
delegate: new SliverChildListDelegate(<Widget>[
new SizedBox(height: 400.0, child: new Text('a')),
new SizedBox(height: 400.0, child: new Text('b')),
new SizedBox(height: 400.0, child: new Text('c')),
......@@ -78,7 +78,7 @@ void main() {
offset: offset,
slivers: <Widget>[
new SliverBlock(
delegate: new SliverBlockChildListDelegate(<Widget>[
delegate: new SliverChildListDelegate(<Widget>[
new SizedBox(height: 251.0, child: new Text('a')),
new SizedBox(height: 252.0, child: new Text('b')),
new SizedBox(key: key1, height: 253.0, child: new Text('c')),
......@@ -95,7 +95,7 @@ void main() {
offset: offset,
slivers: <Widget>[
new SliverBlock(
delegate: new SliverBlockChildListDelegate(<Widget>[
delegate: new SliverChildListDelegate(<Widget>[
new SizedBox(key: key1, height: 253.0, child: new Text('c')),
new SizedBox(height: 251.0, child: new Text('a')),
new SizedBox(height: 252.0, child: new Text('b')),
......@@ -112,7 +112,7 @@ void main() {
offset: offset,
slivers: <Widget>[
new SliverBlock(
delegate: new SliverBlockChildListDelegate(<Widget>[
delegate: new SliverChildListDelegate(<Widget>[
new SizedBox(height: 251.0, child: new Text('a')),
new SizedBox(key: key1, height: 253.0, child: new Text('c')),
new SizedBox(height: 252.0, child: new Text('b')),
......@@ -129,7 +129,7 @@ void main() {
offset: offset,
slivers: <Widget>[
new SliverBlock(
delegate: new SliverBlockChildListDelegate(<Widget>[
delegate: new SliverChildListDelegate(<Widget>[
new SizedBox(height: 251.0, child: new Text('a')),
new SizedBox(height: 252.0, child: new Text('b')),
]),
......@@ -144,7 +144,7 @@ void main() {
offset: offset,
slivers: <Widget>[
new SliverBlock(
delegate: new SliverBlockChildListDelegate(<Widget>[
delegate: new SliverChildListDelegate(<Widget>[
new SizedBox(height: 251.0, child: new Text('a')),
new SizedBox(key: key1, height: 253.0, child: new Text('c')),
new SizedBox(height: 252.0, child: new Text('b')),
......@@ -210,7 +210,7 @@ void main() {
offset: new ViewportOffset.zero(),
slivers: <Widget>[
new SliverBlock(
delegate: new SliverBlockChildListDelegate(<Widget>[
delegate: new SliverChildListDelegate(<Widget>[
new SizedBox(height: 400.0, child: new Text('a')),
]),
),
......@@ -223,7 +223,7 @@ void main() {
offset: new ViewportOffset.fixed(100.0),
slivers: <Widget>[
new SliverBlock(
delegate: new SliverBlockChildListDelegate(<Widget>[
delegate: new SliverChildListDelegate(<Widget>[
new SizedBox(height: 400.0, child: new Text('a')),
]),
),
......@@ -236,7 +236,7 @@ void main() {
offset: new ViewportOffset.fixed(100.0),
slivers: <Widget>[
new SliverBlock(
delegate: new SliverBlockChildListDelegate(<Widget>[
delegate: new SliverChildListDelegate(<Widget>[
new SizedBox(height: 4000.0, child: new Text('a')),
]),
),
......@@ -249,7 +249,7 @@ void main() {
offset: new ViewportOffset.zero(),
slivers: <Widget>[
new SliverBlock(
delegate: new SliverBlockChildListDelegate(<Widget>[
delegate: new SliverChildListDelegate(<Widget>[
new SizedBox(height: 4000.0, child: new Text('a')),
]),
),
......
......@@ -119,7 +119,7 @@ void main() {
new SliverAppBar(delegate: new TestSliverAppBarDelegate(150.0), floating: true),
new SliverToBoxAdapter(child: new Container(height: 5.0)),
new SliverBlock(
delegate: new SliverBlockChildListDelegate(<Widget>[
delegate: new SliverChildListDelegate(<Widget>[
new Container(height: 50.0),
new Container(height: 50.0),
new Container(height: 50.0),
......
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