Centering the last row of items in a flutter gridview
Solution 1
There are two answers considering flutter_staggered_grid_view. Although it is a nice package, I think it does much more than you want.
I am providing a more manual, custom implementation.
import 'dart:math';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
/// A [SliverGridLayout] that provide a way to customize the children geometry.
class SliverGridWithCustomGeometryLayout extends SliverGridRegularTileLayout {
/// The builder for each child geometry.
final SliverGridGeometry Function(
int index,
SliverGridRegularTileLayout layout,
) geometryBuilder;
SliverGridWithCustomGeometryLayout({
@required this.geometryBuilder,
@required int crossAxisCount,
@required double mainAxisStride,
@required double crossAxisStride,
@required double childMainAxisExtent,
@required double childCrossAxisExtent,
@required bool reverseCrossAxis,
}) : assert(geometryBuilder != null),
assert(crossAxisCount != null && crossAxisCount > 0),
assert(mainAxisStride != null && mainAxisStride >= 0),
assert(crossAxisStride != null && crossAxisStride >= 0),
assert(childMainAxisExtent != null && childMainAxisExtent >= 0),
assert(childCrossAxisExtent != null && childCrossAxisExtent >= 0),
assert(reverseCrossAxis != null),
super(
crossAxisCount: crossAxisCount,
mainAxisStride: mainAxisStride,
crossAxisStride: crossAxisStride,
childMainAxisExtent: childMainAxisExtent,
childCrossAxisExtent: childCrossAxisExtent,
reverseCrossAxis: reverseCrossAxis,
);
@override
SliverGridGeometry getGeometryForChildIndex(int index) {
return geometryBuilder(index, this);
}
}
/// Creates grid layouts with a fixed number of tiles in the cross axis, such
/// that fhe last element, if the grid item count is odd, is centralized.
class SliverGridDelegateWithFixedCrossAxisCountAndCentralizedLastElement
extends SliverGridDelegateWithFixedCrossAxisCount {
/// The total number of itens in the layout.
final int itemCount;
SliverGridDelegateWithFixedCrossAxisCountAndCentralizedLastElement({
@required this.itemCount,
@required int crossAxisCount,
double mainAxisSpacing = 0.0,
double crossAxisSpacing = 0.0,
double childAspectRatio = 1.0,
}) : assert(itemCount != null && itemCount > 0),
assert(crossAxisCount != null && crossAxisCount > 0),
assert(mainAxisSpacing != null && mainAxisSpacing >= 0),
assert(crossAxisSpacing != null && crossAxisSpacing >= 0),
assert(childAspectRatio != null && childAspectRatio > 0),
super(
crossAxisCount: crossAxisCount,
mainAxisSpacing: mainAxisSpacing,
crossAxisSpacing: crossAxisSpacing,
childAspectRatio: childAspectRatio,
);
bool _debugAssertIsValid() {
assert(crossAxisCount > 0);
assert(mainAxisSpacing >= 0.0);
assert(crossAxisSpacing >= 0.0);
assert(childAspectRatio > 0.0);
return true;
}
@override
SliverGridLayout getLayout(SliverConstraints constraints) {
assert(_debugAssertIsValid());
final usableCrossAxisExtent = max(
0.0,
constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1),
);
final childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount;
final childMainAxisExtent = childCrossAxisExtent / childAspectRatio;
return SliverGridWithCustomGeometryLayout(
geometryBuilder: (index, layout) {
return SliverGridGeometry(
scrollOffset: (index ~/ crossAxisCount) * layout.mainAxisStride,
crossAxisOffset: itemCount.isOdd && index == itemCount - 1
? layout.crossAxisStride / 2
: _getOffsetFromStartInCrossAxis(index, layout),
mainAxisExtent: childMainAxisExtent,
crossAxisExtent: childCrossAxisExtent,
);
},
crossAxisCount: crossAxisCount,
mainAxisStride: childMainAxisExtent + mainAxisSpacing,
crossAxisStride: childCrossAxisExtent + crossAxisSpacing,
childMainAxisExtent: childMainAxisExtent,
childCrossAxisExtent: childCrossAxisExtent,
reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection),
);
}
double _getOffsetFromStartInCrossAxis(
int index,
SliverGridRegularTileLayout layout,
) {
final crossAxisStart = (index % crossAxisCount) * layout.crossAxisStride;
if (layout.reverseCrossAxis) {
return crossAxisCount * layout.crossAxisStride -
crossAxisStart -
layout.childCrossAxisExtent -
(layout.crossAxisStride - layout.childCrossAxisExtent);
}
return crossAxisStart;
}
}
Explanation
Explaining a little, what this code is doing is that it is a custom implementation of SliverGridLayout
, which is the type of class that tells Flutter how to position children on a grid layout.
The more important method here is getGeometryForChildIndex
. This is the method that says where each child should be positioned, based on its index
. Note, however, that we are exposing this method through a parameter to the next class I'll be talking about.
The next class we implement is SliverGridDelegate
. We have to make a custom implementation to use SliverGridWithCustomGeometryLayout
, as there's no other way to make a delegate use a specific SliverGridLayout
. Here we used our parameter geometryBuilder
to delegate to it the role of returning a SliverGridGeometry
.
This implementation of geometryBuilder
is where all the magic happens. It is basically a copy of the original SliverGridRegularTileLayout
method, but with one change. We check if the index of the element is even and if it is also the last one. If both checks pass, we return a centered position. Else, we return the position it would have anyway.
To use our solution, just pass it to the gridDelegate
parameter on a GridView
. Example:
GridView.builder(
itemCount: 9,
itemBuilder: (_, __) =>
Container(width: 100, height: 100, color: Colors.red),
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCountAndCentralizedLastElement(
itemCount: checklist.sections.length(),
crossAxisCount: 2,
childAspectRatio: 0.825,
),
)
Disclaimer
This solution was not exhaustively tested, and I posted it here only to one have the idea about how to make it. However, I am using it in production code. If I eventually find any problem with it, I'll edit here.
Solution 2
How about you try below things?
- Generate GridView with 2n item.
- If there is a remain item, add a widget for last one item.
mindfullsilence
Updated on December 18, 2022Comments
-
mindfullsilence over 1 year
I have a dynamic list of items I am outputting into a
GridView.count
constructor with a mainAxisCount of 2 (2 column grid). If the list length is odd, the last row will only contain a single item. I want this single item to be centered on the screen, rather than being aligned with the first column. Can this be done?-
Admin about 4 years
-
-
Phan Sinh about 3 yearsDid you implement the collapse the column in the specific index? @Mateus Felipe I did implement flutter_staggered_grid_view, but the gridview did not rebuild the column width when the container has resize. :((
-
Mateus Felipe about 3 yearsYes, this example is very fixed, but it shouldn't be hard to generalize.