Skip to content

Instantly share code, notes, and snippets.

@lukepighetti
Last active February 14, 2023 22:41
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save lukepighetti/df460db180b9f6cb3410e3cc91ed74e6 to your computer and use it in GitHub Desktop.
Save lukepighetti/df460db180b9f6cb3410e3cc91ed74e6 to your computer and use it in GitHub Desktop.
A simple animated grid in Flutter. See: https://twitter.com/luke_pighetti/status/1366151664567255041
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;
}
@mauriciofontanadevargas
Copy link

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

@lukepighetti
Copy link
Author

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.

@purpurni
Copy link

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!

@emvaized
Copy link

Looks promising! However, can't manage to get transition on item removal/add.

@lukepighetti
Copy link
Author

I'm not sure what you're expecting or seeing

@emvaized
Copy link

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];
  }

@Dharmik14
Copy link

@lukepighetti Could u plz provide a small example of how to use it...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment