在颤动的网格视图中将最后一行项目居中

Posted

技术标签:

【中文标题】在颤动的网格视图中将最后一行项目居中【英文标题】:Centering the last row of items in a flutter gridview 【发布时间】:2020-06-18 21:16:11 【问题描述】:

我有一个项目的动态列表,我正在输出到一个 mainAxisCount 为 2(2 列网格)的 GridView.count 构造函数中。如果列表长度为奇数,则最后一行将只包含一个项目。我希望这一项在屏幕上居中,而不是与第一列对齐。这个可以吗?

【问题讨论】:

***.com/questions/57955051/… 【参考方案1】:

你试试下面的东西怎么样?

    使用 2n 项生成 GridView。 如果有剩余项,则为最后一项添加小部件。

【讨论】:

【参考方案2】:

您可以为此使用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,
)
    staggeredTileBuilder 中返回最后一行的跨度计数为 1 为itemBuilder 中的最后一行返回居中小部件

【讨论】:

【参考方案3】:

你可以使用 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),
)

【讨论】:

【参考方案4】:

考虑到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 的自定义实现,这是告诉Flutter 如何在网格布局上定位子级的类的类型。

这里比较重要的方法是getGeometryForChildIndex。这是根据index 说明每个孩子应该定位的方法。但是请注意,我们通过一个参数将这个方法暴露给我将要讨论的下一个类。

我们实现的下一个类是SliverGridDelegate。我们必须自定义实现才能使用SliverGridWithCustomGeometryLayout,因为没有其他方法可以让委托使用特定的SliverGridLayout。在这里,我们使用参数geometryBuilder 来委派它返回SliverGridGeometry 的角色。

geometryBuilder 的这个实现是所有魔法发生的地方。它基本上是原始SliverGridRegularTileLayout 方法的副本,但有一个变化。我们检查元素的索引是否是偶数以及它是否也是最后一个。如果两个检查都通过,我们返回一个居中的位置。否则,我们将返回它本来应该有的位置。

要使用我们的解决方案,只需将其传递给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,
        ),
)

免责声明

这个解决方案没有经过详尽的测试,我在这里发布它只是为了让一个知道如何制作它的人。但是,我在生产代码中使用它。如果我最终发现它有任何问题,我会在这里编辑。

【讨论】:

你是否实现了特定索引中的列折叠? @Mateus Felipe我确实实现了flutter_staggered_grid_view,但是当容器调整大小时gridview没有重建列宽。 :(( 是的,这个例子很固定,但应该不难概括。

以上是关于在颤动的网格视图中将最后一行项目居中的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Flutter 中将 gridView 项目居中?

在颤动中显示带有网格视图的图像

尽管宽度相等,但集合视图网格最后一行的显示方式不同 - swift iOS

当项目具有动态高度时,如何根据设备方向创建动态颤动网格布局

颤振中的自定义高度网格视图

居中网格内容,同时左对齐每一行的内容,具有任意项/列宽度