Flutter SliverGrid Flip Axis

Issue

tl;dr How do I flip the X axis in a SliverGrid?

I have to display a long list in a grid (every cell is equal in size). I can request the list from a server with pagination. Now the user should be able to open that list at any index in the grid and should be able to scroll in both direction.

I am trying to create a GridView in Flutter and lazy load its content in both directions (upwards and downwards) while scrolling.

My first attempt was to modify 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. I changed that back to the normal behavior but then only the maxExtend was working the minExtend was always 0 and therefore I was not able to scroll the grid upwards.

My second attempt was a Scrollable with a Viewport. Inside the Viewport I put two SliverGrids (one growing upwards and one growing downwards). I set the center key of the Viewport to the key of the second SliverGrid so the first Item of the downwards growing SliverGrid was the first Cell on screen and the user could scroll to see the cells above and below. With that approach the scrolling extends were kept intact. So the user could only scroll until he reached the end of the list (or the end of the already received cells).

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),
                ),
              ]);
        });
      },
    );
  }
}

With this setup I am facing the problem that I have to flip the X axis for the upper SliverGrid to get the order on the upper half right. But I do not know how.

wrong building order on the x axis in upper half

How do I flip the X axis in a SliverGrid? or achieve increasing order from left to right in the upper half? (I do not know the number of cells per row, so as far as I know I can not manipulate the order by translating the index in the builder).

Demo Code:

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,
  ),
),

Solution

Since I am using a SliverGridDelegateWithMaxCrossAxisExtent as my grid delegate I simply subclassed it and slightly modified getLayout method.

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,
    );
  }
}

Answered By – s0me1

Answer Checked By – Marilyn (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *