Revert "[dynamic_layouts] Add Wrap layout and updates to the README (#2513)" (#2534)

This reverts commit a2e65e9656a018692be2adc4f3e2d689b82adea5.
This commit is contained in:
stuartmorgan
2022-08-30 18:47:21 -04:00
committed by GitHub
parent a2e65e9656
commit 505aaa667a
8 changed files with 27 additions and 1113 deletions

View File

@ -13,138 +13,25 @@ and the Flutter guide for
[developing packages and plugins](https://flutter.dev/developing-packages).
-->
<!-- TODO(DavBot02): Put a short description of the package here that helps potential users
know whether this package might be useful for them.-->
TODO: Put a short description of the package here that helps potential users
know whether this package might be useful for them.
## Features
This package provides support for multi sized tiles and different layouts.
Currently the layouts that are implemented in this package are `Stagger` and
`Wrap`.
The following are some demos of how each of the grids look.
A stagger grid demo:
<!-- TODO(snat-s): Add stagger video demo -->
A wrap demo:
<!-- TODO(snat-s): Add wrap video demo -->
### Stagger Features
`DynamicGridView` is a subclass of `GridView` and gives access
to the `SliverGridDelegate`s that are already implemented in the Flutter
Framework. Some `SliverGridDelegate`s are `SliverGridDelegateWithMaxCrossAxisExtent` and
`SliverGridDelegateWithFixedCrossAxisCount`. This layout can be used with
`DynamicGridView.stagger`.
### Wrap Features
The Wrap layout is able to do runs of different widgets and adapt accordingly with
the sizes of the children. It can leave spacing with `mainAxisSpacing` and
`crossAxisSpacing`.
Having different sizes in only one of the axis is possible by
changing the values of `childCrossAxisExtent` and `childMainAxisExtent`. These
values by default are set to have loose constraints, but by giving `childCrossAxisExtent` a specific value like
100 pixels, it will make all of the children 100 pixels in the main axis.
This layout can be used with `DynamicGridView.wrap` and with
`DynamicGridView.builder` and `SliverGridDelegateWithWrapping` as the delegate.
TODO: List what your package can do. Maybe include images, gifs, or videos.
## Getting started
<!-- TODO(DavBot02): List prerequisites and provide or point to information on how to start using the package. -->
TODO: List prerequisites and provide or point to information on how to
start using the package.
## Usage
Use `DynamicGridView`s to access this layouts.
`DynamicGridView` has some constructors that use `SliverChildListDelegate` like
`.wrap` and `.stagger`. For a more efficient option that uses `SliverChildBuilderDelegate` use
`.builder`, it works the same as `GridView.builder`.
### Wrap
The following are simple examples of how to use `DynamicGridView.wrap`.
<?code-excerpt "dynamic_grid_view_wrap.dart" (Example)?>
```dart
final List<Widget> children = List.generate(
250,
(index) => Container(
height: index.isEven ? 100 : 50,
width: index.isEven ? 95 : 180,
color: index.isEven ? Colors.red : Colors.blue,
child: Center(child: Text('Item $index')),
),
);
DynamicGridView.wrap(
mainAxisSpacing: 10,
crossAxisSpacing: 20,
children: children,
);
```
The following example uses `DynamicGridView.builder` with
`SliverGridDelegateWithWrapping`.
<?code-excerpt "dynamic_grid_view_builder.dart (Example)"?>
```dart
DynamicGridView.builder(
gridDelegate: const SliverGridDelegateWithWrapping(
mainAxisSpacing: 20,
childMainAxisExtent: 250,
childCrossAxisExtent: 50,
),
itemBuilder: (BuildContext context, int index) {
return Container(
height: 200,
color: index.isEven ? Colors.amber : Colors.blue,
child: Center(
child: Text('$index'),
),
);
},
),
```
By using `childCrossAxisExtent` and `childMainAxisExtent` the main axis
can be limited to have a specific size and the other can be set to loose
constraints.
<?code-excerpt "wrapping_fixed_axis.dart" (Example)?>
```dart
DynamicGridView.builder(
gridDelegate: const SliverGridDelegateWithWrapping(
mainAxisSpacing: 20,
childMainAxisExtent: 250,
),
itemBuilder: (BuildContext context, int index) {
return Container(
height: 200,
color: index.isEven ? Colors.amber : Colors.blue,
child: Center(
child: Text('$index'),
),
);
},
),
```
### Stagger
The `Stagger` layout can be used with the constructor
`DynamicGridView.stagger` and still use the delegates from `GridView`
like `SliverGridDelegateWithMaxCrossAxisExtent` and
`SliverGridDelegateWithFixedCrossAxisCount`.
<!-- TODO(DavBot02): Add a code example of DynamicGrid.stagger -->
<!-- TODO(snat-s): Add a video of DynamicGrid.stagger -->
TODO: Include short and useful examples for package users. Add longer examples
to `/example` folder.
## Additional information
<!-- TODO(DavBot02): Tell users more about the package: where to find more information, how to
TODO: Tell users more about the package: where to find more information, how to
contribute to the package, how to file issues, what response they can expect
from the package authors, and more. -->
from the package authors, and more.

View File

@ -5,4 +5,3 @@
export 'src/base_grid_layout.dart';
export 'src/dynamic_grid.dart';
export 'src/render_dynamic_grid.dart';
export 'src/wrap_layout.dart';

View File

@ -37,14 +37,14 @@ class DynamicSliverGridGeometry extends SliverGridGeometry {
crossAxisExtent.isInfinite ? 0.0 : crossAxisExtent;
switch (constraints.axis) {
case Axis.vertical:
case Axis.horizontal:
return BoxConstraints(
minHeight: mainMinExtent,
maxHeight: mainAxisExtent,
minWidth: crossMinExtent,
maxWidth: crossAxisExtent,
);
case Axis.horizontal:
case Axis.vertical:
return BoxConstraints(
minHeight: crossMinExtent,
maxHeight: crossAxisExtent,
@ -68,17 +68,14 @@ abstract class DynamicSliverGridLayout extends SliverGridLayout {
/// provide looser constraints to the child, whose size after layout can be
/// reported back to the layout object in [updateGeometryForChildIndex].
@override
DynamicSliverGridGeometry getGeometryForChildIndex(int index);
SliverGridGeometry getGeometryForChildIndex(int index);
/// Update the size and position of the child with the given index,
/// considering the size of the child after layout.
///
/// This is used to update the layout object after the child has laid out,
/// allowing the layout pattern to adapt to the child's size.
DynamicSliverGridGeometry updateGeometryForChildIndex(
int index,
Size childSize,
);
SliverGridGeometry updateGeometryForChildIndex(int index, Size childSize);
/// Called by [RenderDynamicSliverGrid] to validate the layout pattern has
/// filled the screen.

View File

@ -5,7 +5,6 @@
import 'package:flutter/widgets.dart';
import 'render_dynamic_grid.dart';
import 'wrap_layout.dart';
/// A scrollable, 2D array of widgets.
///
@ -13,10 +12,13 @@ import 'wrap_layout.dart';
class DynamicGridView extends GridView {
/// Creates a scrollable, 2D array of widgets with a custom
/// [SliverGridDelegate].
///
// TODO(all): what other parameters should we add to these
// constructors, here, builder, etc.?
// + reverse
// + scrollDirection
DynamicGridView({
super.key,
super.scrollDirection,
super.reverse,
required super.gridDelegate,
// This creates a SliverChildListDelegate in the super class.
super.children = const <Widget>[],
@ -25,77 +27,13 @@ class DynamicGridView extends GridView {
/// Creates a scrollable, 2D array of widgets that are created on demand.
DynamicGridView.builder({
super.key,
super.scrollDirection,
super.reverse,
required super.gridDelegate,
// This creates a SliverChildBuilderDelegate in the super class.
required super.itemBuilder,
super.itemCount,
}) : super.builder();
/// Creates a scrollable, 2D array of widgets with tiles where each tile can
/// have its own size.
///
/// Uses a [SliverGridDelegateWithWrapping] as the [gridDelegate].
///
/// The following example shows how to use the DynamicGridView.wrap constructor.
///
/// ```dart
/// DynamicGridView.wrap(
/// mainAxisSpacing: 10,
/// crossAxisSpacing: 20,
/// children: [
/// Container(
/// height: 100,
/// width: 200,
/// color: Colors.amberAccent[100],
/// child: const Center(child: Text('Item 1')
/// ),
/// ),
/// Container(
/// height: 50,
/// width: 70,
/// color: Colors.blue[100],
/// child: const Center(child: Text('Item 2'),
/// ),
/// ),
/// Container(
/// height: 82,
/// width: 300,
/// color: Colors.pink[100],
/// child: const Center(child: Text('Item 3'),
/// ),
/// ),
/// Container(
/// color: Colors.green[100],
/// child: const Center(child: Text('Item 3'),
/// ),
/// ),
/// ],
/// ),
/// ```
///
/// See also:
///
/// * [SliverGridDelegateWithWrapping] to see a more detailed explanation of
/// how the wrapping works.
DynamicGridView.wrap({
super.key,
super.scrollDirection,
super.reverse,
double mainAxisSpacing = 0.0,
double crossAxisSpacing = 0.0,
double childCrossAxisExtent = double.infinity,
double childMainAxisExtent = double.infinity,
super.children = const <Widget>[],
}) : super(
gridDelegate: SliverGridDelegateWithWrapping(
mainAxisSpacing: mainAxisSpacing,
crossAxisSpacing: crossAxisSpacing,
childCrossAxisExtent: childCrossAxisExtent,
childMainAxisExtent: childMainAxisExtent,
),
);
// TODO(snat-s): DynamicGridView.wrap?
// TODO(DavBot09): DynamicGridView.stagger?

View File

@ -1,268 +0,0 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/rendering.dart';
import 'base_grid_layout.dart';
// The model that tracks the current max size of the Sliver in the mainAxis and
// tracks if there is still space on the crossAxis.
class _RunMetrics {
_RunMetrics({
required this.maxSliver,
required this.currentSizeUsed,
required this.scrollOffset,
});
/// The biggest sliver size for the current run.
double maxSliver;
/// The current size that has been used in the current run.
double currentSizeUsed;
/// The scroll offset in the current run.
double scrollOffset;
}
/// A [DynamicSliverGridLayout] that uses dynamically sized tiles.
///
/// Rather that providing a grid with a [DynamicSliverGridLayout] directly, instead
/// provide the grid a [SliverGridDelegate], which can compute a
/// [DynamicSliverGridLayout] given the current [SliverConstraints].
///
/// This layout is used by [SliverGridDelegateWithWrapping].
///
/// See also:
///
/// * [SliverGridDelegateWithWrapping], which uses this layout.
/// * [DynamicSliverGridLayout], which represents an arbitrary dynamic tile layout.
/// * [DynamicSliverGridGeometry], which represents the size and position of a
/// single tile in a grid.
/// * [SliverGridDelegate.getLayout], which returns this object to describe the
/// delegate's layout.
/// * [RenderDynamicSliverGrid], which uses this class during its
/// [RenderDynamicSliverGrid.performLayout] method.
class SliverGridWrappingTileLayout extends DynamicSliverGridLayout {
/// Creates a layout that uses dynamic sized and spaced tiles.
///
/// All of the arguments must not be null and must not be negative.
SliverGridWrappingTileLayout({
required this.mainAxisSpacing,
required this.crossAxisSpacing,
required this.childMainAxisExtent,
required this.childCrossAxisExtent,
required this.crossAxisExtent,
required this.scrollDirection,
}) : assert(mainAxisSpacing != null && mainAxisSpacing >= 0),
assert(crossAxisSpacing != null && crossAxisSpacing >= 0),
assert(childMainAxisExtent != null && childMainAxisExtent >= 0),
assert(childCrossAxisExtent != null && childCrossAxisExtent >= 0),
assert(crossAxisExtent != null && crossAxisExtent >= 0),
assert(scrollDirection != null &&
(scrollDirection == Axis.horizontal ||
scrollDirection == Axis.vertical));
/// The direction in wich the layout should be built.
final Axis scrollDirection;
/// The extent of the child in the non-scrolling axis.
final double crossAxisExtent;
/// 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 number of pixels from the leading edge of one tile to the trailing
/// edge of the same tile in the main axis.
final double childMainAxisExtent;
/// The number of pixels from the leading edge of one tile to the trailing
/// edge of the same tile in the cross axis.
final double childCrossAxisExtent;
/// The model that is used internally to keep track of how much space is left
/// and how much has been used.
final List<_RunMetrics> _model = <_RunMetrics>[
_RunMetrics(maxSliver: 0.0, currentSizeUsed: 0.0, scrollOffset: 0.0)
];
// This method provides the initial constraints for the child to layout,
// and then it is updated with the final size later in
// updateGeometryForChildIndex.
@override
DynamicSliverGridGeometry getGeometryForChildIndex(int index) {
return DynamicSliverGridGeometry(
scrollOffset: 0,
crossAxisOffset: 0,
mainAxisExtent: childMainAxisExtent,
crossAxisExtent: childCrossAxisExtent,
);
}
@override
DynamicSliverGridGeometry updateGeometryForChildIndex(
int index,
Size childSize,
) {
final double scrollOffset = _model.last.scrollOffset;
final double currentSizeUsed = _model.last.currentSizeUsed;
late final double addedSize;
switch (scrollDirection) {
case Axis.vertical:
addedSize = currentSizeUsed + childSize.width + crossAxisSpacing;
break;
case Axis.horizontal:
addedSize = currentSizeUsed + childSize.height + mainAxisSpacing;
break;
}
if (addedSize > crossAxisExtent && _model.last.currentSizeUsed > 0.0) {
switch (scrollDirection) {
case Axis.vertical:
_model.add(
_RunMetrics(
maxSliver: childSize.height + mainAxisSpacing,
currentSizeUsed: childSize.width + crossAxisSpacing,
scrollOffset:
scrollOffset + _model.last.maxSliver + mainAxisSpacing,
),
);
break;
case Axis.horizontal:
_model.add(
_RunMetrics(
maxSliver: childSize.width + crossAxisSpacing,
currentSizeUsed: childSize.height + mainAxisSpacing,
scrollOffset:
scrollOffset + _model.last.maxSliver + crossAxisSpacing,
),
);
break;
}
return DynamicSliverGridGeometry(
scrollOffset: _model.last.scrollOffset,
crossAxisOffset: 0.0,
mainAxisExtent: childSize.height + mainAxisSpacing,
crossAxisExtent: childSize.width + crossAxisSpacing,
);
} else {
_model.last.currentSizeUsed = addedSize;
}
switch (scrollDirection) {
case Axis.vertical:
if (childSize.height + mainAxisSpacing > _model.last.maxSliver) {
_model.last.maxSliver = childSize.height + mainAxisSpacing;
}
break;
case Axis.horizontal:
if (childSize.width + crossAxisSpacing > _model.last.maxSliver) {
_model.last.maxSliver = childSize.width + crossAxisSpacing;
}
break;
}
return DynamicSliverGridGeometry(
scrollOffset: scrollOffset,
crossAxisOffset: currentSizeUsed,
mainAxisExtent: childSize.height,
crossAxisExtent: childSize.width,
);
}
@override
bool reachedTargetScrollOffset(double targetOffset) {
return _model.last.scrollOffset > targetOffset;
}
}
/// A [SliverGridDelegate] for creating grids that wrap variably sized tiles.
///
/// For example, if the grid is vertical, this delegate will create a layout
/// where the children are layed out until they fill the horizontal axis and then
/// they continue in the next row. If the grid is horizontal, this delegate will
/// do the same but it will fill the vertical axis and will pass to another
/// column until it finishes.
///
/// This delegate creates grids with different sized tiles. Tiles
/// can have fixed dimensions if [childCrossAxisExtent] or
/// [childMainAxisExtent] are provided.
///
/// See also:
/// * [DynamicGridView.wrap], wich is a constructor to use this [SliverGridDelegate],
/// like `GridView.extent`.
/// * [DynamicGridView], which can use this delegate to control the layout of its
/// tiles.
/// * [RenderDynamicSliverGrid], which can use this delegate to control the
/// layout of its tiles.
class SliverGridDelegateWithWrapping extends SliverGridDelegate {
/// Create a delegate that wraps variably sized tiles.
///
/// The children widgets are provided with loose constraints, and if any of the
/// extent parameters are set, the children are given tight constraints.
/// The way that children are made to have loose constraints is by assigning
/// the value of [double.infinity] to [childMainAxisExtent] and
/// [childCrossAxisExtent].
/// To have same sized tiles with the wrapping, specify the [childCrossAxisExtent]
/// and the [childMainAxisExtent] to be the same size. Or only one of them to
/// be of a certain size in one of the axis.
const SliverGridDelegateWithWrapping({
this.mainAxisSpacing = 0.0,
this.crossAxisSpacing = 0.0,
this.childCrossAxisExtent = double.infinity,
this.childMainAxisExtent = double.infinity,
}) : assert(mainAxisSpacing != null && mainAxisSpacing >= 0),
assert(crossAxisSpacing != null && crossAxisSpacing >= 0);
/// The number of pixels from the leading edge of one tile to the trailing
/// edge of the same tile in the main axis.
///
/// Defaults to [double.infinity] to provide the child with loose constraints.
final double childMainAxisExtent;
/// The number of pixels from the leading edge of one tile to the trailing
/// edge of the same tile in the cross axis.
///
/// Defaults to [double.infinity] to provide the child with loose constraints.
final double childCrossAxisExtent;
/// The number of logical pixels between each child along the main axis.
///
/// Defaults to 0.0
final double mainAxisSpacing;
/// The number of logical pixels between each child along the cross axis.
///
/// Defaults to 0.0
final double crossAxisSpacing;
bool _debugAssertIsValid() {
assert(mainAxisSpacing >= 0.0);
assert(crossAxisSpacing >= 0.0);
return true;
}
@override
SliverGridLayout getLayout(SliverConstraints constraints) {
assert(_debugAssertIsValid());
return SliverGridWrappingTileLayout(
childMainAxisExtent: childMainAxisExtent,
childCrossAxisExtent: childCrossAxisExtent,
mainAxisSpacing: mainAxisSpacing,
crossAxisSpacing: crossAxisSpacing,
scrollDirection: axisDirectionToAxis(constraints.axisDirection),
crossAxisExtent: constraints.crossAxisExtent,
);
}
@override
bool shouldRelayout(SliverGridDelegateWithWrapping oldDelegate) {
return oldDelegate.mainAxisSpacing != mainAxisSpacing ||
oldDelegate.crossAxisSpacing != crossAxisSpacing;
}
}

View File

@ -12,8 +12,8 @@ void main() {
const DynamicSliverGridGeometry geometry = DynamicSliverGridGeometry(
scrollOffset: 0,
crossAxisOffset: 0,
crossAxisExtent: 150.0,
mainAxisExtent: 50.0,
mainAxisExtent: 150.0,
crossAxisExtent: 50.0,
);
// Vertical

View File

@ -38,7 +38,7 @@ void main() {
),
);
// Only the visible tiles have been laid out.
// Only the visible tiles have ben laid out.
expect(find.text('Index 0'), findsOneWidget);
expect(tester.getTopLeft(find.text('Index 0')), Offset.zero);
expect(find.text('Index 1'), findsOneWidget);
@ -69,7 +69,7 @@ void main() {
),
);
// Only the visible tiles have been laid out, up to itemCount.
// Only the visible tiles have ben laid out, up to itemCount.
expect(find.text('Index 0'), findsOneWidget);
expect(tester.getTopLeft(find.text('Index 0')), Offset.zero);
expect(find.text('Index 1'), findsOneWidget);
@ -119,9 +119,9 @@ class TestSimpleLayout extends DynamicSliverGridLayout {
static const double childExtent = 50.0;
@override
DynamicSliverGridGeometry getGeometryForChildIndex(int index) {
SliverGridGeometry getGeometryForChildIndex(int index) {
final double crossAxisStart = (index % crossAxisCount) * childExtent;
return DynamicSliverGridGeometry(
return SliverGridGeometry(
scrollOffset: (index ~/ crossAxisCount) * childExtent,
crossAxisOffset: crossAxisStart,
mainAxisExtent: childExtent,
@ -133,10 +133,7 @@ class TestSimpleLayout extends DynamicSliverGridLayout {
bool reachedTargetScrollOffset(double targetOffset) => true;
@override
DynamicSliverGridGeometry updateGeometryForChildIndex(
int index,
Size childSize,
) {
SliverGridGeometry updateGeometryForChildIndex(int index, Size childSize) {
return getGeometryForChildIndex(index);
}
}
@ -145,7 +142,7 @@ class TestDelegate extends SliverGridDelegateWithFixedCrossAxisCount {
TestDelegate({required super.crossAxisCount});
@override
DynamicSliverGridLayout getLayout(SliverConstraints constraints) {
SliverGridLayout getLayout(SliverConstraints constraints) {
return TestSimpleLayout(crossAxisCount: crossAxisCount);
}
}

View File

@ -1,636 +0,0 @@
// Copyright 2013 The Flutter 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:dynamic_layouts/dynamic_layouts.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets(
'DynamicGridView generates children and checks if they are layed out',
(WidgetTester tester) async {
final List<Widget> children = List<Widget>.generate(
10,
(int index) => SizedBox(
height: index.isEven ? 100 : 50,
width: index.isEven ? 95 : 180,
child: Text('Item $index'),
),
);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: DynamicGridView(
gridDelegate: const SliverGridDelegateWithWrapping(),
children: children,
),
),
),
);
// Check that the children are in the tree
for (int i = 0; i < 10; i++) {
expect(find.text('Item $i'), findsOneWidget);
}
// Check that the children are in the right position
expect(tester.getTopLeft(find.text('Item 0')), const Offset(0.0, 0.0));
expect(tester.getTopLeft(find.text('Item 1')), const Offset(95.0, 0.0));
expect(tester.getTopLeft(find.text('Item 2')), const Offset(275.0, 0.0));
expect(tester.getTopLeft(find.text('Item 3')), const Offset(370.0, 0.0));
expect(tester.getTopLeft(find.text('Item 4')), const Offset(550.0, 0.0));
expect(tester.getTopLeft(find.text('Item 5')), const Offset(0.0, 100.0));
expect(tester.getTopLeft(find.text('Item 6')), const Offset(180.0, 100.0));
expect(tester.getTopLeft(find.text('Item 7')), const Offset(275.0, 100.0));
expect(tester.getTopLeft(find.text('Item 8')), const Offset(455.0, 100.0));
expect(tester.getTopLeft(find.text('Item 9')), const Offset(550.0, 100.0));
});
testWidgets(
'Test for wrap that generates children and checks if they are layed out',
(WidgetTester tester) async {
final List<Widget> children = List<Widget>.generate(
10,
(int index) => SizedBox(
height: index.isEven ? 100 : 50,
width: index.isEven ? 95 : 180,
child: Text('Item $index'),
),
);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: DynamicGridView.wrap(
children: children,
),
),
),
);
for (int i = 0; i < 10; i++) {
expect(find.text('Item $i'), findsOneWidget);
}
// Check that the children are in the right position
expect(tester.getTopLeft(find.text('Item 0')), const Offset(0.0, 0.0));
expect(tester.getTopLeft(find.text('Item 1')), const Offset(95.0, 0.0));
expect(tester.getTopLeft(find.text('Item 2')), const Offset(275.0, 0.0));
expect(tester.getTopLeft(find.text('Item 3')), const Offset(370.0, 0.0));
expect(tester.getTopLeft(find.text('Item 4')), const Offset(550.0, 0.0));
expect(tester.getTopLeft(find.text('Item 5')), const Offset(0.0, 100.0));
expect(tester.getTopLeft(find.text('Item 6')), const Offset(180.0, 100.0));
expect(tester.getTopLeft(find.text('Item 7')), const Offset(275.0, 100.0));
expect(tester.getTopLeft(find.text('Item 8')), const Offset(455.0, 100.0));
expect(tester.getTopLeft(find.text('Item 9')), const Offset(550.0, 100.0));
});
testWidgets('Test for wrap to be laying child dynamically',
(WidgetTester tester) async {
final List<Widget> children = List<Widget>.generate(
20,
(int index) => SizedBox(
height: index.isEven ? 1000 : 50,
width: index.isEven ? 95 : 180,
child: Text('Item $index'),
),
);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: DynamicGridView.builder(
itemCount: children.length,
gridDelegate: const SliverGridDelegateWithWrapping(),
itemBuilder: (BuildContext context, int index) => children[index],
),
),
),
);
for (int i = 0; i < 5; i++) {
expect(find.text('Item $i'), findsOneWidget);
}
// Check that the children are in the right position
expect(tester.getTopLeft(find.text('Item 0')), const Offset(0.0, 0.0));
expect(tester.getTopLeft(find.text('Item 1')), const Offset(95.0, 0.0));
expect(tester.getTopLeft(find.text('Item 2')), const Offset(275.0, 0.0));
expect(tester.getTopLeft(find.text('Item 3')), const Offset(370.0, 0.0));
expect(tester.getTopLeft(find.text('Item 4')), const Offset(550.0, 0.0));
expect(find.text('Item 5'), findsNothing);
await tester.scrollUntilVisible(find.text('Item 19'), 500.0);
await tester.pumpAndSettle();
expect(find.text('Item 18'), findsOneWidget);
expect(tester.getTopLeft(find.text('Item 18')), const Offset(455.0, 0.0));
expect(find.text('Item 0'), findsNothing);
expect(find.text('Item 1'), findsNothing);
expect(find.text('Item 2'), findsNothing);
expect(find.text('Item 3'), findsNothing);
});
testWidgets(
'Test for DynamicGridView.wrap to scrollDirection Axis.horizontal',
(WidgetTester tester) async {
final List<Widget> children = List<Widget>.generate(
20,
(int index) => SizedBox(
height: index.isEven ? 100 : 50,
width: index.isEven ? 100 : 180,
child: Text('Item $index'),
),
);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: DynamicGridView.wrap(
scrollDirection: Axis.horizontal,
children: children,
),
),
),
);
for (int i = 0; i < 20; i++) {
expect(find.text('Item $i'), findsOneWidget);
}
// Check that the children are in the right position
double dy = 0, dx = 0;
for (int i = 0; i < 20; i++) {
if (dy >= 600.0) {
dy = 0.0;
dx += 180.0;
}
expect(tester.getTopLeft(find.text('Item $i')), Offset(dx, dy));
dy += i.isEven ? 100 : 50;
}
});
testWidgets('Test DynamicGridView.builder for GridView.reverse to true',
(WidgetTester tester) async {
final List<Widget> children = List<Widget>.generate(
10,
(int index) => SizedBox(
height: index.isEven ? 100 : 50,
width: index.isEven ? 100 : 180,
child: Text('Item $index'),
),
);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: DynamicGridView.builder(
reverse: true,
itemCount: children.length,
gridDelegate: const SliverGridDelegateWithWrapping(),
itemBuilder: (BuildContext context, int index) => children[index],
),
),
),
);
for (int i = 0; i < 10; i++) {
expect(find.text('Item $i'), findsOneWidget);
}
double dx = 0.0, dy = 600.0;
for (int i = 0; i < 10; i++) {
if (dx >= 600.0) {
dx = 0.0;
dy -= 100.0;
}
expect(tester.getBottomLeft(find.text('Item $i')), Offset(dx, dy));
dx += i.isEven ? 100 : 180;
}
});
testWidgets('DynamicGridView.wrap for GridView.reverse to true',
(WidgetTester tester) async {
final List<Widget> children = List<Widget>.generate(
20,
(int index) => SizedBox(
height: index.isEven ? 100 : 50,
width: index.isEven ? 100 : 180,
child: Text('Item $index'),
),
);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: DynamicGridView.wrap(
reverse: true,
children: children,
),
),
),
);
for (int i = 0; i < 20; i++) {
expect(find.text('Item $i'), findsOneWidget);
}
// Check that the children are in the right position
double dx = 0.0, dy = 600.0;
for (int i = 0; i < 20; i++) {
if (dx >= 600.0) {
dx = 0.0;
dy -= 100.0;
}
expect(tester.getBottomLeft(find.text('Item $i')), Offset(dx, dy));
dx += i.isEven ? 100 : 180;
}
});
testWidgets('DynamicGridView.wrap dismiss keyboard onDrag test',
(WidgetTester tester) async {
final List<FocusNode> focusNodes =
List<FocusNode>.generate(50, (int i) => FocusNode());
await tester.pumpWidget(
textFieldBoilerplate(
child: GridView.extent(
padding: EdgeInsets.zero,
maxCrossAxisExtent: 300,
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
children: focusNodes.map((FocusNode focusNode) {
return Container(
height: 50,
color: Colors.green,
child: TextField(
focusNode: focusNode,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
);
}).toList(),
),
),
);
final Finder finder = find.byType(TextField).first;
final TextField textField = tester.widget(finder);
await tester.showKeyboard(finder);
expect(textField.focusNode!.hasFocus, isTrue);
await tester.drag(finder, const Offset(0.0, -40.0));
await tester.pumpAndSettle();
expect(textField.focusNode!.hasFocus, isFalse);
});
testWidgets('ChildMainAxisExtent & childCrossAxisExtent are respected',
(WidgetTester tester) async {
final List<Widget> children = List<Widget>.generate(
10,
(int index) => SizedBox(
key: Key(index.toString()),
child: Text('Item $index'),
),
);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: DynamicGridView.builder(
gridDelegate: const SliverGridDelegateWithWrapping(
childMainAxisExtent: 150,
childCrossAxisExtent: 200,
),
itemCount: children.length,
itemBuilder: (BuildContext context, int index) => children[index],
),
),
),
);
for (int i = 0; i < 10; i++) {
final Size sizeOfCurrent = tester.getSize(find.byKey(Key('$i')));
expect(sizeOfCurrent.width, equals(200));
expect(sizeOfCurrent.height, equals(150));
}
// Check that the children are in the right position
double dy = 0, dx = 0;
for (int i = 0; i < 10; i++) {
if (dx > 600.0) {
dx = 0.0;
dy += 150.0;
}
expect(tester.getTopLeft(find.text('Item $i')), Offset(dx, dy));
dx += 200;
}
});
testWidgets('ChildMainAxisExtent is respected', (WidgetTester tester) async {
final List<Widget> children = List<Widget>.generate(
10,
(int index) => SizedBox(
key: Key(index.toString()),
width: index.isEven ? 100 : 180,
child: Text('Item $index'),
),
);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: DynamicGridView.builder(
gridDelegate: const SliverGridDelegateWithWrapping(
childMainAxisExtent: 200,
),
itemCount: children.length,
itemBuilder: (BuildContext context, int index) => children[index],
),
),
),
);
for (int i = 0; i < 10; i++) {
final Size sizeOfCurrent = tester.getSize(find.byKey(Key('$i')));
expect(sizeOfCurrent.height, equals(200));
}
// Check that the children are in the right position
double dy = 0, dx = 0;
for (int i = 0; i < 10; i++) {
if (dx >= 600.0) {
dx = 0.0;
dy += 200.0;
}
expect(tester.getTopLeft(find.text('Item $i')), Offset(dx, dy));
dx += i.isEven ? 100 : 180;
}
});
testWidgets('ChildCrossAxisExtent is respected', (WidgetTester tester) async {
final List<Widget> children = List<Widget>.generate(
10,
(int index) => SizedBox(
height: index.isEven ? 100 : 50,
key: Key(index.toString()),
child: Text('Item $index'),
),
);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: DynamicGridView.builder(
gridDelegate: const SliverGridDelegateWithWrapping(
childCrossAxisExtent: 150,
),
itemCount: children.length,
itemBuilder: (BuildContext context, int index) => children[index],
),
),
),
);
for (int i = 0; i < 10; i++) {
final Size sizeOfCurrent = tester.getSize(find.byKey(Key('$i')));
expect(sizeOfCurrent.width, equals(150));
}
// Check that the children are in the right position
double dy = 0, dx = 0;
for (int i = 0; i < 10; i++) {
if (dx >= 750.0) {
dx = 0.0;
dy += 100.0;
}
expect(tester.getTopLeft(find.text('Item $i')), Offset(dx, dy));
dx += 150;
}
});
testWidgets('Test wrap to see nothing affected if elements are deleted.',
(WidgetTester tester) async {
late StateSetter stateSetter;
final List<Widget> children = List<Widget>.generate(
10,
(int index) => SizedBox(
height: index.isEven ? 100 : 50,
width: index.isEven ? 100 : 180,
child: Text('Item $index'),
),
);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
stateSetter = setState;
return DynamicGridView.builder(
gridDelegate: const SliverGridDelegateWithWrapping(),
itemCount: children.length,
itemBuilder: (BuildContext context, int index) => children[index],
);
}),
),
),
);
// See if the children are in the tree.
for (int i = 0; i < 10; i++) {
expect(find.text('Item $i'), findsOneWidget);
}
// See if they are layed properly.
double dx = 0.0, dy = 0.0;
for (int i = 0; i < 10; i++) {
if (dx >= 600) {
dx = 0.0;
dy += 100;
}
expect(tester.getTopLeft(find.text('Item $i')), Offset(dx, dy));
dx += i.isEven ? 100 : 180;
}
stateSetter(() {
// Remove children
children.removeAt(0);
children.removeAt(8);
children.removeAt(5);
});
await tester.pump();
// See if the proper widgets are in the tree.
expect(find.text('Item 0'), findsNothing);
expect(find.text('Item 6'), findsNothing);
expect(find.text('Item 9'), findsNothing);
expect(find.text('Item 1'), findsOneWidget);
expect(find.text('Item 2'), findsOneWidget);
expect(find.text('Item 3'), findsOneWidget);
expect(find.text('Item 4'), findsOneWidget);
expect(find.text('Item 5'), findsOneWidget);
expect(find.text('Item 7'), findsOneWidget);
expect(find.text('Item 8'), findsOneWidget);
// See if the proper widgets are in the tree.
expect(tester.getTopLeft(find.text('Item 1')), const Offset(0.0, 0.0));
expect(tester.getTopLeft(find.text('Item 2')), const Offset(180.0, 0.0));
expect(tester.getTopLeft(find.text('Item 3')), const Offset(280.0, 0.0));
expect(tester.getTopLeft(find.text('Item 4')), const Offset(460.0, 0.0));
expect(tester.getTopLeft(find.text('Item 5')), const Offset(560.0, 0.0));
expect(tester.getTopLeft(find.text('Item 7')), const Offset(0.0, 100.0));
expect(tester.getTopLeft(find.text('Item 8')), const Offset(180.0, 100.0));
});
testWidgets('Test wrap in Axis.vertical direction',
(WidgetTester tester) async {
final List<Widget> children = List<Widget>.generate(
5,
(int index) => SizedBox(
height: index.isEven ? 100 : 50,
width: index.isEven ? 100 : 180,
child: Text('Item $index'),
),
);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: DynamicGridView.builder(
itemCount: children.length,
gridDelegate: const SliverGridDelegateWithWrapping(),
itemBuilder: (BuildContext context, int index) => children[index],
),
),
),
);
// Change the size of the screen
await tester.binding.setSurfaceSize(const Size(500, 100));
await tester.pumpAndSettle();
expect(find.text('Item 0'), findsOneWidget);
expect(find.text('Item 1'), findsOneWidget);
expect(find.text('Item 2'), findsOneWidget);
expect(tester.getTopLeft(find.text('Item 0')), const Offset(0.0, 0.0));
expect(tester.getTopLeft(find.text('Item 1')), const Offset(100.0, 0.0));
expect(tester.getTopLeft(find.text('Item 2')), const Offset(280.0, 0.0));
expect(find.text('Item 3'), findsNothing);
expect(find.text('Item 4'), findsNothing);
await tester.binding.setSurfaceSize(const Size(560, 100));
await tester.pumpAndSettle();
expect(find.text('Item 3'), findsOneWidget);
expect(tester.getTopLeft(find.text('Item 3')), const Offset(380.0, 0.0));
expect(find.text('Item 4'), findsNothing);
await tester.binding.setSurfaceSize(const Size(280, 100));
// resets the screen to its original size after the test end
addTearDown(tester.binding.window.clearPhysicalSizeTestValue);
await tester.pumpAndSettle();
expect(find.text('Item 0'), findsOneWidget);
expect(find.text('Item 1'), findsOneWidget);
expect(tester.getTopLeft(find.text('Item 0')), const Offset(0.0, 0.0));
expect(tester.getTopLeft(find.text('Item 1')), const Offset(100.0, 0.0));
expect(find.text('Item 2'), findsNothing);
expect(find.text('Item 3'), findsNothing);
expect(find.text('Item 4'), findsNothing);
});
testWidgets('Test wrap in Axis.horizontal direction',
(WidgetTester tester) async {
final List<Widget> children = List<Widget>.generate(
5,
(int index) => SizedBox(
height: index.isEven ? 100 : 50,
width: index.isEven ? 100 : 180,
child: Text('Item $index'),
),
);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: DynamicGridView.wrap(
scrollDirection: Axis.horizontal,
children: children,
),
),
),
);
// Change the size of the screen
await tester.binding.setSurfaceSize(const Size(180, 150));
await tester.pumpAndSettle();
expect(find.text('Item 0'), findsOneWidget);
expect(find.text('Item 1'), findsOneWidget);
expect(tester.getTopLeft(find.text('Item 0')), const Offset(0.0, 0.0));
expect(tester.getTopLeft(find.text('Item 1')), const Offset(0.0, 100.0));
expect(find.text('Item 2'), findsNothing);
expect(find.text('Item 3'), findsNothing);
await tester.binding.setSurfaceSize(const Size(180, 400));
await tester.pumpAndSettle();
expect(find.text('Item 0'), findsOneWidget);
expect(find.text('Item 1'), findsOneWidget);
expect(find.text('Item 2'), findsOneWidget);
expect(find.text('Item 3'), findsOneWidget);
expect(find.text('Item 4'), findsOneWidget);
expect(tester.getTopLeft(find.text('Item 0')), const Offset(0.0, 0.0));
expect(tester.getTopLeft(find.text('Item 1')), const Offset(0.0, 100.0));
expect(tester.getTopLeft(find.text('Item 2')), const Offset(0.0, 150.0));
expect(tester.getTopLeft(find.text('Item 3')), const Offset(0.0, 250.0));
expect(tester.getTopLeft(find.text('Item 4')), const Offset(0.0, 300.0));
await tester.binding.setSurfaceSize(const Size(560, 100));
// resets the screen to its original size after the test end
addTearDown(tester.binding.window.clearPhysicalSizeTestValue);
await tester.pumpAndSettle();
expect(find.text('Item 0'), findsOneWidget);
expect(find.text('Item 1'), findsOneWidget);
expect(find.text('Item 2'), findsOneWidget);
expect(find.text('Item 3'), findsOneWidget);
expect(find.text('Item 4'), findsNothing);
expect(tester.getTopLeft(find.text('Item 0')), const Offset(0.0, 0.0));
expect(tester.getTopLeft(find.text('Item 1')), const Offset(100.0, 0.0));
expect(tester.getTopLeft(find.text('Item 2')), const Offset(280.0, 0.0));
expect(tester.getTopLeft(find.text('Item 3')), const Offset(380.0, 0.0));
});
}
Widget textFieldBoilerplate({required Widget child}) {
return MaterialApp(
home: Localizations(
locale: const Locale('en', 'US'),
delegates: <LocalizationsDelegate<dynamic>>[
WidgetsLocalizationsDelegate(),
MaterialLocalizationsDelegate(),
],
child: Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(size: Size(800.0, 600.0)),
child: Center(
child: Material(
child: child,
),
),
),
),
),
);
}
class MaterialLocalizationsDelegate
extends LocalizationsDelegate<MaterialLocalizations> {
@override
bool isSupported(Locale locale) => true;
@override
Future<MaterialLocalizations> load(Locale locale) =>
DefaultMaterialLocalizations.load(locale);
@override
bool shouldReload(MaterialLocalizationsDelegate old) => false;
}
class WidgetsLocalizationsDelegate
extends LocalizationsDelegate<WidgetsLocalizations> {
@override
bool isSupported(Locale locale) => true;
@override
Future<WidgetsLocalizations> load(Locale locale) =>
DefaultWidgetsLocalizations.load(locale);
@override
bool shouldReload(WidgetsLocalizationsDelegate old) => false;
}