beautiful_staggered_grid_view 1.0.0+3 copy "beautiful_staggered_grid_view: ^1.0.0+3" to clipboard
beautiful_staggered_grid_view: ^1.0.0+3 copied to clipboard

A beautiful and customizable staggered grid view package for Flutter.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:collection/collection.dart';


/// Entry point of the application.
void main() => runApp(const MyApp());

/// The root widget of the example application.
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Staggered Grid View Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const ExampleHomePage(),
    );
  }
}

/// The home page that demonstrates the staggered grid layout.
class ExampleHomePage extends StatelessWidget {
  const ExampleHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Staggered Grid Example')),
      body: const StaggeredPage(),
    );
  }
}

/// A scaffold widget that provides a common layout with an app bar.
class AppScaffold extends StatelessWidget {
  /// The title to display in the app bar.
  final String title;

  /// The content of the page.
  final Widget child;

  /// Creates an [AppScaffold] with the given [title] and [child].
  const AppScaffold({
    Key? key,
    required this.title,
    required this.child,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
        centerTitle: true,
      ),
      body: Padding(
        padding: const EdgeInsets.all(12.0),
        child: child,
      ),
    );
  }
}

/// A model that represents the configuration for a grid tile.
class GridTile {
  /// Number of cells the tile spans in the cross axis.
  final int crossAxisCount;

  /// Number of cells the tile spans in the main axis.
  final int mainAxisCount;

  /// Creates a [GridTile] with the given cell counts.
  const GridTile(this.crossAxisCount, this.mainAxisCount);
}

/// A widget that displays a colored tile with its index.
class ImageTile extends StatelessWidget {
  /// The index used to determine the color.
  final int index;

  /// The width of the tile.
  final double width;

  /// The height of the tile.
  final double height;

  /// Creates an [ImageTile] with the specified dimensions.
  const ImageTile({
    Key? key,
    required this.index,
    required this.width,
    required this.height,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // Use Color.fromRGBO to generate a color with 50% opacity.
    final baseColor = Colors.primaries[index % Colors.primaries.length];
    return Container(
      width: width,
      height: height,
      decoration: BoxDecoration(
        color: Color.fromRGBO(baseColor.red, baseColor.green, baseColor.blue, 0.5),
        borderRadius: BorderRadius.circular(12),
      ),
      child: Center(
        child: Text(
          'Tile $index',
          style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: Colors.white),
        ),
      ),
    );
  }
}

/// A custom staggered grid view widget that arranges its children.
class StaggeredGrid extends StatelessWidget {
  /// Number of columns in the grid.
  final int crossAxisCount;

  /// Vertical spacing between grid items.
  final double mainAxisSpacing;

  /// Horizontal spacing between grid items.
  final double crossAxisSpacing;

  /// The list of grid tiles to display.
  final List<StaggeredGridTile> children;

  /// Creates a [StaggeredGrid] with a fixed column count.
  const StaggeredGrid.count({
    Key? key,
    required this.crossAxisCount,
    this.mainAxisSpacing = 4.0,
    this.crossAxisSpacing = 4.0,
    required this.children,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final totalWidth = constraints.maxWidth;
        final cellWidth = (totalWidth - (crossAxisCount - 1) * crossAxisSpacing) / crossAxisCount;
        final cellHeight = cellWidth; // Using square cells
        final occupancy = <List<bool>>[];
        final positions = <_TilePosition>[];

        for (var tile in children) {
          final pos = _findPositionForTile(tile, occupancy, crossAxisCount);
          positions.add(pos);
          _markOccupancy(occupancy, pos, tile.crossAxisCellCount, tile.mainAxisCellCount);
        }

        final totalRows = occupancy.length;
        final gridHeight = totalRows * cellHeight + (totalRows - 1) * mainAxisSpacing;

        final positionedChildren = <Widget>[];
        for (var i = 0; i < children.length; i++) {
          final tile = children[i];
          final pos = positions[i];
          final left = pos.col * (cellWidth + crossAxisSpacing);
          final top = pos.row * (cellHeight + mainAxisSpacing);
          final tileWidth = tile.crossAxisCellCount * cellWidth + (tile.crossAxisCellCount - 1) * crossAxisSpacing;
          final tileHeight = tile.mainAxisCellCount * cellHeight + (tile.mainAxisCellCount - 1) * mainAxisSpacing;

          positionedChildren.add(Positioned(
            left: left,
            top: top,
            width: tileWidth,
            height: tileHeight,
            child: tile.child,
          ));
        }

        return SizedBox(
          width: totalWidth,
          height: gridHeight,
          child: Stack(children: positionedChildren),
        );
      },
    );
  }
}

