import 'package:flutter/material.dart'; typedef AnimatedGridBuilder = Widget Function(BuildContext, T item); class AnimateGrid extends StatelessWidget { final int columns; final double itemHeight; final double gap; final List items; final Key Function(T item) keyBuilder; final AnimatedGridBuilder builder; final Duration duration; final Curve curve; const AnimateGrid({ super.key, required this.items, required this.itemHeight, required this.keyBuilder, required this.builder, this.gap = 8, this.duration = const Duration(milliseconds: 300), this.curve = Curves.easeOut, this.columns = 2, }); int _rows(int columns, int count) => (count / columns).ceil(); Offset _getOffset( int index, int count, double itemWidth, double itemHeight, ) { final xIndex = index % columns; final yIndex = (index / columns).floor(); return Offset( xIndex * itemWidth + xIndex * gap, yIndex * itemHeight + yIndex * gap); } @override Widget build(BuildContext context) { return LayoutBuilder(builder: (_, constraints) { assert(constraints.hasBoundedHeight == false); final gapWidth = (columns - 1) * gap; final width = constraints.maxWidth; final itemWidth = (width - gapWidth) / columns; final count = items.length; final rows = _rows(columns, count); final gapHeight = (rows - 1) * gap; final height = rows * itemHeight + gapHeight; return SizedBox( width: width, height: height, child: Stack( children: [ for (var i = 0; i <= count - 1; i++) Builder( key: keyBuilder(items[i]), builder: (context) { final item = items[i]; final offset = _getOffset( i, count, itemWidth, itemHeight, ); return TweenAnimationBuilder( tween: Tween(end: offset), duration: duration, curve: curve, builder: (_, offset, child) { return Transform.translate( offset: offset, child: child, ); }, child: SizedBox( height: itemHeight, width: itemWidth, child: builder( context, item, ), ), ); }, ), ], ), ); }); } }