Reputation: 452
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?
Upvotes: 5
Views: 6724
Reputation: 1
ListView.builder(
itemCount: (imagePaths.length / 3).ceil() +
(imagePaths.length % 3 == 0
? 0
: 1),
itemBuilder: (BuildContext context, int index) {
final int itemCountInRow = (index ==
(imagePaths.length / 3).ceil() -
1 &&
imagePaths.length % 3 != 0)
? imagePaths.length % 3
: 3;
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment
.center, // Center the last row
children: List.generate(itemCountInRow,
(int subIndex) {
final int actualIndex =
index * 3 + subIndex;
if (actualIndex >= imagePaths.length)
return Container(); // Handle out-of-bounds access
Map<String, dynamic> imageData =
imagePaths[actualIndex];
return card("Your widget"),
}),
),
);
},
),
Upvotes: -1
Reputation: 1171
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;
}
}
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,
),
)
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.
Upvotes: 3
Reputation: 21
You can use StaggeredGridView
StaggeredGridView.countBuilder(
crossAxisCount: 2,
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
itemCount: totalCount,
itemBuilder: (BuildContext context, int index) {
return Center(
child: Container(
width: w,
height: w,
color: Colors.redAccent,
),
);
},
staggeredTileBuilder: (int index) =>
new StaggeredTile.count(index == totalCount-1 ? 2 : 1, 1),
)
Upvotes: 0
Reputation: 11481
You can use the flutter_staggered_grid_view package for this.
Example:
StaggeredGridView.countBuilder(
crossAxisCount: 4,
itemCount: 8,
itemBuilder: (BuildContext context, int index) => Container(
color: Colors.green,
child: Center(
child: new CircleAvatar(
backgroundColor: Colors.white,
child: new Text('$index'),
),
)),
staggeredTileBuilder: (int index) =>
StaggeredTile.count(2, index.isEven ? 2 : 1),
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
)
staggeredTileBuilder
itemBuilder
Upvotes: 0
Reputation: 7492
How about you try below things?
Upvotes: 1