Flutter SliverGrid 翻转轴

Flutter SliverGrid Flip Axis

tl;dr 如何在 SliverGrid 中翻转 X 轴?

我必须在网格中显示一个长列表(每个单元格的大小都相等)。我可以通过分页从服务器请求列表。现在用户应该能够在网格中的任何索引处打开该列表,并且应该能够在两个方向上滚动。

我正在尝试在 Flutter 中创建一个 GridView 并在滚动时在两个方向(向上和向下)延迟加载其内容。

我的第一次尝试是修改infinite_listview to become an gridview. It looked really promising at first but the problem was that the min and maxExtend was overridden to be always negative and positive infinite。我将其改回正常行为,但随后只有 maxExtend 起作用,minExtend 始终为 0,因此我无法向上滚动网格。

我的第二次尝试是带视口的 Scrollable。在视口内,我放置了两个 SliverGrid(一个向上生长,一个向下生长)。我将视口的中心键设置为第二个 SliverGrid 的键,因此向下增长的 SliverGrid 的第一个项目是屏幕上的第一个单元格,用户可以滚动查看上方和下方的单元格。通过这种方法,滚动扩展保持不变。因此用户只能滚动直到他到达列表的末尾(或已收到的单元格的末尾)。

class BidirectionalGridView extends StatelessWidget {
  static const Key centerKey = ValueKey("CENTER");
  final SliverGridDelegate gridDelegate;
  final IndexedWidgetBuilder itemBuilder;
  final int upperItemCount;
  final int lowerItemCount;

  const BidirectionalGridView({
    required this.gridDelegate,
    required this.itemBuilder,
    required this.upperItemCount,
    required this.lowerItemCount,
    Key? key,
  }) : super(key: key);

  int get totalItemCount => upperItemCount + lowerItemCount;

  @override
  Widget build(BuildContext context) {
    return Scrollable(
      axisDirection: AxisDirection.down,
      viewportBuilder: (context, position) {
        return Builder(builder: (context) {
          return Viewport(
              center: centerKey,
              offset: position,
              anchor: 0,
              slivers: [
                SliverGrid(
                  gridDelegate: gridDelegate,
                  delegate: SliverChildBuilderDelegate(
                      (BuildContext context, int index) {
                    return itemBuilder(context, upperItemCount - 1 - index);
                  }, childCount: upperItemCount),
                ),
                SliverGrid(
                  key: centerKey,
                  gridDelegate: gridDelegate,
                  delegate: SliverChildBuilderDelegate(
                      (BuildContext context, int index) {
                    return itemBuilder(context, upperItemCount + index);
                  }, childCount: lowerItemCount),
                ),
              ]);
        });
      },
    );
  }
}

使用此设置,我面临的问题是我必须翻转上部 SliverGrid 的 X 轴才能获得右上半部分的顺序。但是我不知道怎么办。

如何在SliverGrid中翻转X轴?或者在上半部分实现从左到右递增的顺序? (我不知道每行的单元格数量,据我所知,我无法通过在构建器中翻译索引来操纵顺序)。

演示代码:

var items = <int>[];
  for (var i = 0; i < 300; ++i) {
    items.add(i - 100);
  }

/*
 * ...
 */

BidirectionalGridView(
  upperItemCount: 100,
  lowerItemCount: 200,
  itemBuilder: (context, index) {
    return SizedBox.square(
      dimension: 72,
      child: Align(
        alignment: Alignment.center,
        child: Text("${items[index]}"),
      ),
    );
  },
  gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
    maxCrossAxisExtent: 72,
    crossAxisSpacing: 4,
    mainAxisSpacing: 4,
    childAspectRatio: 1,
  ),
),

因为我使用 SliverGridDelegateWithMaxCrossAxisExtent 作为我的网格委托,所以我简单地对其进行了子类化并稍微修改了 getLayout 方法。

class CrossAxisFlippedSliverGridDelegateWithMaxCrossAxisExtent
    extends SliverGridDelegateWithMaxCrossAxisExtent {
  /// Creates a delegate that makes grid layouts with tiles that have a maximum
  /// cross-axis extent and are flipped along the cross axis.
  /// ...
  const CrossAxisFlippedSliverGridDelegateWithMaxCrossAxisExtent({
    ...
    }) : super(...);

  @override
  SliverGridLayout getLayout(SliverConstraints constraints) {
    var tileLayout = super.getLayout(constraints) as SliverGridRegularTileLayout;

    return SliverGridRegularTileLayout(
      crossAxisCount: tileLayout.crossAxisCount,
      mainAxisStride: tileLayout.mainAxisStride,
      crossAxisStride: tileLayout.crossAxisStride,
      childMainAxisExtent: tileLayout.childMainAxisExtent,
      childCrossAxisExtent: tileLayout.childCrossAxisExtent,

      // Where the actual magic happens
      reverseCrossAxis: !tileLayout.reverseCrossAxis,
    );
  }
}