|
|
@@ -0,0 +1,271 @@ |
|
|
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; |
|
|
} |
|
|
} |