How to center a tile within a StaggeredGridView

303

Intro: I came into this myself, so I want to thank you for opening the question.

Baseline: calculate the remainder of the model count and the cross axis count (userProfile.bioCategories and 6 in the original question), then place the "remaining" tiles in a row (spaced evenly) and span the row all over entire grid width.

Demo

problemsolution

Repro: Github.

Code (a single-file solution, the only prerequsite is flutter_staggered_grid_view: 0.4.0):

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

import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';

void main() {
  runApp(const TabBarDemo());
}

generateRandomColor() => Color((Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0);

class Model {
  final num;

  Model({this.num});
}

class ModelWidget extends StatelessWidget {
  final Model model;

  ModelWidget(this.model) : super();

  @override
  Widget build(BuildContext context) {
    return AspectRatio(
      aspectRatio: 1,
      child: Container(
          color: generateRandomColor(),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                model.num.toString(),
                textAlign: TextAlign.center,
              ),
            ],
          )),
    );
  }
}

class TabBarDemo extends StatelessWidget {
  const TabBarDemo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // ------------------------------- given ----------------------------------
    const CROSS_AXIS_COUNT = 6;
    const CROSS_AXIS_SPACING = 10.0;
    const MAIN_AXIS_SPACING = 10.0;
    final models = List.generate(20, (_) => Model(num: Random().nextInt(100)));
    // ------------------------------------------------------------------------
    // ------------------------------ solution --------------------------------
    final modelCount = models.length;
    final int fittingCount = modelCount - modelCount % CROSS_AXIS_COUNT;
    // ------------------------------------------------------------------------

    return MaterialApp(
      home: DefaultTabController(
        length: 3,
        child: Scaffold(
          appBar: AppBar(
            bottom: const TabBar(
              tabs: [
                Tab(
                  child: Text("problem"),
                ),
                Tab(
                  child: Text("solution (static)"),
                ),
                Tab(icon: Text("solution (builder)")),
              ],
            ),
            title: const Text('staggered_grid - centering tiles'),
          ),
          body: Padding(
            padding: const EdgeInsets.all(8.0),
            child: TabBarView(
              children: [
                // ------------------------------ problem ---------------------------------
                Scaffold(
                  body: StaggeredGridView.count(
                    crossAxisCount: CROSS_AXIS_COUNT,
                    shrinkWrap: true,
                    mainAxisSpacing: MAIN_AXIS_SPACING,
                    crossAxisSpacing: CROSS_AXIS_SPACING,
                    staggeredTiles: models.map((m) => StaggeredTile.fit(1)).toList(),
                    children: models.map((m) => ModelWidget(m)).toList(),
                  ),
                ),
                // ------------------------------------------------------------------------
                // ------------------------- solution (static) ----------------------------
                Scaffold(
                  body: LayoutBuilder(builder: (context, constraints) {
                    return StaggeredGridView.count(
                      crossAxisCount: CROSS_AXIS_COUNT,
                      shrinkWrap: true,
                      mainAxisSpacing: MAIN_AXIS_SPACING,
                      crossAxisSpacing: CROSS_AXIS_SPACING,
                      staggeredTiles: [
                        ...models.sublist(0, fittingCount).map((m) => StaggeredTile.fit(1)).toList(),
                        if (modelCount != fittingCount) StaggeredTile.fit(CROSS_AXIS_COUNT)
                      ],
                      children: [
                        ...models.sublist(0, fittingCount).map((m) => ModelWidget(m)).toList(),
                        if (modelCount != fittingCount)
                          Row(
                              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                              children: models.sublist(fittingCount, modelCount).map((m) {
                                return Container(
                                    width: constraints.maxWidth / CROSS_AXIS_COUNT - CROSS_AXIS_SPACING,
                                    child: ModelWidget(m));
                              }).toList())
                      ],
                    );
                  }),
                ),
                // ------------------------------------------------------------------------
                // ------------------------- solution (builder) ---------------------------
                Scaffold(
                  body: LayoutBuilder(builder: (context, constraints) {
                    return StaggeredGridView.countBuilder(
                        crossAxisCount: CROSS_AXIS_COUNT,
                        shrinkWrap: true,
                        mainAxisSpacing: MAIN_AXIS_SPACING,
                        crossAxisSpacing: CROSS_AXIS_SPACING,
                        itemCount: modelCount == fittingCount ? fittingCount : fittingCount + 1,
                        itemBuilder: (context, index) {
                          if (index < fittingCount) {
                            return ModelWidget(models.elementAt(index));
                          } else {
                            return Row(
                                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                                children: models.sublist(fittingCount, modelCount).map((m) {
                                  return Container(
                                      width: constraints.maxWidth / CROSS_AXIS_COUNT - CROSS_AXIS_SPACING,
                                      child: ModelWidget(m));
                                }).toList());
                          }
                        },
                        staggeredTileBuilder: (int index) {
                          if (index < fittingCount) {
                            return StaggeredTile.count(1, 1);
                          } else {
                            return StaggeredTile.count(CROSS_AXIS_COUNT, 1);
                          }
                        });
                  }),
                ),
                // ------------------------------------------------------------------------
              ],
            ),
          ),
        ),
      ),
    );
  }
}
Share:
303
Blau
Author by

