在 flutter gridview 中将最后一行项目居中
Centering the last row of items in a flutter gridview
我有一个要输出到 GridView.count
构造函数的动态项目列表,其中 mainAxisCount 为 2(2 列网格)。如果列表长度为奇数,最后一行将只包含一个项目。我希望这个单个项目在屏幕上居中,而不是与第一列对齐。这能做到吗?
你试试下面的东西怎么样?
- 生成包含 2n 个项目的 GridView。
- 如果还有剩余项,请为最后一项添加一个小部件。
您可以为此使用 flutter_staggered_grid_view 包。
示例:
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,
)
- Return
staggeredTileBuilder
最后一行的跨度计数为 1
- Return
itemBuilder
中最后一行的居中小部件
您可以使用 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),
)
考虑到 flutter_staggered_grid_view,有两个答案。虽然它是一个不错的包,但我认为它的功能比你想要的要多得多。
我提供了一个更手动、自定义的实现。
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;
}
}
说明
稍微解释一下,这段代码所做的是它是 SliverGridLayout
的自定义实现,它是 class 的类型,它告诉 Flutter 如何在网格布局上定位子项。
这里比较重要的方法是getGeometryForChildIndex
。这是根据其 index
说明每个子项应放置在何处的方法。但是请注意,我们通过参数将此方法公开给下一个 class 我将要谈论的
我们实施的下一个 class 是 SliverGridDelegate
。我们必须自定义实现才能使用 SliverGridWithCustomGeometryLayout
,因为没有其他方法可以让委托使用特定的 SliverGridLayout
。在这里,我们使用参数 geometryBuilder
将 returning a SliverGridGeometry
.
的角色委托给它
geometryBuilder
的实现是所有魔法发生的地方。它基本上是原始 SliverGridRegularTileLayout
方法的副本,但有一个变化。我们检查元素的索引是否偶数以及它是否也是最后一个。如果两个检查都通过,我们 return 居中位置。否则,我们 return 它无论如何都会有的位置。
要使用我们的解决方案,只需将其传递给 GridView
上的 gridDelegate
参数即可。示例:
GridView.builder(
itemCount: 9,
itemBuilder: (_, __) =>
Container(width: 100, height: 100, color: Colors.red),
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCountAndCentralizedLastElement(
itemCount: checklist.sections.length(),
crossAxisCount: 2,
childAspectRatio: 0.825,
),
)
免责声明
这个解决方案没有经过详尽的测试,我把它贴在这里只是为了让那些对如何制作它有想法的人。但是,我在生产代码中使用它。如果我最终发现它有任何问题,我会在这里编辑。
我有一个要输出到 GridView.count
构造函数的动态项目列表,其中 mainAxisCount 为 2(2 列网格)。如果列表长度为奇数,最后一行将只包含一个项目。我希望这个单个项目在屏幕上居中,而不是与第一列对齐。这能做到吗?
你试试下面的东西怎么样?
- 生成包含 2n 个项目的 GridView。
- 如果还有剩余项,请为最后一项添加一个小部件。
您可以为此使用 flutter_staggered_grid_view 包。
示例:
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,
)
- Return
staggeredTileBuilder
最后一行的跨度计数为 1
- Return
itemBuilder
中最后一行的居中小部件
您可以使用 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),
)
考虑到 flutter_staggered_grid_view,有两个答案。虽然它是一个不错的包,但我认为它的功能比你想要的要多得多。
我提供了一个更手动、自定义的实现。
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;
}
}
说明
稍微解释一下,这段代码所做的是它是 SliverGridLayout
的自定义实现,它是 class 的类型,它告诉 Flutter 如何在网格布局上定位子项。
这里比较重要的方法是getGeometryForChildIndex
。这是根据其 index
说明每个子项应放置在何处的方法。但是请注意,我们通过参数将此方法公开给下一个 class 我将要谈论的
我们实施的下一个 class 是 SliverGridDelegate
。我们必须自定义实现才能使用 SliverGridWithCustomGeometryLayout
,因为没有其他方法可以让委托使用特定的 SliverGridLayout
。在这里,我们使用参数 geometryBuilder
将 returning a SliverGridGeometry
.
geometryBuilder
的实现是所有魔法发生的地方。它基本上是原始 SliverGridRegularTileLayout
方法的副本,但有一个变化。我们检查元素的索引是否偶数以及它是否也是最后一个。如果两个检查都通过,我们 return 居中位置。否则,我们 return 它无论如何都会有的位置。
要使用我们的解决方案,只需将其传递给 GridView
上的 gridDelegate
参数即可。示例:
GridView.builder(
itemCount: 9,
itemBuilder: (_, __) =>
Container(width: 100, height: 100, color: Colors.red),
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCountAndCentralizedLastElement(
itemCount: checklist.sections.length(),
crossAxisCount: 2,
childAspectRatio: 0.825,
),
)
免责声明
这个解决方案没有经过详尽的测试,我把它贴在这里只是为了让那些对如何制作它有想法的人。但是,我在生产代码中使用它。如果我最终发现它有任何问题,我会在这里编辑。