Skip to content

Instantly share code, notes, and snippets.

@lukepighetti
Last active April 13, 2025 09:48

Revisions

  1. lukepighetti revised this gist Mar 1, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion animated_grid.dart
    Original file line number Diff line number Diff line change
    @@ -123,8 +123,8 @@ class AnimatedGridDetails {
    @required this.index,
    @required this.columnIndex,
    @required this.rowIndex,
    @required this.rows,
    @required this.columns,
    @required this.rows,
    });

    /// The current index
  2. lukepighetti revised this gist Mar 1, 2021. 1 changed file with 41 additions and 5 deletions.
    46 changes: 41 additions & 5 deletions animated_grid.dart
    Original file line number Diff line number Diff line change
    @@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
    import '../extensions/extensions.dart';

    typedef AnimatedGridBuilder<T> = Widget Function(
    BuildContext, T item, int columnIndex, int rowIndex);
    BuildContext, T item, AnimatedGridDetails details);

    class AnimatedGrid<T> extends StatelessWidget {
    /// An animated grid the animates when the items change sort.
    @@ -39,11 +39,11 @@ class AnimatedGrid<T> extends StatelessWidget {
    /// The curve of the sort animation.
    final Curve curve;

    static int _rows(int columns, int items) => (items / columns).ceil();
    static int _rows(int columns, int count) => (count / columns).ceil();

    @visibleForTesting
    static List<int> gridIndicies(int index, int columns, int items) {
    final rows = _rows(columns, items);
    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;
    @@ -94,7 +94,17 @@ class AnimatedGrid<T> extends StatelessWidget {
    child: SizedBox(
    height: itemHeight,
    width: itemWidth,
    child: builder(context, item, xIndex, yIndex),
    child: builder(
    context,
    item,
    AnimatedGridDetails(
    index: i,
    columnIndex: xIndex,
    rowIndex: yIndex,
    columns: columns,
    rows: rows,
    ),
    ),
    ),
    );
    },
    @@ -106,3 +116,29 @@ class AnimatedGrid<T> extends StatelessWidget {
    );
    }
    }

    class AnimatedGridDetails {
    /// A collection of details currently being used by [AnimatedGrid]
    AnimatedGridDetails({
    @required this.index,
    @required this.columnIndex,
    @required this.rowIndex,
    @required this.rows,
    @required this.columns,
    });

    /// 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;
    }
  3. lukepighetti revised this gist Mar 1, 2021. 1 changed file with 8 additions and 0 deletions.
    8 changes: 8 additions & 0 deletions extensions.dart
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,8 @@
    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;
    }
  4. lukepighetti revised this gist Mar 1, 2021. 1 changed file with 6 additions and 2 deletions.
    8 changes: 6 additions & 2 deletions animated_grid.dart
    Original file line number Diff line number Diff line change
    @@ -2,6 +2,9 @@ import 'package:flutter/material.dart';

    import '../extensions/extensions.dart';

    typedef AnimatedGridBuilder<T> = Widget Function(
    BuildContext, T item, int columnIndex, int rowIndex);

    class AnimatedGrid<T> extends StatelessWidget {
    /// An animated grid the animates when the items change sort.
    const AnimatedGrid({
    @@ -18,10 +21,11 @@ class AnimatedGrid<T> extends StatelessWidget {
    /// 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;

    final Widget Function(BuildContext, T item, int columIndex, int rowIndex)
    builder;
    /// 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;
  5. lukepighetti created this gist Mar 1, 2021.
    104 changes: 104 additions & 0 deletions animated_grid.dart
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,104 @@
    import 'package:flutter/material.dart';

    import '../extensions/extensions.dart';

    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;

    final Key Function(T item) keyBuilder;

    final Widget Function(BuildContext, T item, int columIndex, int rowIndex)
    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 items) => (items / columns).ceil();

    @visibleForTesting
    static List<int> gridIndicies(int index, int columns, int items) {
    final rows = _rows(columns, items);
    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, xIndex, yIndex),
    ),
    );
    },
    ),
    ],
    ),
    );
    },
    );
    }
    }
    82 changes: 82 additions & 0 deletions animated_grid_test.dart
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,82 @@
    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]),
    );
    });
    });
    }