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.

Revisions

  1. @orestesgaolin orestesgaolin created this gist Aug 25, 2019.
    271 changes: 271 additions & 0 deletions animated_sliver_grid.dart
    Original file line number Diff line number Diff line change
    @@ -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;
    }
    }