Blau

Updated on November 20, 2022

Comments

  • Blau
    Blau over 1 year

    I'm working with a StaggeredGridView within Flutter and I don't seem to see a way to center a tile if a tile is going to be the only tile in that "row".

    For example, if I set the crossAxisCount of the StaggeredGridView to something like 6; and then sent the tile's crossAxisCellCount to 4, the tile occupies 4 "cells" starting from the left, leaving 2 "Cells" worth of empty space on the right if there isn't a tile that can occupy 2 cells.

    Is it possible to force the tile to be centered? Essentially, making it occupy cells 2 - 5, leaving 1 empty cell on the left and 1 empty cell on the right?

    I've tried wrapping the StaggeredGridView in a Center widget, but this didn't appear to make a difference.

    Currently I have a stateful widget which has the following build method.

      @override
      Widget build(BuildContext context) {
        return ChangeNotifierProvider<ProfileEditScreenViewModel>(
          create: (context) => model,
          child: Consumer<ProfileEditScreenViewModel>(
            builder: (context, model, child) => Scaffold(
              appBar: AppBar(
                centerTitle: true,
                title: Text(model.screenTitle),
              ),
              body: Center(
                child: StaggeredGridView.count(
                  crossAxisCount: 6,
                  shrinkWrap: true,
                  mainAxisSpacing: 10.0,
                  crossAxisSpacing: 10.0,
                  staggeredTiles: model.displayCardTiles,
                  children: model.displayCards,
                ),
              ),
            ),
          ),
        );
      }
    

    In the View model for that widget there are two relevant functions that the StaggeredGridView uses: the displayCardTiles function that creates the StaggeredTiles, and the displayCards function which creates the widgets that go in the tiles. Those two functions are as follows:

    List<StaggeredTile> _buildDisplayCardTiles(){
        List<StaggeredTile> myList = [];
    
        for(var bioCategory in userProfile.bioCategories!){
          myList.add(StaggeredTile.fit(bioCategory.crossAxisCellCount));
        }
    
        return myList;
      }
    
    List<Widget> _buildDisplayCards(){
        List<Widget> myList = [];
    
        for(var bioCategory in userProfile.bioCategories!){
          myList.add(ProfileItemCard(bioCategory: bioCategory));
        }
    
        return myList;
      }
    

    The "ProfileItemCard" is just a Card widget that displays a variety of Text widgets and checkboxes, but it's contents wouldn't impact the position of the Card within the StaggeredGridView.

    I've also tried wrapping the ProfileItemCard in a Center widget, but it doesn't have any impact on how the Card is displayed.

  • Blau
    Blau about 3 years
    I did give that a try but it didn't seem to work. I should have specified that in my question. My apologies.
  • Blau
    Blau about 3 years
    I appreciate the attempt. I tried the code you suggested; it appears to center the gridview and moves all of the tiles closer to the center of the screen, but it isn't quite the outcome I was looking for. I was hoping that the gridview tiles could take up all available space when needed, but when two tiles couldn't fit on one row, to just center that tile. I'm thinking that this isn't possible; unless, perhaps by creating some transparent dummy tiles to surround the tile that I want centered, but that feels a little messy to me. Thank you again for taking a stab at it for me.