Revert "Revert "Add SliverGrid and ScrollGrid"" (#7780)

This commit is contained in:
Adam Barth 2017-02-01 10:18:48 -08:00 committed by GitHub
parent 3a43fc88b6
commit 695302029f
16 changed files with 1117 additions and 186 deletions

View file

@ -131,9 +131,9 @@ class _GridPhotoViewerState extends State<GridPhotoViewer> with SingleTickerProv
..translate(_offset.dx, _offset.dy)
..scale(_scale),
child: new ClipRect(
child: new Image.asset(config.photo.assetName, fit: ImageFit.cover)
)
)
child: new Image.asset(config.photo.assetName, fit: ImageFit.cover),
),
),
);
}
);
@ -200,11 +200,11 @@ class GridDemoPhotoItem extends StatelessWidget {
backgroundColor: Colors.black45,
leading: new Icon(
icon,
color: Colors.white
)
)
color: Colors.white,
),
),
),
child: image
child: image,
);
case GridDemoTileStyle.twoLine:
@ -217,11 +217,11 @@ class GridDemoPhotoItem extends StatelessWidget {
subtitle: new _GridTitleText(photo.caption),
trailing: new Icon(
icon,
color: Colors.white
)
)
color: Colors.white,
),
),
),
child: image
child: image,
);
}
assert(tileStyle != null);
@ -245,62 +245,62 @@ class GridListDemoState extends State<GridListDemo> {
new Photo(
assetName: 'packages/flutter_gallery_assets/landscape_0.jpg',
title: 'Philippines',
caption: 'Batad rice terraces'
caption: 'Batad rice terraces',
),
new Photo(
assetName: 'packages/flutter_gallery_assets/landscape_1.jpg',
title: 'Italy',
caption: 'Ceresole Reale'
caption: 'Ceresole Reale',
),
new Photo(
assetName: 'packages/flutter_gallery_assets/landscape_2.jpg',
title: 'Somewhere',
caption: 'Beautiful mountains'
caption: 'Beautiful mountains',
),
new Photo(
assetName: 'packages/flutter_gallery_assets/landscape_3.jpg',
title: 'A place',
caption: 'Beautiful hills'
caption: 'Beautiful hills',
),
new Photo(
assetName: 'packages/flutter_gallery_assets/landscape_4.jpg',
title: 'New Zealand',
caption: 'View from the van'
caption: 'View from the van',
),
new Photo(
assetName: 'packages/flutter_gallery_assets/landscape_5.jpg',
title: 'Autumn',
caption: 'The golden season'
caption: 'The golden season',
),
new Photo(
assetName: 'packages/flutter_gallery_assets/landscape_6.jpg',
title: 'Germany',
caption: 'Englischer Garten'
caption: 'Englischer Garten',
),
new Photo(
assetName: 'packages/flutter_gallery_assets/landscape_7.jpg',
title: 'A country',
caption: 'Grass fields'
caption: 'Grass fields',
),
new Photo(
assetName: 'packages/flutter_gallery_assets/landscape_8.jpg',
title: 'Mountain country',
caption: 'River forest'
caption: 'River forest',
),
new Photo(
assetName: 'packages/flutter_gallery_assets/landscape_9.jpg',
title: 'Alpine place',
caption: 'Green hills'
caption: 'Green hills',
),
new Photo(
assetName: 'packages/flutter_gallery_assets/landscape_10.jpg',
title: 'Desert land',
caption: 'Blue skies'
caption: 'Blue skies',
),
new Photo(
assetName: 'packages/flutter_gallery_assets/landscape_11.jpg',
title: 'Narnia',
caption: 'Rocks and rivers'
caption: 'Rocks and rivers',
),
];
@ -325,31 +325,29 @@ class GridListDemoState extends State<GridListDemo> {
itemBuilder: (BuildContext context) => <PopupMenuItem<GridDemoTileStyle>>[
new PopupMenuItem<GridDemoTileStyle>(
value: GridDemoTileStyle.imageOnly,
child: new Text('Image only')
child: new Text('Image only'),
),
new PopupMenuItem<GridDemoTileStyle>(
value: GridDemoTileStyle.oneLine,
child: new Text('One line')
child: new Text('One line'),
),
new PopupMenuItem<GridDemoTileStyle>(
value: GridDemoTileStyle.twoLine,
child: new Text('Two line')
)
]
)
]
child: new Text('Two line'),
),
],
),
],
),
body: new Column(
children: <Widget>[
new Expanded(
child: new ScrollableGrid(
delegate: new FixedColumnCountGridDelegate(
columnCount: (orientation == Orientation.portrait) ? 2 : 3,
rowSpacing: 4.0,
columnSpacing: 4.0,
padding: const EdgeInsets.all(4.0),
tileAspectRatio: (orientation == Orientation.portrait) ? 1.0 : 1.3
),
child: new ScrollGrid.count(
crossAxisCount: (orientation == Orientation.portrait) ? 2 : 3,
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
padding: const EdgeInsets.all(4.0),
childAspectRatio: (orientation == Orientation.portrait) ? 1.0 : 1.3,
children: photos.map((Photo photo) {
return new GridDemoPhotoItem(
photo: photo,
@ -360,7 +358,7 @@ class GridListDemoState extends State<GridListDemo> {
});
}
);
})
}).toList(),
)
)
]

View file

@ -47,6 +47,7 @@ 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_grid.dart';
export 'src/rendering/sliver_list.dart';
export 'src/rendering/sliver_multi_box_adaptor.dart';
export 'src/rendering/sliver_padding.dart';

View file

