mirror of
https://github.com/flutter/flutter
synced 2024-10-13 19:52:53 +00:00
Revert "Revert "Add SliverGrid and ScrollGrid"" (#7780)
This commit is contained in:
parent
3a43fc88b6
commit
695302029f
|
@ -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(),
|
||||
)
|
||||
)
|
||||
]
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
426
packages/flutter/lib/src/rendering/sliver_grid.dart
Normal file
426
packages/flutter/lib/src/rendering/sliver_grid.dart
Normal 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());
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 ],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ class TestRenderSliverBoxChildManager extends RenderSliverBoxChildManager {
|
|||
}
|
||||
|
||||
@override
|
||||
double estimateScrollOffsetExtent({
|
||||
double estimateMaxScrollOffset(SliverConstraints constraints, {
|
||||
int firstIndex,
|
||||
int lastIndex,
|
||||
double leadingScrollOffset,
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
|
306
packages/flutter/test/widgets/scroll_grid_test.dart
Normal file
306
packages/flutter/test/widgets/scroll_grid_test.dart
Normal 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);
|
||||
});
|
||||
}
|
|
@ -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(
|
||||
|
|
56
packages/flutter/test/widgets/states.dart
Normal file
56
packages/flutter/test/widgets/states.dart
Normal 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',
|
||||
];
|
Loading…
Reference in a new issue