-
-
Save lukepighetti/df460db180b9f6cb3410e3cc91ed74e6 to your computer and use it in GitHub Desktop.
import 'package:flutter/material.dart'; | |
import '../extensions/extensions.dart'; | |
typedef AnimatedGridBuilder<T> = Widget Function( | |
BuildContext, T item, AnimatedGridDetails details); | |
class AnimatedGrid<T> extends StatelessWidget { | |
/// An animated grid the animates when the items change sort. | |
const AnimatedGrid({ | |
Key key, | |
@required this.itemHeight, | |
@required this.items, | |
@required this.keyBuilder, | |
@required this.builder, | |
this.columns = 2, | |
this.duration = const Duration(milliseconds: 750), | |
this.curve = Curves.elasticOut, | |
}) : super(key: key); | |
/// The grid items. Should all be the same height. | |
final List<T> items; | |
/// Construct keys given the item provided. Each key must be unique. | |
final Key Function(T item) keyBuilder; | |
/// Build a widget given a context, the current item, and the column and row index. | |
final AnimatedGridBuilder<T> builder; | |
/// The number of columns wide to display. | |
final int columns; | |
/// The height of each child. | |
final double itemHeight; | |
/// The duration of the sort animation. | |
final Duration duration; | |
/// The curve of the sort animation. | |
final Curve curve; | |
static int _rows(int columns, int count) => (count / columns).ceil(); | |
@visibleForTesting | |
static List<int> gridIndicies(int index, int columns, int count) { | |
final rows = _rows(columns, count); | |
final maxItemsForGridSize = columns * rows; | |
final xIndex = (index / maxItemsForGridSize * columns).floor(); | |
final yIndex = index % rows; | |
return [xIndex, yIndex]; | |
} | |
@override | |
Widget build(BuildContext context) { | |
return LayoutBuilder( | |
builder: (context, constraints) { | |
assert(constraints.hasBoundedWidth); | |
assert(constraints.hasBoundedHeight == false); | |
final width = constraints.maxWidth; | |
final count = items.length; | |
final itemWidth = width / columns; | |
final rows = _rows(columns, count); | |
final gridHeight = rows * itemHeight; | |
return SizedBox( | |
height: gridHeight, | |
child: Stack( | |
alignment: Alignment.topLeft, | |
children: [ | |
for (var i = 0; i <= items.lastIndex; i++) | |
Builder( | |
key: keyBuilder(items[i]), | |
builder: (context) { | |
final item = items[i]; | |
final indicies = gridIndicies(i, columns, count); | |
assert(indicies.length == 2); | |
final xIndex = indicies.first; | |
final yIndex = indicies.last; | |
final offset = | |
Offset(xIndex * itemWidth, yIndex * itemHeight); | |
return TweenAnimationBuilder( | |
tween: Tween<Offset>(end: offset), | |
duration: duration, | |
curve: curve, | |
builder: (context, offset, child) { | |
return Transform.translate( | |
offset: offset, | |
child: child, | |
); | |
}, | |
child: SizedBox( | |
height: itemHeight, | |
width: itemWidth, | |
child: builder( | |
context, | |
item, | |
AnimatedGridDetails( | |
index: i, | |
columnIndex: xIndex, | |
rowIndex: yIndex, | |
columns: columns, | |
rows: rows, | |
), | |
), | |
), | |
); | |
}, | |
), | |
], | |
), | |
); | |
}, | |
); | |
} | |
} | |
class AnimatedGridDetails { | |
/// A collection of details currently being used by [AnimatedGrid] | |
AnimatedGridDetails({ | |
@required this.index, | |
@required this.columnIndex, | |
@required this.rowIndex, | |
@required this.columns, | |
@required this.rows, | |
}); | |
/// The current index | |
final int index; | |
/// The current column index | |
final int columnIndex; | |
/// The current row index | |
final int rowIndex; | |
/// The number of columns | |
final int columns; | |
/// The number of rows | |
final int rows; | |
} |
import 'package:flutter_test/flutter_test.dart'; | |
import 'package:vgl/widgets/animated_grid.dart'; | |
main() { | |
group('AnimatedGrid', () { | |
test('gridIndicies', () { | |
/// index 0 | |
/// | |
/// ``` | |
/// 0 2 | |
/// 1 | |
/// ``` | |
expect( | |
AnimatedGrid.gridIndicies(0, 2, 3), | |
equals([0, 0]), | |
); | |
/// index 2 | |
/// | |
/// ``` | |
/// 0 2 | |
/// 1 | |
/// ``` | |
expect( | |
AnimatedGrid.gridIndicies(2, 2, 3), | |
equals([1, 0]), | |
); | |
/// index 9 | |
/// | |
/// ``` | |
/// 0 4 8 | |
/// 1 5 9 | |
/// 2 6 | |
/// 3 7 | |
/// ``` | |
expect( | |
AnimatedGrid.gridIndicies(9, 3, 10), | |
equals([2, 1]), | |
); | |
/// index 7 | |
/// | |
/// ``` | |
/// 0 4 8 | |
/// 1 5 9 | |
/// 2 6 | |
/// 3 7 | |
/// ``` | |
expect( | |
AnimatedGrid.gridIndicies(7, 3, 10), | |
equals([1, 3]), | |
); | |
/// index 6 | |
/// | |
/// ``` | |
/// 0 4 8 | |
/// 1 5 9 | |
/// 2 6 | |
/// 3 7 | |
/// ``` | |
expect( | |
AnimatedGrid.gridIndicies(6, 3, 10), | |
equals([1, 2]), | |
); | |
/// index 3 | |
/// | |
/// ``` | |
/// 0 4 8 | |
/// 1 5 9 | |
/// 2 6 | |
/// 3 7 | |
/// ``` | |
expect( | |
AnimatedGrid.gridIndicies(3, 3, 10), | |
equals([0, 3]), | |
); | |
}); | |
}); | |
} |
extension IterableX<T> on Iterable<T> { | |
/// The last index on this iterable. | |
/// | |
/// Ie `[A,B,C].lastIndex == 2` | |
int get lastIndex => length == 0 | |
? throw RangeError('Cannot find the last index of an empty iterable') | |
: length - 1; | |
} |
This is not like a GridView. It's more like a Row or Column. You'll have to put it inside a scrollable view, like a ListView.
Hello! I'm new at Flutter and can't understand how to implement that, maybe you have a little example?! I want to animate deleting in grid. Thanks a lot!
Looks promising! However, can't manage to get transition on item removal/add.
I'm not sure what you're expecting or seeing
No problem, it happened due to keys mismatch on removing items. I modified your code now in order to make item-key relation more clear.
Btw I think it'd be better if the code for generating grid indices will match the standard GridView sorting:
1 2
3 4
instead of currently provided:
1 3
2 4
I rewrote it like this for that purpose:
@visibleForTesting
static List<int> generateGridIndices(int index, int columns, int count) {
final rows = getRowsCount(columns, count);
final maxItemsForGridSize = columns * rows;
final yIndex = (index / maxItemsForGridSize * rows).floor();
final xIndex = index % columns;
return [xIndex, yIndex];
}
@lukepighetti Could u plz provide a small example of how to use it...
Thanks for the code. I'm a bit confused on how to use your widget where I currently have a GridView.builder().
Any high level directions on how to use your Widget? Thanks