@ -306,7 +306,9 @@ class SliverConstraints extends Constraints {
BoxConstraints asBoxConstraints({
double minExtent: 0.0,
double maxExtent: double.INFINITY,
double crossAxisExtent,
}) {
crossAxisExtent ??= this.crossAxisExtent;
switch (axis) {
case Axis.horizontal:
return new BoxConstraints(
@ -839,13 +841,16 @@ abstract class RenderSliver extends RenderObject {
/// the [RenderSliverHelpers] mixin and do not call this method yourself, you
/// do not need to implement this method.
@protected
double childPosition(@checked RenderObject child) {
double childMainAxisPosition(@checked RenderObject child) {
assert(() {
throw new FlutterError('$runtimeType does not implement childPosition.');
});
return 0.0;
}
@protected
double childCrossAxisPosition(@checked RenderObject child) => 0.0;
@override
void applyPaintTransform(RenderObject child, Matrix4 transform) {
assert(() {
@ -1022,24 +1027,25 @@ abstract class RenderSliverHelpers implements RenderSliver {
/// This function takes care of converting the position from the sliver
/// coordinate system to the cartesian coordinate system used by [RenderBox].
///
/// This function relies on [childPosition] to determine the position of
/// This function relies on [childMainAxisPosition] to determine the position of
/// child in question.
///
/// Calling this for a child that is not visible is not valid.
@protected
bool hitTestBoxChild(HitTestResult result, RenderBox child, { @required double mainAxisPosition, @required double crossAxisPosition }) {
final bool rightWayUp = _getRightWayUp(constraints);
double absolutePosition = mainAxisPosition - childPosition(child);
double absolutePosition = mainAxisPosition - childMainAxisPosition(child);
final double absoluteCrossAxisPosition = crossAxisPosition - childCrossAxisPosition(child);
assert(constraints.axis != null);
switch (constraints.axis) {
case Axis.horizontal:
if (!rightWayUp)
absolutePosition = child.size.width - absolutePosition;
return child.hitTest(result, position: new Point(absolutePosition, crossAxisPosition));
return child.hitTest(result, position: new Point(absolutePosition, absoluteCrossAxisPosition));
case Axis.vertical:
if (!rightWayUp)
absolutePosition = child.size.height - absolutePosition;
return child.hitTest(result, position: new Point(crossAxisPosition, absolutePosition));
return child.hitTest(result, position: new Point(absoluteCrossAxisPosition, absolutePosition));
}
return false;
}
@ -1047,25 +1053,27 @@ abstract class RenderSliverHelpers implements RenderSliver {
/// Utility function for [applyPaintTransform] for use when the children are
/// [RenderBox] widgets.
///
/// This function turns the value returned by [childPosition] for the child in
/// question into a translation that it then applies to the given matrix.
/// This function turns the value returned by [childMainAxisPosition] and
/// [childCrossAxisPosition]for the child in question into a translation that
/// it then applies to the given matrix.
///
/// Calling this for a child that is not visible is not valid.
@protected
void applyPaintTransformForBoxChild(RenderBox child, Matrix4 transform) {
final bool rightWayUp = _getRightWayUp(constraints);
double delta = childPosition(child);
double delta = childMainAxisPosition(child);
final double crossAxisDelta = childCrossAxisPosition(child);
assert(constraints.axis != null);
switch (constraints.axis) {
case Axis.horizontal:
if (!rightWayUp)
delta = geometry.paintExtent - child.size.width - delta;
transform.translate(delta, 0.0);
transform.translate(delta, crossAxisDelta);
break;
case Axis.vertical:
if (!rightWayUp)
delta = geometry.paintExtent - child.size.height - delta;
transform.translate(0.0, delta);
transform.translate(crossAxisDelta, delta);
break;
}
}
@ -1862,7 +1870,7 @@ class RenderSliverToBoxAdapter extends RenderSliver with RenderObjectWithChildMi
}
@override
double childPosition(RenderBox child) {
double childMainAxisPosition(RenderBox child) {
return -constraints.scrollOffset;
}

View file

@ -132,7 +132,7 @@ abstract class RenderSliverAppBar extends RenderSliver with RenderObjectWithChil
///
/// If there is no child, this should return 0.0.
@override
double childPosition(@checked RenderObject child) => super.childPosition(child);
double childMainAxisPosition(@checked RenderObject child) => super.childMainAxisPosition(child);
@override
bool hitTestChildren(HitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) {
@ -155,16 +155,16 @@ abstract class RenderSliverAppBar extends RenderSliver with RenderObjectWithChil
assert(constraints.axisDirection != null);
switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
case AxisDirection.up:
offset += new Offset(0.0, geometry.paintExtent - childPosition(child) - childExtent);
offset += new Offset(0.0, geometry.paintExtent - childMainAxisPosition(child) - childExtent);
break;
case AxisDirection.down:
offset += new Offset(0.0, childPosition(child));
offset += new Offset(0.0, childMainAxisPosition(child));
break;
case AxisDirection.left:
offset += new Offset(geometry.paintExtent - childPosition(child) - childExtent, 0.0);
offset += new Offset(geometry.paintExtent - childMainAxisPosition(child) - childExtent, 0.0);
break;
case AxisDirection.right:
offset += new Offset(childPosition(child), 0.0);
offset += new Offset(childMainAxisPosition(child), 0.0);
break;
}
context.paintChild(child, offset);
@ -180,7 +180,7 @@ abstract class RenderSliverAppBar extends RenderSliver with RenderObjectWithChil
description.add('maxExtent: EXCEPTION (${e.runtimeType}) WHILE COMPUTING MAX EXTENT');
}
try {
description.add('child position: ${childPosition(child).toStringAsFixed(1)}');
description.add('child position: ${childMainAxisPosition(child).toStringAsFixed(1)}');
} catch (e) {
description.add('child position: EXCEPTION (${e.runtimeType}) WHILE COMPUTING CHILD POSITION');
}
@ -216,7 +216,7 @@ abstract class RenderSliverScrollingAppBar extends RenderSliverAppBar {
}
@override
double childPosition(RenderBox child) {
double childMainAxisPosition(RenderBox child) {
assert(child == this.child);
return _childPosition;
}
@ -246,7 +246,7 @@ abstract class RenderSliverPinnedAppBar extends RenderSliverAppBar {
}
@override
double childPosition(RenderBox child) {
double childMainAxisPosition(RenderBox child) {
assert(child == this.child);
return constraints?.overlap;
}
@ -298,7 +298,7 @@ abstract class RenderSliverFloatingAppBar extends RenderSliverAppBar {
}
@override
double childPosition(RenderBox child) {
double childMainAxisPosition(RenderBox child) {
assert(child == this.child);
return _childPosition;
}

View file

@ -184,17 +184,18 @@ class RenderSliverBlock extends RenderSliverMultiBoxAdaptor {
collectGarbage(leadingGarbage, trailingGarbage);
assert(debugAssertChildListIsNonEmptyAndContiguous());
double estimatedTotalExtent;
double estimatedMaxScrollOffset;
if (reachedEnd) {
estimatedTotalExtent = endScrollOffset;
estimatedMaxScrollOffset = endScrollOffset;
} else {
estimatedTotalExtent = childManager.estimateScrollOffsetExtent(
estimatedMaxScrollOffset = childManager.estimateMaxScrollOffset(
constraints,
firstIndex: indexOf(firstChild),
lastIndex: indexOf(lastChild),
leadingScrollOffset: offsetOf(firstChild),
trailingScrollOffset: endScrollOffset,
);
assert(estimatedTotalExtent >= endScrollOffset - offsetOf(firstChild));
assert(estimatedMaxScrollOffset >= endScrollOffset - offsetOf(firstChild));
}
final double paintedExtent = calculatePaintOffset(
constraints,
@ -202,9 +203,9 @@ class RenderSliverBlock extends RenderSliverMultiBoxAdaptor {
to: endScrollOffset,
);
geometry = new SliverGeometry(
scrollExtent: estimatedTotalExtent,
scrollExtent: estimatedMaxScrollOffset,
paintExtent: paintedExtent,
maxPaintExtent: estimatedTotalExtent,
maxPaintExtent: estimatedMaxScrollOffset,
// Conservative to avoid flickering away the clip during scroll.
hasVisualOverflow: endScrollOffset > targetEndScrollOffset || constraints.scrollOffset > 0.0,
);

View file

@ -0,0 +1,426 @@
// 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 'object.dart';
import 'sliver.dart';
import 'sliver_multi_box_adaptor.dart';
/// Describes the placement of a child in a [RenderSliverGrid].
///
/// See also:
///
/// * [SliverGridDelegate.getGeometryForChildIndex], which returns this object
/// to describe the child's placement.
/// * [RenderSliverGrid], which uses this class during its
/// [RenderSliverGrid.performLayout] method.
class SliverGridGeometry {
/// Creates an object that describes the placement of a child in a [RenderSliverGrid].
const SliverGridGeometry({
this.scrollOffset,
this.crossAxisOffset,
this.mainAxisExtent,
this.crossAxisExtent,
});
/// The scroll offset of the leading edge of the child relative to the leading
/// edge of the parent.
final double scrollOffset;
/// The offset of the child in the non-scrolling axis.
///
/// If the scroll axis is vertical, this offset is from the left-most edge of
/// the parent to the left-most edge of the child. If the scroll axis is
/// horizontal, this offset is from the top-most edge of the parent to the
/// top-most edge of the child.
final double crossAxisOffset;
/// The extent of the child in the scrolling axis.
///
/// If the scroll axis is vertical, this extent is the child's height. If the
/// scroll axis is horizontal, this extent is the child's width.
final double mainAxisExtent;
/// The extent of the child in the non-scrolling axis.
///
/// If the scroll axis is vertical, this extent is the child's width. If the
/// scroll axis is horizontal, this extent is the child's height.
final double crossAxisExtent;
/// Returns a tight [BoxConstraints] that forces the child to have the
/// required size.
BoxConstraints getBoxConstraints(SliverConstraints constraints) {
return constraints.asBoxConstraints(
minExtent: mainAxisExtent,
maxExtent: mainAxisExtent,
crossAxisExtent: crossAxisExtent,
);
}
}
class SliverGridParentData extends SliverMultiBoxAdaptorParentData {
double crossAxisOffset;
@override
String toString() => 'crossAxisOffset=$crossAxisOffset; ${super.toString()}';
}
abstract class SliverGridDelegate {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const SliverGridDelegate();
int getMinChildIndexForScrollOffset(SliverConstraints constraints, double scrollOffset);
int getMaxChildIndexForScrollOffset(SliverConstraints constraints, double scrollOffset);
SliverGridGeometry getGeometryForChildIndex(SliverConstraints constraints, int index);
double estimateMaxScrollOffset(SliverConstraints constraints, int childCount);
bool shouldRelayout(@checked SliverGridDelegate oldDelegate);
}
class SliverGridDelegateWithFixedCrossAxisCount extends SliverGridDelegate {
const SliverGridDelegateWithFixedCrossAxisCount({
@required this.crossAxisCount,
this.mainAxisSpacing: 0.0,
this.crossAxisSpacing: 0.0,
this.childAspectRatio: 1.0,
});
/// The number of children in the cross axis.
final int crossAxisCount;
/// The number of logical pixels between each child along the main axis.
final double mainAxisSpacing;
/// The number of logical pixels between each child along the cross axis.
final double crossAxisSpacing;
/// The ratio of the cross-axis to the main-axis extent of each child.
final double childAspectRatio;
bool _debugAssertIsValid() {
assert(crossAxisCount > 0);
assert(mainAxisSpacing >= 0.0);
assert(crossAxisSpacing >= 0.0);
assert(childAspectRatio > 0.0);
return true;
}
double _getMainAxisStride(double crossAxisExtent) {
final double usableCrossAxisExtent = crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1);
final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount;
final double childMainAxisExtent = childCrossAxisExtent / childAspectRatio;
return childMainAxisExtent + mainAxisSpacing;
}
@override
int getMinChildIndexForScrollOffset(SliverConstraints constraints, double scrollOffset) {
assert(_debugAssertIsValid());
return crossAxisCount * (scrollOffset ~/ _getMainAxisStride(constraints.crossAxisExtent));
}
@override
int getMaxChildIndexForScrollOffset(SliverConstraints constraints, double scrollOffset) {
assert(_debugAssertIsValid());
final int mainAxisCount = (scrollOffset / _getMainAxisStride(constraints.crossAxisExtent)).ceil();
return math.max(0, crossAxisCount * mainAxisCount - 1);
}
@override
SliverGridGeometry getGeometryForChildIndex(SliverConstraints constraints, int index) {
final double usableCrossAxisExtent = constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1);
final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount;
final double childMainAxisExtent = childCrossAxisExtent / childAspectRatio;
final double mainAxisStride = childMainAxisExtent + mainAxisSpacing;
final double crossAxisStrid = childCrossAxisExtent + crossAxisSpacing;
assert(mainAxisStride == _getMainAxisStride(constraints.crossAxisExtent));
return new SliverGridGeometry(
scrollOffset: (index ~/ crossAxisCount) * mainAxisStride,
crossAxisOffset: (index % crossAxisCount) * crossAxisStrid,
mainAxisExtent: childMainAxisExtent,
crossAxisExtent: childCrossAxisExtent,
);
}
@override
double estimateMaxScrollOffset(SliverConstraints constraints, int childCount) {
if (childCount == null)
return null;
final int mainAxisCount = ((childCount - 1) / crossAxisCount).floor() + 1;
return _getMainAxisStride(constraints.crossAxisExtent) * mainAxisCount - mainAxisSpacing;
}
@override
bool shouldRelayout(SliverGridDelegateWithFixedCrossAxisCount oldDelegate) {
return oldDelegate.crossAxisCount != crossAxisCount
|| oldDelegate.mainAxisSpacing != mainAxisSpacing
|| oldDelegate.crossAxisSpacing != crossAxisSpacing
|| oldDelegate.childAspectRatio != childAspectRatio;
}
}
/// A [GridDelegate] that fills the width with a variable number of tiles.
///
/// This delegate will select a tile width that is as large as possible subject
/// to the following conditions:
///
/// - The tile width evenly divides the width of the grid.
/// - The tile width is at most [maxTileWidth].
class SliverGridDelegateWithMaxCrossAxisExtent extends SliverGridDelegate {
/// Creates a grid delegate that uses a max tile width.
///
/// The [maxTileWidth] argument must not be null.
const SliverGridDelegateWithMaxCrossAxisExtent({
@required this.maxCrossAxisExtent,
this.mainAxisSpacing: 0.0,
this.crossAxisSpacing: 0.0,
this.childAspectRatio: 1.0,
});
/// The number of children in the cross axis.
final double maxCrossAxisExtent;
/// The number of logical pixels between each child along the main axis.
final double mainAxisSpacing;
/// The number of logical pixels between each child along the cross axis.
final double crossAxisSpacing;
/// The ratio of the cross-axis to the main-axis extent of each child.
final double childAspectRatio;
bool _debugAssertIsValid() {
assert(maxCrossAxisExtent > 0.0);
assert(mainAxisSpacing >= 0.0);
assert(crossAxisSpacing >= 0.0);
assert(childAspectRatio > 0.0);
return true;
}
int _getCrossAxisCount(double crossAxisExtent) {
return (crossAxisExtent / (maxCrossAxisExtent + crossAxisSpacing)).ceil();
}
double _getMainAxisStride(double crossAxisExtent, int crossAxisCount) {
final double usableCrossAxisExtent = crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1);
final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount;
final double childMainAxisExtent = childCrossAxisExtent / childAspectRatio;
return childMainAxisExtent + mainAxisSpacing;
}
@override
int getMinChildIndexForScrollOffset(SliverConstraints constraints, double scrollOffset) {
assert(_debugAssertIsValid());
final double crossAxisExtent = constraints.crossAxisExtent;
final int crossAxisCount = _getCrossAxisCount(crossAxisExtent);
return crossAxisCount * (scrollOffset ~/ _getMainAxisStride(crossAxisExtent, crossAxisCount));
}
@override
int getMaxChildIndexForScrollOffset(SliverConstraints constraints, double scrollOffset) {
assert(_debugAssertIsValid());
final double crossAxisExtent = constraints.crossAxisExtent;
final int crossAxisCount = _getCrossAxisCount(crossAxisExtent);
final int mainAxisCount = (scrollOffset / _getMainAxisStride(crossAxisExtent, crossAxisCount)).ceil();
return math.max(0, crossAxisCount * mainAxisCount - 1);
}
@override
SliverGridGeometry getGeometryForChildIndex(SliverConstraints constraints, int index) {
final int crossAxisCount = _getCrossAxisCount(constraints.crossAxisExtent);
final double usableCrossAxisExtent = constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1);
final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount;
final double childMainAxisExtent = childCrossAxisExtent / childAspectRatio;
final double mainAxisStride = childMainAxisExtent + mainAxisSpacing;
final double crossAxisStrid = childCrossAxisExtent + crossAxisSpacing;
assert(mainAxisStride == _getMainAxisStride(constraints.crossAxisExtent, crossAxisCount));
return new SliverGridGeometry(
scrollOffset: (index ~/ crossAxisCount) * mainAxisStride,
crossAxisOffset: (index % crossAxisCount) * crossAxisStrid,
mainAxisExtent: childMainAxisExtent,
crossAxisExtent: childCrossAxisExtent,
);
}
@override
double estimateMaxScrollOffset(SliverConstraints constraints, int childCount) {
if (childCount == null)
return null;
final double crossAxisExtent = constraints.crossAxisExtent;
final int crossAxisCount = _getCrossAxisCount(crossAxisExtent);
final int mainAxisCount = ((childCount - 1) / crossAxisCount).floor() + 1;
return _getMainAxisStride(crossAxisExtent, crossAxisCount) * mainAxisCount - mainAxisSpacing;
}
@override
bool shouldRelayout(SliverGridDelegateWithMaxCrossAxisExtent oldDelegate) {
return oldDelegate.maxCrossAxisExtent != maxCrossAxisExtent
|| oldDelegate.mainAxisSpacing != mainAxisSpacing
|| oldDelegate.crossAxisSpacing != crossAxisSpacing
|| oldDelegate.childAspectRatio != childAspectRatio;
}
}
class RenderSliverGrid extends RenderSliverMultiBoxAdaptor {
RenderSliverGrid({
@required RenderSliverBoxChildManager childManager,
@required SliverGridDelegate gridDelegate,
}) : _gridDelegate = gridDelegate,
super(childManager: childManager) {
gridDelegate != null;
}
@override
void setupParentData(RenderObject child) {
if (child.parentData is! SliverGridParentData)
child.parentData = new SliverGridParentData();
}
SliverGridDelegate get gridDelegate => _gridDelegate;
SliverGridDelegate _gridDelegate;
set gridDelegate(SliverGridDelegate newDelegate) {
assert(newDelegate != null);
if (_gridDelegate == newDelegate)
return;
if (newDelegate.runtimeType != _gridDelegate.runtimeType ||
newDelegate.shouldRelayout(_gridDelegate))
markNeedsLayout();
_gridDelegate = newDelegate;
}
@override
double childCrossAxisPosition(RenderBox child) {
final SliverGridParentData childParentData = child.parentData;
return childParentData.crossAxisOffset;
}
@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;
final int firstIndex = _gridDelegate.getMinChildIndexForScrollOffset(constraints, scrollOffset);
final int targetLastIndex = _gridDelegate.getMaxChildIndexForScrollOffset(constraints, targetEndScrollOffset);
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);
}
final SliverGridGeometry firstChildGridGeometry = _gridDelegate
.getGeometryForChildIndex(constraints, firstIndex);
double leadingScrollOffset = firstChildGridGeometry.scrollOffset;
double trailingScrollOffset = firstChildGridGeometry.scrollOffset;
if (firstChild == null) {
if (!addInitialChild(index: firstIndex,
scrollOffset: firstChildGridGeometry.scrollOffset)) {
// There are no children.
geometry = SliverGeometry.zero;
return;
}
}
RenderBox trailingChildWithLayout;
for (int index = indexOf(firstChild) - 1; index >= firstIndex; --index) {
final SliverGridGeometry gridGeometry = _gridDelegate
.getGeometryForChildIndex(constraints, index);
final RenderBox child = insertAndLayoutLeadingChild(
gridGeometry.getBoxConstraints(constraints));
final SliverGridParentData childParentData = child.parentData;
childParentData.scrollOffset = gridGeometry.scrollOffset;
childParentData.crossAxisOffset = gridGeometry.crossAxisOffset;
assert(childParentData.index == index);
trailingChildWithLayout ??= child;
if (gridGeometry.scrollOffset > trailingScrollOffset)
trailingScrollOffset = gridGeometry.scrollOffset;
}
assert(offsetOf(firstChild) <= scrollOffset);
if (trailingChildWithLayout == null) {
firstChild.layout(firstChildGridGeometry.getBoxConstraints(constraints));
final SliverGridParentData childParentData = firstChild.parentData;
childParentData.crossAxisOffset = firstChildGridGeometry.crossAxisOffset;
assert(childParentData.scrollOffset ==
firstChildGridGeometry.scrollOffset);
trailingChildWithLayout = firstChild;
}
for (int index = indexOf(trailingChildWithLayout) + 1; index <=
targetLastIndex; ++index) {
final SliverGridGeometry gridGeometry = _gridDelegate
.getGeometryForChildIndex(constraints, index);
final BoxConstraints childConstraints = gridGeometry.getBoxConstraints(
constraints);
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 SliverGridParentData childParentData = child.parentData;
childParentData.scrollOffset = gridGeometry.scrollOffset;
childParentData.crossAxisOffset = gridGeometry.crossAxisOffset;
assert(childParentData.index == index);
if (gridGeometry.scrollOffset > trailingScrollOffset)
trailingScrollOffset = gridGeometry.scrollOffset;
}
final int lastIndex = indexOf(lastChild);
assert(debugAssertChildListIsNonEmptyAndContiguous());
assert(indexOf(firstChild) == firstIndex);
assert(lastIndex <= targetLastIndex);
final double estimatedTotalExtent = childManager.estimateMaxScrollOffset(
constraints,
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 complexity.
hasVisualOverflow: true,
);
assert(childManager.debugAssertChildListLocked());
}
}

View file

@ -14,7 +14,9 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor {
RenderSliverList({
@required RenderSliverBoxChildManager childManager,
double itemExtent,
}) : _itemExtent = itemExtent, super(childManager: childManager);
}) : _itemExtent = itemExtent, super(childManager: childManager) {
assert(itemExtent != null);
}
/// The main-axis extent of each item in the list.
double get itemExtent => _itemExtent;
@ -105,7 +107,8 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor {
assert(indexOf(firstChild) == firstIndex);
assert(lastIndex <= targetLastIndex);
final double estimatedTotalExtent = childManager.estimateScrollOffsetExtent(
final double estimatedMaxScrollOffset = childManager.estimateMaxScrollOffset(
constraints,
firstIndex: firstIndex,
lastIndex: lastIndex,
leadingScrollOffset: leadingScrollOffset,
@ -119,9 +122,9 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor {
);
geometry = new SliverGeometry(
scrollExtent: estimatedTotalExtent,
scrollExtent: estimatedMaxScrollOffset,
paintExtent: paintedExtent,
maxPaintExtent: estimatedTotalExtent,
maxPaintExtent: estimatedMaxScrollOffset,
// Conservative to avoid flickering away the clip during scroll.
hasVisualOverflow: lastIndex >= targetLastIndex || constraints.scrollOffset > 0.0,
);

View file

@ -61,7 +61,7 @@ abstract class RenderSliverBoxChildManager {
/// 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({
double estimateMaxScrollOffset(SliverConstraints constraints, {
int firstIndex,
int lastIndex,
double leadingScrollOffset,
@ -304,7 +304,7 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
}
@override
double childPosition(RenderBox child) {
double childMainAxisPosition(RenderBox child) {
return offsetOf(child) - constraints.scrollOffset;
}
@ -319,37 +319,46 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
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;
Offset mainAxisUnit, crossAxisUnit, originOffset;
bool addExtent;
switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
case AxisDirection.up:
unitOffset = const Offset(0.0, -1.0);
mainAxisUnit = const Offset(0.0, -1.0);
crossAxisUnit = const Offset(1.0, 0.0);
originOffset = offset + new Offset(0.0, geometry.paintExtent);
addExtent = true;
break;
case AxisDirection.right:
unitOffset = const Offset(1.0, 0.0);
mainAxisUnit = const Offset(1.0, 0.0);
crossAxisUnit = const Offset(0.0, 1.0);
originOffset = offset;
addExtent = false;
break;
case AxisDirection.down:
unitOffset = const Offset(0.0, 1.0);
mainAxisUnit = const Offset(0.0, 1.0);
crossAxisUnit = const Offset(1.0, 0.0);
originOffset = offset;
addExtent = false;
break;
case AxisDirection.left:
unitOffset = const Offset(-1.0, 0.0);
mainAxisUnit = const Offset(-1.0, 0.0);
crossAxisUnit = const Offset(0.0, 1.0);
originOffset = offset + new Offset(geometry.paintExtent, 0.0);
addExtent = true;
break;
}
assert(unitOffset != null);
assert(mainAxisUnit != null);
assert(addExtent != null);
RenderBox child = firstChild;
while (child != null) {
Offset childOffset = originOffset + unitOffset * childPosition(child);
final double mainAxisDelta = childMainAxisPosition(child);
final double crossAxisDelta = childCrossAxisPosition(child);
Offset childOffset = new Offset(
originOffset.dx + mainAxisUnit.dx * mainAxisDelta + crossAxisUnit.dx * crossAxisDelta,
originOffset.dy + mainAxisUnit.dy * mainAxisDelta + crossAxisUnit.dy * crossAxisDelta,
);
if (addExtent)
childOffset += unitOffset * paintExtentOf(child);
childOffset += mainAxisUnit * paintExtentOf(child);
context.paintChild(child, childOffset);
child = childAfter(child);
}

View file

@ -262,17 +262,24 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
@override
bool hitTestChildren(HitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) {
if (child.geometry.hitTestExtent > 0.0)
return child.hitTest(result, mainAxisPosition: mainAxisPosition - childPosition(child), crossAxisPosition: crossAxisPosition - startPadding);
return child.hitTest(result, mainAxisPosition: mainAxisPosition - childMainAxisPosition(child), crossAxisPosition: crossAxisPosition - childCrossAxisPosition(child));
return false;
}
@override
double childPosition(RenderSliver child) {
double childMainAxisPosition(RenderSliver child) {
assert(child != null);
assert(child == this.child);
return calculatePaintOffset(constraints, from: 0.0, to: beforePadding);
}
@override
double childCrossAxisPosition(RenderSliver child) {
assert(child != null);
assert(child == this.child);
return startPadding;
}
@override
void applyPaintTransform(RenderObject child, Matrix4 transform) {
assert(child != null);

View file

@ -3,21 +3,31 @@
// found in the LICENSE file.
import 'package:flutter/rendering.dart';
import 'package:meta/meta.dart';
import 'framework.dart';
import 'basic.dart';
import 'scrollable.dart';
import 'sliver.dart';
AxisDirection _getDirection(BuildContext context, Axis scrollDirection) {
// TODO(abarth): Consider reading direction.
switch (scrollDirection) {
case Axis.horizontal:
return AxisDirection.right;
case Axis.vertical:
return AxisDirection.down;
}
return null;
}
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);
@ -25,50 +35,109 @@ class ScrollView extends StatelessWidget {
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) {
Widget _buildChildLayout() {
final SliverChildListDelegate delegate = new SliverChildListDelegate(children);
Widget sliver;
if (itemExtent == null) {
sliver = new SliverBlock(delegate: delegate);
} else {
sliver = new SliverList(
if (itemExtent != null) {
return new SliverList(
delegate: delegate,
itemExtent: itemExtent,
);
}
return new SliverBlock(delegate: delegate);
}
@override
Widget build(BuildContext context) {
Widget sliver = _buildChildLayout();
if (padding != null)
sliver = new SliverPadding(padding: padding, child: sliver);
return new ScrollableViewport2(
axisDirection: _getDirection(context),
anchor: anchor,
axisDirection: _getDirection(context, scrollDirection),
initialScrollOffset: initialScrollOffset,
slivers: <Widget>[ sliver ],
);
}
}
class ScrollGrid extends StatelessWidget {
ScrollGrid({
Key key,
this.padding,
this.scrollDirection: Axis.vertical,
this.initialScrollOffset: 0.0,
this.gridDelegate,
this.children,
}) : super(key: key);
ScrollGrid.count({
Key key,
this.padding,
this.scrollDirection: Axis.vertical,
this.initialScrollOffset: 0.0,
@required int crossAxisCount,
double mainAxisSpacing: 0.0,
double crossAxisSpacing: 0.0,
double childAspectRatio: 1.0,
this.children,
}) : gridDelegate = new SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
mainAxisSpacing: mainAxisSpacing,
crossAxisSpacing: crossAxisSpacing,
childAspectRatio: childAspectRatio,
), super(key: key);
ScrollGrid.extent({
Key key,
this.padding,
this.scrollDirection: Axis.vertical,
this.initialScrollOffset: 0.0,
@required double maxCrossAxisExtent,
double mainAxisSpacing: 0.0,
double crossAxisSpacing: 0.0,
double childAspectRatio: 1.0,
this.children,
}) : gridDelegate = new SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: maxCrossAxisExtent,
mainAxisSpacing: mainAxisSpacing,
crossAxisSpacing: crossAxisSpacing,
childAspectRatio: childAspectRatio,
), super(key: key);
final EdgeInsets padding;
final Axis scrollDirection;
final double initialScrollOffset;
final SliverGridDelegate gridDelegate;
final List<Widget> children;
@override
Widget build(BuildContext context) {
final SliverChildListDelegate delegate = new SliverChildListDelegate(children);
Widget sliver = new SliverGrid(
delegate: delegate,
gridDelegate: gridDelegate,
);
if (padding != null)
sliver = new SliverPadding(padding: padding, child: sliver);
return new ScrollableViewport2(
axisDirection: _getDirection(context, scrollDirection),
initialScrollOffset: initialScrollOffset,
center: center,
slivers: <Widget>[ sliver ],
);
}

View file

@ -10,6 +10,11 @@ import 'package:flutter/rendering.dart';
import 'framework.dart';
import 'basic.dart';
export 'package:flutter/rendering.dart' show
SliverGridDelegate,
SliverGridDelegateWithFixedCrossAxisCount,
SliverGridDelegateWithMaxCrossAxisExtent;
abstract class SliverChildDelegate {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
@ -17,24 +22,23 @@ abstract class SliverChildDelegate {
Widget build(BuildContext context, int index);
bool shouldRebuild(@checked SliverChildDelegate oldDelegate);
/// Returns an estimate of the number of children this delegate will build.
///
/// Used to estimate the maximum scroll offset if [estimateMaxScrollOffset]
/// returns null.
///
/// Return null if there are an unbounded number of children or if it would
/// be too difficult to estimate the number of children.
int get estimatedChildCount => null;
int get childCount;
double estimateScrollOffsetExtent(
double estimateMaxScrollOffset(
int firstIndex,
int lastIndex,
double leadingScrollOffset,
double trailingScrollOffset,
) {
final int childCount = this.childCount;
if (lastIndex == childCount - 1)
return trailingScrollOffset;
final int reifiedCount = lastIndex - firstIndex + 1;
final double averageExtent = (trailingScrollOffset - leadingScrollOffset) / reifiedCount;
final int remainingCount = childCount - lastIndex - 1;
return trailingScrollOffset + averageExtent * remainingCount;
}
) => null;
bool shouldRebuild(@checked SliverChildDelegate oldDelegate);
}
// ///
@ -64,13 +68,13 @@ class SliverChildListDelegate extends SliverChildDelegate {
return children[index];
}
@override
int get estimatedChildCount => children.length;
@override
bool shouldRebuild(@checked SliverChildListDelegate oldDelegate) {
return children != oldDelegate.children;
}
@override
int get childCount => children.length;
}
abstract class SliverMultiBoxAdaptorWidget extends RenderObjectWidget {
@ -89,6 +93,22 @@ abstract class SliverMultiBoxAdaptorWidget extends RenderObjectWidget {
@override
RenderSliverMultiBoxAdaptor createRenderObject(BuildContext context);
double estimateMaxScrollOffset(
SliverConstraints constraints,
int firstIndex,
int lastIndex,
double leadingScrollOffset,
double trailingScrollOffset,
) {
assert(lastIndex >= firstIndex);
return delegate.estimateMaxScrollOffset(
firstIndex,
lastIndex,
leadingScrollOffset,
trailingScrollOffset,
);
}
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
@ -130,6 +150,44 @@ class SliverList extends SliverMultiBoxAdaptorWidget {
}
}
class SliverGrid extends SliverMultiBoxAdaptorWidget {
SliverGrid({
Key key,
@required SliverChildDelegate delegate,
@required this.gridDelegate,
}) : super(key: key, delegate: delegate);
final SliverGridDelegate gridDelegate;
@override
RenderSliverGrid createRenderObject(BuildContext context) {
final SliverMultiBoxAdaptorElement element = context;
return new RenderSliverGrid(childManager: element, gridDelegate: gridDelegate);
}
@override
void updateRenderObject(BuildContext context, RenderSliverGrid renderObject) {
renderObject.gridDelegate = gridDelegate;
}
@override
double estimateMaxScrollOffset(
SliverConstraints constraints,
int firstIndex,
int lastIndex,
double leadingScrollOffset,
double trailingScrollOffset,
) {
return super.estimateMaxScrollOffset(
constraints,
firstIndex,
lastIndex,
leadingScrollOffset,
trailingScrollOffset,
) ?? gridDelegate.estimateMaxScrollOffset(constraints, delegate.estimatedChildCount);
}
}
class SliverMultiBoxAdaptorElement extends RenderObjectElement implements RenderSliverBoxChildManager {
SliverMultiBoxAdaptorElement(SliverMultiBoxAdaptorWidget widget) : super(widget);
@ -241,19 +299,41 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render
});
}
double _extrapolateMaxScrollOffset(
int firstIndex,
int lastIndex,
double leadingScrollOffset,
double trailingScrollOffset,
) {
final int childCount = widget.delegate.estimatedChildCount;
if (childCount == null)
return double.INFINITY;
if (lastIndex == childCount - 1)
return trailingScrollOffset;
final int reifiedCount = lastIndex - firstIndex + 1;
final double averageExtent = (trailingScrollOffset - leadingScrollOffset) / reifiedCount;
final int remainingCount = childCount - lastIndex - 1;
return trailingScrollOffset + averageExtent * remainingCount;
}
@override
double estimateScrollOffsetExtent({
double estimateMaxScrollOffset(SliverConstraints constraints, {
int firstIndex,
int lastIndex,
double leadingScrollOffset,
double trailingScrollOffset,
}) {
assert(lastIndex >= firstIndex);
return widget.delegate.estimateScrollOffsetExtent(
return widget.estimateMaxScrollOffset(
constraints,
firstIndex,
lastIndex,
leadingScrollOffset,
trailingScrollOffset
trailingScrollOffset,
) ?? _extrapolateMaxScrollOffset(
firstIndex,
lastIndex,
leadingScrollOffset,
trailingScrollOffset,
);
}

View file

@ -43,7 +43,7 @@ class TestRenderSliverBoxChildManager extends RenderSliverBoxChildManager {
}
@override
double estimateScrollOffsetExtent({
double estimateMaxScrollOffset(SliverConstraints constraints, {
int firstIndex,
int lastIndex,
double leadingScrollOffset,

View file

@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
final Key blockKey = new Key('test');
@ -120,7 +121,7 @@ void main() {
expect(key.currentState.scrollOffset, 0.0);
});
testWidgets('SliverBlockChildListDelegate.estimateScrollOffsetExtent hits end', (WidgetTester tester) async {
testWidgets('SliverBlockChildListDelegate.estimateMaxScrollOffset hits end', (WidgetTester tester) async {
SliverChildListDelegate delegate = new SliverChildListDelegate(<Widget>[
new Container(),
new Container(),
@ -129,6 +130,23 @@ void main() {
new Container(),
]);
expect(delegate.estimateScrollOffsetExtent(3, 4, 25.0, 26.0), equals(26.0));
await tester.pumpWidget(new ScrollableViewport2(
slivers: <Widget>[
new SliverBlock(
delegate: delegate,
),
],
));
final SliverMultiBoxAdaptorElement element = tester.element(find.byType(SliverBlock));
final double maxScrollOffset = element.estimateMaxScrollOffset(
null,
firstIndex: 3,
lastIndex: 4,
leadingScrollOffset: 25.0,
trailingScrollOffset: 26.0
);
expect(maxScrollOffset, equals(26.0));
});
}

View file

@ -0,0 +1,306 @@
// 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_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
import 'states.dart';
void main() {
testWidgets('Empty ScrollGrid', (WidgetTester tester) async {
await tester.pumpWidget(new ScrollGrid.count(
crossAxisCount: 4,
children: const <Widget>[],
));
});
testWidgets('ScrollGrid.count control test', (WidgetTester tester) async {
List<String> log = <String>[];
await tester.pumpWidget(new ScrollGrid.count(
crossAxisCount: 4,
children: kStates.map((String state) {
return new GestureDetector(
onTap: () {
log.add(state);
},
child: new Container(
decoration: const BoxDecoration(
backgroundColor: const Color(0xFF0000FF),
),
child: new Text(state),
),
);
}).toList(),
));
expect(tester.getSize(find.text('Arkansas')), equals(const Size(200.0, 200.0)));
for (int i = 0; i < 8; ++i) {
await tester.tap(find.text(kStates[i]));
expect(log, equals(<String>[kStates[i]]));
log.clear();
}
expect(find.text(kStates[12]), findsNothing);
expect(find.text('Nevada'), findsNothing);
await tester.scroll(find.text('Arkansas'), const Offset(0.0, -200.0));
await tester.pump();
for (int i = 0; i < 4; ++i)
expect(find.text(kStates[i]), findsNothing);
for (int i = 4; i < 12; ++i) {
await tester.tap(find.text(kStates[i]));
expect(log, equals(<String>[kStates[i]]));
log.clear();
}
await tester.scroll(find.text('Delaware'), const Offset(0.0, -4000.0));
await tester.pump();
expect(find.text('Alabama'), findsNothing);
expect(find.text('Pennsylvania'), findsNothing);
expect(tester.getCenter(find.text('Tennessee')),
equals(const Point(300.0, 100.0)));
await tester.tap(find.text('Tennessee'));
expect(log, equals(<String>['Tennessee']));
log.clear();
await tester.scroll(find.text('Tennessee'), const Offset(0.0, 200.0));
await tester.pump();
await tester.tap(find.text('Tennessee'));
expect(log, equals(<String>['Tennessee']));
log.clear();
await tester.tap(find.text('Pennsylvania'));
expect(log, equals(<String>['Pennsylvania']));
log.clear();
});
testWidgets('ScrollGrid.extent control test', (WidgetTester tester) async {
List<String> log = <String>[];
await tester.pumpWidget(new ScrollGrid.extent(
maxCrossAxisExtent: 200.0,
children: kStates.map((String state) {
return new GestureDetector(
onTap: () {
log.add(state);
},
child: new Container(
decoration: const BoxDecoration(
backgroundColor: const Color(0xFF0000FF),
),
child: new Text(state),
),
);
}).toList(),
));
expect(tester.getSize(find.text('Arkansas')), equals(const Size(200.0, 200.0)));
for (int i = 0; i < 8; ++i) {
await tester.tap(find.text(kStates[i]));
expect(log, equals(<String>[kStates[i]]));
log.clear();
}
expect(find.text('Nevada'), findsNothing);
await tester.scroll(find.text('Arkansas'), const Offset(0.0, -4000.0));
await tester.pump();
expect(find.text('Alabama'), findsNothing);
expect(tester.getCenter(find.text('Tennessee')),
equals(const Point(300.0, 100.0)));
await tester.tap(find.text('Tennessee'));
expect(log, equals(<String>['Tennessee']));
log.clear();
});
testWidgets('ScrollGrid large scroll jump', (WidgetTester tester) async {
List<int> log = <int>[];
await tester.pumpWidget(
new ScrollGrid.extent(
scrollDirection: Axis.horizontal,
maxCrossAxisExtent: 200.0,
childAspectRatio: 0.75,
children: new List<Widget>.generate(80, (int i) {
return new Builder(
builder: (BuildContext context) {
log.add(i);
return new Container(
child: new Text('$i'),
);
}
);
}),
),
);
expect(tester.getSize(find.text('4')), equals(const Size(200.0 / 0.75, 200.0)));
expect(log, equals(<int>[
0, 1, 2, // col 0
3, 4, 5, // col 1
6, 7, 8, // col 2
]));
log.clear();
Scrollable2State state = tester.state(find.byType(Scrollable2));
AbsoluteScrollPosition position = state.position;
position.jumpTo(3025.0);
expect(log, isEmpty);
await tester.pump();
expect(log, equals(<int>[
33, 34, 35, // col 11
36, 37, 38, // col 12
39, 40, 41, // col 13
42, 43, 44, // col 14
]));
log.clear();
position.jumpTo(975.0);
expect(log, isEmpty);
await tester.pump();
expect(log, equals(<int>[
9, 10, 11, // col 3
12, 13, 14, // col 4
15, 16, 17, // col 5
18, 19, 20, // col 6
]));
log.clear();
});
testWidgets('ScrollGrid - change crossAxisCount', (WidgetTester tester) async {
List<int> log = <int>[];
await tester.pumpWidget(
new ScrollGrid(
gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
),
children: new List<Widget>.generate(40, (int i) {
return new Builder(
builder: (BuildContext context) {
log.add(i);
return new Container(
child: new Text('$i'),
);
}
);
}),
),
);
expect(tester.getSize(find.text('4')), equals(const Size(200.0, 200.0)));
expect(log, equals(<int>[
0, 1, 2, 3, // row 0
4, 5, 6, 7, // row 1
8, 9, 10, 11, // row 2
]));
log.clear();
await tester.pumpWidget(
new ScrollGrid(
gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
children: new List<Widget>.generate(40, (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, // row 0
4, 5, 6, 7, // row 1
8, 9, 10, 11, // row 2
]));
log.clear();
expect(tester.getSize(find.text('3')), equals(const Size(400.0, 400.0)));
expect(find.text('4'), findsNothing);
});
testWidgets('ScrollGrid - change maxChildCrossAxisExtent', (WidgetTester tester) async {
List<int> log = <int>[];
await tester.pumpWidget(
new ScrollGrid(
gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200.0,
),
children: new List<Widget>.generate(40, (int i) {
return new Builder(
builder: (BuildContext context) {
log.add(i);
return new Container(
child: new Text('$i'),
);
}
);
}),
),
);
expect(tester.getSize(find.text('4')), equals(const Size(200.0, 200.0)));
expect(log, equals(<int>[
0, 1, 2, 3, // row 0
4, 5, 6, 7, // row 1
8, 9, 10, 11, // row 2
]));
log.clear();
await tester.pumpWidget(
new ScrollGrid(
gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 400.0,
),
children: new List<Widget>.generate(40, (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, // row 0
4, 5, 6, 7, // row 1
8, 9, 10, 11, // row 2
]));
log.clear();
expect(tester.getSize(find.text('3')), equals(const Size(400.0, 400.0)));
expect(find.text('4'), findsNothing);
});
}

View file

@ -5,65 +5,14 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
const List<String> _kStates = const <String>[
'Alabama',
'Alaska',
'Arizona',
'Arkansas',
'California',
'Colorado',
'Connecticut',
'Delaware',
'Florida',
'Georgia',
'Hawaii',
'Idaho',
'Illinois',
'Indiana',
'Iowa',
'Kansas',
'Kentucky',
'Louisiana',
'Maine',
'Maryland',
'Massachusetts',
'Michigan',
'Minnesota',
'Mississippi',
'Missouri',
'Montana',
'Nebraska',
'Nevada',
'New Hampshire',
'New Jersey',
'New Mexico',
'New York',
'North Carolina',
'North Dakota',
'Ohio',
'Oklahoma',
'Oregon',
'Pennsylvania',
'Rhode Island',
'South Carolina',
'South Dakota',
'Tennessee',
'Texas',
'Utah',
'Vermont',
'Virginia',
'Washington',
'West Virginia',
'Wisconsin',
'Wyoming',
];
import 'states.dart';
void main() {
testWidgets('ScrollView control test', (WidgetTester tester) async {
List<String> log = <String>[];
await tester.pumpWidget(new ScrollView(
children: _kStates.map<Widget>((String state) {
children: kStates.map<Widget>((String state) {
return new GestureDetector(
onTap: () {
log.add(state);
@ -99,7 +48,7 @@ void main() {
testWidgets('ScrollView restart ballistic activity out of range', (WidgetTester tester) async {
Widget buildScrollView(int n) {
return new ScrollView(
children: _kStates.take(n).map<Widget>((String state) {
children: kStates.take(n).map<Widget>((String state) {
return new Container(
height: 200.0,
decoration: const BoxDecoration(

View file

@ -0,0 +1,56 @@
// 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.
const List<String> kStates = const <String>[
'Alabama',
'Alaska',
'Arizona',
'Arkansas',
'California',
'Colorado',
'Connecticut',
'Delaware',
'Florida',
'Georgia',
'Hawaii',
'Idaho',
'Illinois',
'Indiana',
'Iowa',
'Kansas',
'Kentucky',
'Louisiana',
'Maine',
'Maryland',
'Massachusetts',
'Michigan',
'Minnesota',
'Mississippi',
'Missouri',
'Montana',
'Nebraska',
'Nevada',
'New Hampshire',
'New Jersey',
'New Mexico',
'New York',
'North Carolina',
'North Dakota',
'Ohio',
'Oklahoma',
'Oregon',
'Pennsylvania',
'Rhode Island',
'South Carolina',
'South Dakota',
'Tennessee',
'Texas',
'Utah',
'Vermont',
'Virginia',
'Washington',
'West Virginia',
'Wisconsin',
'Wyoming',
];