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). [developing packages and plugins](https://flutter.dev/developing-packages).
--> -->
<!-- TODO(DavBot02): Put a short description of the package here that helps potential users TODO: Put a short description of the package here that helps potential users
know whether this package might be useful for them.--> know whether this package might be useful for them.
## Features ## 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. TODO: List what your package can do. Maybe include images, gifs, or videos.
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.
## Getting started ## 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 ## Usage
Use `DynamicGridView`s to access this layouts. TODO: Include short and useful examples for package users. Add longer examples
`DynamicGridView` has some constructors that use `SliverChildListDelegate` like to `/example` folder.
`.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 -->
## Additional information ## 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 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/base_grid_layout.dart';
export 'src/dynamic_grid.dart'; export 'src/dynamic_grid.dart';
export 'src/render_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; crossAxisExtent.isInfinite ? 0.0 : crossAxisExtent;
switch (constraints.axis) { switch (constraints.axis) {
case Axis.vertical: case Axis.horizontal:
return BoxConstraints( return BoxConstraints(
minHeight: mainMinExtent, minHeight: mainMinExtent,
maxHeight: mainAxisExtent, maxHeight: mainAxisExtent,
minWidth: crossMinExtent, minWidth: crossMinExtent,
maxWidth: crossAxisExtent, maxWidth: crossAxisExtent,
); );
case Axis.horizontal: case Axis.vertical:
return BoxConstraints( return BoxConstraints(
minHeight: crossMinExtent, minHeight: crossMinExtent,
maxHeight: crossAxisExtent, maxHeight: crossAxisExtent,
@ -68,17 +68,14 @@ abstract class DynamicSliverGridLayout extends SliverGridLayout {
/// provide looser constraints to the child, whose size after layout can be /// provide looser constraints to the child, whose size after layout can be
/// reported back to the layout object in [updateGeometryForChildIndex]. /// reported back to the layout object in [updateGeometryForChildIndex].
@override @override
DynamicSliverGridGeometry getGeometryForChildIndex(int index); SliverGridGeometry getGeometryForChildIndex(int index);
/// Update the size and position of the child with the given index, /// Update the size and position of the child with the given index,
/// considering the size of the child after layout. /// considering the size of the child after layout.
/// ///
/// This is used to update the layout object after the child has laid out, /// 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. /// allowing the layout pattern to adapt to the child's size.
DynamicSliverGridGeometry updateGeometryForChildIndex( SliverGridGeometry updateGeometryForChildIndex(int index, Size childSize);
int index,
Size childSize,
);
/// Called by [RenderDynamicSliverGrid] to validate the layout pattern has /// Called by [RenderDynamicSliverGrid] to validate the layout pattern has
/// filled the screen. /// filled the screen.

View File

@ -5,7 +5,6 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'render_dynamic_grid.dart'; import 'render_dynamic_grid.dart';
import 'wrap_layout.dart';
/// A scrollable, 2D array of widgets. /// A scrollable, 2D array of widgets.
/// ///
@ -13,10 +12,13 @@ import 'wrap_layout.dart';
class DynamicGridView extends GridView { class DynamicGridView extends GridView {
/// Creates a scrollable, 2D array of widgets with a custom /// Creates a scrollable, 2D array of widgets with a custom
/// [SliverGridDelegate]. /// [SliverGridDelegate].
///
// TODO(all): what other parameters should we add to these
// constructors, here, builder, etc.?
// + reverse
// + scrollDirection
DynamicGridView({ DynamicGridView({
super.key, super.key,
super.scrollDirection,
super.reverse,
required super.gridDelegate, required super.gridDelegate,
// This creates a SliverChildListDelegate in the super class. // This creates a SliverChildListDelegate in the super class.
super.children = const <Widget>[], super.children = const <Widget>[],
@ -25,77 +27,13 @@ class DynamicGridView extends GridView {
/// Creates a scrollable, 2D array of widgets that are created on demand. /// Creates a scrollable, 2D array of widgets that are created on demand.
DynamicGridView.builder({ DynamicGridView.builder({
super.key, super.key,
super.scrollDirection,
super.reverse,
required super.gridDelegate, required super.gridDelegate,
// This creates a SliverChildBuilderDelegate in the super class. // This creates a SliverChildBuilderDelegate in the super class.
required super.itemBuilder, required super.itemBuilder,
super.itemCount, super.itemCount,
}) : super.builder(); }) : super.builder();
/// Creates a scrollable, 2D array of widgets with tiles where each tile can // TODO(snat-s): DynamicGridView.wrap?
/// 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(DavBot09): DynamicGridView.stagger? // 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( const DynamicSliverGridGeometry geometry = DynamicSliverGridGeometry(
scrollOffset: 0, scrollOffset: 0,
crossAxisOffset: 0, crossAxisOffset: 0,
crossAxisExtent: 150.0, mainAxisExtent: 150.0,
mainAxisExtent: 50.0, crossAxisExtent: 50.0,
); );
// Vertical // 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(find.text('Index 0'), findsOneWidget);
expect(tester.getTopLeft(find.text('Index 0')), Offset.zero); expect(tester.getTopLeft(find.text('Index 0')), Offset.zero);
expect(find.text('Index 1'), findsOneWidget); 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(find.text('Index 0'), findsOneWidget);
expect(tester.getTopLeft(find.text('Index 0')), Offset.zero); expect(tester.getTopLeft(find.text('Index 0')), Offset.zero);
expect(find.text('Index 1'), findsOneWidget); expect(find.text('Index 1'), findsOneWidget);
@ -119,9 +119,9 @@ class TestSimpleLayout extends DynamicSliverGridLayout {
static const double childExtent = 50.0; static const double childExtent = 50.0;
@override @override
DynamicSliverGridGeometry getGeometryForChildIndex(int index) { SliverGridGeometry getGeometryForChildIndex(int index) {
final double crossAxisStart = (index % crossAxisCount) * childExtent; final double crossAxisStart = (index % crossAxisCount) * childExtent;
return DynamicSliverGridGeometry( return SliverGridGeometry(
scrollOffset: (index ~/ crossAxisCount) * childExtent, scrollOffset: (index ~/ crossAxisCount) * childExtent,
crossAxisOffset: crossAxisStart, crossAxisOffset: crossAxisStart,
mainAxisExtent: childExtent, mainAxisExtent: childExtent,
@ -133,10 +133,7 @@ class TestSimpleLayout extends DynamicSliverGridLayout {
bool reachedTargetScrollOffset(double targetOffset) => true; bool reachedTargetScrollOffset(double targetOffset) => true;
@override @override
DynamicSliverGridGeometry updateGeometryForChildIndex( SliverGridGeometry updateGeometryForChildIndex(int index, Size childSize) {
int index,
Size childSize,
) {
return getGeometryForChildIndex(index); return getGeometryForChildIndex(index);
} }
} }
@ -145,7 +142,7 @@ class TestDelegate extends SliverGridDelegateWithFixedCrossAxisCount {
TestDelegate({required super.crossAxisCount}); TestDelegate({required super.crossAxisCount});
@override @override
DynamicSliverGridLayout getLayout(SliverConstraints constraints) { SliverGridLayout getLayout(SliverConstraints constraints) {
return TestSimpleLayout(crossAxisCount: crossAxisCount); 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;
}