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

min*_*nce 5 gridview flutter flutter-layout

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

Mat*_*ipe 2

考虑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;
  }
}
Run Code Online (Sandbox Code Playgroud)

解释

稍微解释一下,这段代码的作用是它是 的自定义实现SliverGridLayout,它是告诉 Flutter 如何在网格布局上定位子级的类类型。

这里比较重要的方法是getGeometryForChildIndex。该方法表示每个子项应根据其 定位在哪里index。但请注意,我们通过参数向我将要讨论的下一个类公开此方法。

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

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

要使用我们的解决方案,只需将其传递gridDelegateGridView. 例子:

GridView.builder(
    itemCount: 9,
    itemBuilder: (_, __) =>
        Container(width: 100, height: 100, color: Colors.red),
    gridDelegate:
        SliverGridDelegateWithFixedCrossAxisCountAndCentralizedLastElement(
            itemCount: checklist.sections.length(),
            crossAxisCount: 2,
            childAspectRatio: 0.825,
        ),
)
Run Code Online (Sandbox Code Playgroud)

免责声明

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