/// A widget that defines a tile for the [StaggeredGrid].
class StaggeredGridTile extends StatelessWidget {
  /// Number of cells to span in the cross axis.
  final int crossAxisCellCount;

  /// Number of cells to span in the main axis.
  final int mainAxisCellCount;

  /// The widget to display within this tile.
  final Widget child;

  /// Creates a [StaggeredGridTile] with the given spans and child.
  const StaggeredGridTile.count({
    Key? key,
    required this.crossAxisCellCount,
    required this.mainAxisCellCount,
    required this.child,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) => child;
}

/// Internal helper class to track a tile's position.
class _TilePosition {
  int row;
  int col;
  _TilePosition({required this.row, required this.col});
}

_TilePosition _findPositionForTile(StaggeredGridTile tile, List<List<bool>> occupancy, int crossAxisCount) {
  final requiredRows = tile.mainAxisCellCount;
  final requiredCols = tile.crossAxisCellCount;
  var row = 0;
  while (true) {
    while (occupancy.length < row + requiredRows) {
      occupancy.add(List.filled(crossAxisCount, false));
    }
    for (var col = 0; col <= crossAxisCount - requiredCols; col++) {
      if (_canPlaceAt(occupancy, row, col, requiredRows, requiredCols)) {
        return _TilePosition(row: row, col: col);
      }
    }
    row++;
  }
}

bool _canPlaceAt(List<List<bool>> occupancy, int startRow, int startCol, int requiredRows, int requiredCols) {
  for (var r = startRow; r < startRow + requiredRows; r++) {
    for (var c = startCol; c < startCol + requiredCols; c++) {
      if (occupancy[r][c]) return false;
    }
  }
  return true;
}

void _markOccupancy(List<List<bool>> occupancy, _TilePosition pos, int tileCols, int tileRows) {
  for (var r = pos.row; r < pos.row + tileRows; r++) {
    for (var c = pos.col; c < pos.col + tileCols; c++) {
      occupancy[r][c] = true;
    }
  }
}

/// A page that demonstrates the custom staggered grid layout.
class StaggeredPage extends StatelessWidget {
  const StaggeredPage({Key? key}) : super(key: key);

  /// A list of grid tile configurations.
  static const tiles = [
    GridTile(2, 2),
    GridTile(2, 1),
    GridTile(1, 2),
    GridTile(1, 1),
    GridTile(2, 2),
    GridTile(1, 2),
    GridTile(1, 1),
    GridTile(3, 1),
    GridTile(1, 1),
    GridTile(4, 1),
  ];

  @override
  Widget build(BuildContext context) {
    return AppScaffold(
      title: 'Staggered Grid',
      child: SingleChildScrollView(
        child: StaggeredGrid.count(
          crossAxisCount: 4,
          mainAxisSpacing: 8,
          crossAxisSpacing: 8,
          children: tiles.mapIndexed((index, tile) {
            return StaggeredGridTile.count(
              crossAxisCellCount: tile.crossAxisCount,
              mainAxisCellCount: tile.mainAxisCount,
              child: AnimatedContainer(
                duration: const Duration(milliseconds: 300),
                curve: Curves.easeInOut,
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(12),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black.withOpacity(0.1),
                      blurRadius: 6,
                      offset: const Offset(2, 4),
                    ),
                  ],
                ),
                child: ClipRRect(
                  borderRadius: BorderRadius.circular(12),
                  child: ImageTile(
                    index: index,
                    width: tile.crossAxisCount * 100,
                    height: tile.mainAxisCount * 100,
                  ),
                ),
              ),
            );
          }).toList(),
        ),
      ),
    );
  }
}
3
likes
150
points
1
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A beautiful and customizable staggered grid view package for Flutter.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

collection, flutter

More

Packages that depend on beautiful_staggered_grid_view