import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'dart:math' as math; class SliverGridPage extends StatefulWidget { @override _SliverGridPageState createState() => _SliverGridPageState(); } class _SliverGridPageState extends State { ScrollController controller; @override void initState() { super.initState(); controller = ScrollController(); } @override Widget build(BuildContext context) { return Scaffold( body: CustomScrollView( controller: controller, slivers: [ Test(controller: controller), ], ), ); } } class Test extends StatelessWidget { const Test({ Key key, @required this.controller, }) : super(key: key); final ScrollController controller; @override Widget build(BuildContext context) { return SliverAnimatedGrid( gridDelegate: SliverGridDelegateWithFixedCrossAxisCountAndAnimation( crossAxisCount: 2, childAspectRatio: 1, mainAxisSpacing: controller.offset, controller: controller, ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { return Container( alignment: Alignment.center, color: Colors.teal[100 * (index % 9) + 100], child: Text( '$index ', style: TextStyle( fontSize: 40, color: Colors.white, fontWeight: FontWeight.bold), ), ); }, childCount: 100, ), ); } } class SliverAnimatedGrid extends SliverMultiBoxAdaptorWidget { const SliverAnimatedGrid({ Key key, @required SliverChildDelegate delegate, @required this.gridDelegate, }) : super(key: key, delegate: delegate); SliverAnimatedGrid.count({ Key key, @required int crossAxisCount, double mainAxisSpacing = 0.0, double crossAxisSpacing = 0.0, double childAspectRatio = 1.0, List children = const [], }) : gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: crossAxisCount, mainAxisSpacing: mainAxisSpacing, crossAxisSpacing: crossAxisSpacing, childAspectRatio: childAspectRatio, ), super(key: key, delegate: SliverChildListDelegate(children)); SliverAnimatedGrid.extent({ Key key, @required double maxCrossAxisExtent, double mainAxisSpacing = 0.0, double crossAxisSpacing = 0.0, double childAspectRatio = 1.0, List children = const [], }) : gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: maxCrossAxisExtent, mainAxisSpacing: mainAxisSpacing, crossAxisSpacing: crossAxisSpacing, childAspectRatio: childAspectRatio, ), super(key: key, delegate: SliverChildListDelegate(children)); final SliverGridDelegate gridDelegate; @override RenderSliverGrid createRenderObject(BuildContext context) { final SliverMultiBoxAdaptorElement element = context; return RenderSliverGrid(childManager: element, gridDelegate: gridDelegate); } @override void updateRenderObject(BuildContext context, RenderSliverGrid renderObject) { renderObject.gridDelegate = gridDelegate; } @override double estimateMaxScrollOffset( SliverConstraints constraints, int firstIndex, int lastIndex, double leadingScrollOffset, double trailingScrollOffset, ) { return super.estimateMaxScrollOffset( constraints, firstIndex, lastIndex, leadingScrollOffset, trailingScrollOffset, ) ?? gridDelegate .getLayout(constraints) .computeMaxScrollOffset(delegate.estimatedChildCount); } } class SliverGridRegularTileLayout extends SliverGridLayout { const SliverGridRegularTileLayout({ @required this.crossAxisCount, @required this.mainAxisStride, @required this.crossAxisStride, @required this.childMainAxisExtent, @required this.childCrossAxisExtent, @required this.reverseCrossAxis, }) : 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); final int crossAxisCount; final double mainAxisStride; final double crossAxisStride; final double childMainAxisExtent; final double childCrossAxisExtent; final bool reverseCrossAxis; @override int getMinChildIndexForScrollOffset(double scrollOffset) { return mainAxisStride > 0.0 ? crossAxisCount * (scrollOffset ~/ mainAxisStride) : 0; } @override int getMaxChildIndexForScrollOffset(double scrollOffset) { if (mainAxisStride > 0.0) { final int mainAxisCount = (scrollOffset / mainAxisStride).ceil(); return math.max(0, crossAxisCount * mainAxisCount - 1); } return 0; } double _getOffsetFromStartInCrossAxis(double crossAxisStart) { if (reverseCrossAxis) return crossAxisCount * crossAxisStride - crossAxisStart - childCrossAxisExtent - (crossAxisStride - childCrossAxisExtent); return crossAxisStart; } @override SliverGridGeometry getGeometryForChildIndex(int index) { final double crossAxisStart = (index % crossAxisCount) * crossAxisStride; return SliverGridGeometry( scrollOffset: (index ~/ crossAxisCount) * mainAxisStride, crossAxisOffset: _getOffsetFromStartInCrossAxis(crossAxisStart), mainAxisExtent: childMainAxisExtent, crossAxisExtent: childCrossAxisExtent, ); } @override double computeMaxScrollOffset(int childCount) { assert(childCount != null); final int mainAxisCount = ((childCount - 1) ~/ crossAxisCount) + 1; final double mainAxisSpacing = mainAxisStride - childMainAxisExtent; return mainAxisStride * mainAxisCount - mainAxisSpacing; } } class SliverGridDelegateWithFixedCrossAxisCountAndAnimation extends SliverGridDelegate { const SliverGridDelegateWithFixedCrossAxisCountAndAnimation({ @required this.crossAxisCount, this.mainAxisSpacing = 0.0, this.crossAxisSpacing = 0.0, this.childAspectRatio = 1.0, this.controller, }) : assert(crossAxisCount != null && crossAxisCount > 0), assert(mainAxisSpacing != null && mainAxisSpacing >= 0), assert(crossAxisSpacing != null && crossAxisSpacing >= 0), assert(childAspectRatio != null && childAspectRatio > 0); /// Added scroll controller to have access to current position (velocity maybe?) final ScrollController controller; final int crossAxisCount; final double mainAxisSpacing; final double crossAxisSpacing; final double 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 double usableCrossAxisExtent = constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1); final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount; final double childMainAxisExtent = childCrossAxisExtent / childAspectRatio; // Here accessing scroll controller velocity final velocity = (controller.position.activity.velocity / 100.0).clamp(0, 20); return SliverGridRegularTileLayout( crossAxisCount: crossAxisCount, mainAxisStride: childMainAxisExtent + mainAxisSpacing + velocity, crossAxisStride: childCrossAxisExtent + crossAxisSpacing, childMainAxisExtent: childMainAxisExtent, childCrossAxisExtent: childCrossAxisExtent, reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection), ); } @override bool shouldRelayout( SliverGridDelegateWithFixedCrossAxisCountAndAnimation oldDelegate) { return oldDelegate.crossAxisCount != crossAxisCount || oldDelegate.mainAxisSpacing != mainAxisSpacing || oldDelegate.crossAxisSpacing != crossAxisSpacing || oldDelegate.childAspectRatio != childAspectRatio; } }