Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save kmartins/8a89d75d45d11fcd388c1d7abf76b1ce to your computer and use it in GitHub Desktop.
Save kmartins/8a89d75d45d11fcd388c1d7abf76b1ce to your computer and use it in GitHub Desktop.
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<SliverGridPage> {
ScrollController controller;
@override
void initState() {
super.initState();
controller = ScrollController();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
controller: controller,
slivers: <Widget>[
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<Widget> children = const <Widget>[],
}) : 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<Widget> children = const <Widget>[],
}) : 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;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment