Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save orestesgaolin/6a1b3b0275f76fb38d472b9b1dfd9421 to your computer and use it in GitHub Desktop.

Select an option

Save orestesgaolin/6a1b3b0275f76fb38d472b9b1dfd9421 to your computer and use it in GitHub Desktop.

Revisions

  1. orestesgaolin revised this gist May 14, 2020. 2 changed files with 117 additions and 127 deletions.
    148 changes: 117 additions & 31 deletions flutter_spanablegrid.dart
    Original file line number Diff line number Diff line change
    @@ -4,6 +4,92 @@ import 'package:flutter/material.dart';
    import 'package:flutter/src/rendering/sliver.dart';
    import 'package:flutter/src/rendering/sliver_grid.dart';

    void main() => runApp(MyApp());

    class MyApp extends StatelessWidget {
    // This widget is the root of your application.
    @override
    Widget build(BuildContext context) {
    return new MaterialApp(
    title: 'Flutter Demo',
    theme: new ThemeData(
    primarySwatch: Colors.blue,
    ),
    home: new MyHomePage(),
    );
    }
    }

    class MyHomePage extends StatefulWidget {
    MyHomePage({Key key}) : super(key: key);
    @override
    _MyHomePageState createState() => new _MyHomePageState();
    }

    class HomeChildDelegate extends SliverChildDelegate {
    @override
    Widget build(BuildContext context, int index) {
    if (index >= 20) return null;

    Color color = Colors.red;

    if (index == 0)
    color = Colors.blue;
    else if (index == 1 || index == 10)
    color = Colors.cyan;
    else if (index < 10) color = Colors.green;
    ;

    return new Container(
    decoration: new BoxDecoration(color: color, shape: BoxShape.rectangle));
    }

    @override
    bool shouldRebuild(SliverChildDelegate oldDelegate) => true;

    @override
    int get estimatedChildCount => 20;
    }

    class HomeGridDelegate extends SpanableSliverGridDelegate {
    HomeGridDelegate() : super(3, mainAxisSpacing: 10.0, crossAxisSpacing: 10.0);

    @override
    int getCrossAxisSpan(int index) {
    if (index > 1 && index < 10) return 1;

    return 3;
    }

    @override
    double getMainAxisExtent(int index) {
    if (index == 0) return 220.0;

    if (index == 1 || index == 10) return 50.0;

    return 100.0;
    }
    }

    class _MyHomePageState extends State<MyHomePage> {
    _MyHomePageState();

    Widget _buildBody(BuildContext context) {
    return new GridView.custom(
    gridDelegate: new HomeGridDelegate(),
    childrenDelegate: new HomeChildDelegate(),
    padding: new EdgeInsets.all(12.0),
    );
    }

    @override
    Widget build(BuildContext context) {
    return new Scaffold(
    body: _buildBody(context),
    );
    }
    }

    class _CoordinateOffset {
    final double main, cross;
    _CoordinateOffset(this.main, this.cross);
    @@ -14,19 +100,18 @@ typedef int GetCrossAxisSpan(int index);
    typedef double GetMainAxisExtent(int index);

    class SpanableSliverGridLayout extends SliverGridLayout {

    /// Creates a layout that uses equally sized and spaced tiles.
    ///
    /// All of the arguments must not be null and must not be negative. The
    /// `crossAxisCount` argument must be greater than zero.
    const SpanableSliverGridLayout(
    this.crossAxisCount,
    this.childCrossAxisExtent,
    this.crossAxisStride,
    this.mainAxisSpacing,
    this.getCrossAxisSpan,
    this.getMainAxisExtend) :
    assert(crossAxisCount != null && crossAxisCount > 0),
    this.crossAxisCount,
    this.childCrossAxisExtent,
    this.crossAxisStride,
    this.mainAxisSpacing,
    this.getCrossAxisSpan,
    this.getMainAxisExtend)
    : assert(crossAxisCount != null && crossAxisCount > 0),
    assert(mainAxisSpacing != null && mainAxisSpacing >= 0),
    assert(childCrossAxisExtent != null && childCrossAxisExtent >= 0),
    assert(crossAxisStride != null && crossAxisStride >= 0),
    @@ -53,18 +138,17 @@ class SpanableSliverGridLayout extends SliverGridLayout {
    final GetMainAxisExtent getMainAxisExtend;

    _CoordinateOffset _findOffset(int index) {
    int cross= 0;
    int cross = 0;
    double mainOffset = 0.0;
    double crossOffset = 0.0;
    double extend = 0.0;
    int span;

    for (int i = 0; i <= index; i++) {

    span = getCrossAxisSpan(i);
    span = math.min(this.crossAxisCount, math.max(0, span));

    if((cross + span) > this.crossAxisCount) {
    if ((cross + span) > this.crossAxisCount) {
    cross = 0;
    mainOffset += extend + this.mainAxisSpacing;
    crossOffset = 0.0;
    @@ -101,19 +185,20 @@ class SpanableSliverGridLayout extends SliverGridLayout {

    if (min && scrollOffset <= mainOffset + extend) {
    return (i ~/ this.crossAxisCount) * this.crossAxisCount;
    }
    else if(!min && scrollOffset < mainOffset) {
    } else if (!min && scrollOffset < mainOffset) {
    return i;
    }
    i++;
    }
    }

    @override
    int getMinChildIndexForScrollOffset(double scrollOffset) => getMinOrMaxChildIndexForScrollOffset(scrollOffset, true);
    int getMinChildIndexForScrollOffset(double scrollOffset) =>
    getMinOrMaxChildIndexForScrollOffset(scrollOffset, true);

    @override
    int getMaxChildIndexForScrollOffset(double scrollOffset) => getMinOrMaxChildIndexForScrollOffset(scrollOffset, false);
    int getMaxChildIndexForScrollOffset(double scrollOffset) =>
    getMinOrMaxChildIndexForScrollOffset(scrollOffset, false);

    @override
    SliverGridGeometry getGeometryForChildIndex(int index) {
    @@ -125,17 +210,17 @@ class SpanableSliverGridLayout extends SliverGridLayout {
    scrollOffset: offset.main,
    crossAxisOffset: offset.cross,
    mainAxisExtent: mainAxisExtent,
    crossAxisExtent: this.childCrossAxisExtent + (span - 1) * this.crossAxisStride,
    crossAxisExtent:
    this.childCrossAxisExtent + (span - 1) * this.crossAxisStride,
    );
    }

    @override
    double estimateMaxScrollOffset(int childCount)
    {
    if(childCount <= 0)
    return 0.0;
    double computeMaxScrollOffset(int childCount) {
    if (childCount <= 0) return 0.0;

    var lastOffset = _findOffset(childCount-1);
    var extent = getMainAxisExtend(childCount-1);
    var lastOffset = _findOffset(childCount - 1);
    var extent = getMainAxisExtend(childCount - 1);
    return lastOffset.main + extent;
    }
    }
    @@ -148,10 +233,10 @@ abstract class SpanableSliverGridDelegate extends SliverGridDelegate {
    /// `crossAxisSpacing` arguments must not be negative. The `crossAxisCount`
    /// and `childAspectRatio` arguments must be greater than zero.
    const SpanableSliverGridDelegate(
    this.crossAxisCount,
    {this.mainAxisSpacing: 0.0,
    this.crossAxisSpacing: 0.0,
    }) : assert(crossAxisCount != null && crossAxisCount > 0),
    this.crossAxisCount, {
    this.mainAxisSpacing: 0.0,
    this.crossAxisSpacing: 0.0,
    }) : assert(crossAxisCount != null && crossAxisCount > 0),
    assert(mainAxisSpacing != null && mainAxisSpacing >= 0),
    assert(crossAxisSpacing != null && crossAxisSpacing >= 0);

    @@ -174,7 +259,8 @@ abstract class SpanableSliverGridDelegate extends SliverGridDelegate {
    @override
    SliverGridLayout getLayout(SliverConstraints constraints) {
    assert(_debugAssertIsValid());
    final double usableCrossAxisExtent = constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1);
    final double usableCrossAxisExtent =
    constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1);
    final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount;
    return new SpanableSliverGridLayout(
    crossAxisCount,
    @@ -192,8 +278,8 @@ abstract class SpanableSliverGridDelegate extends SliverGridDelegate {

    @override
    bool shouldRelayout(SpanableSliverGridDelegate oldDelegate) {
    return oldDelegate.crossAxisCount != crossAxisCount
    || oldDelegate.mainAxisSpacing != mainAxisSpacing
    || oldDelegate.crossAxisSpacing != crossAxisSpacing;
    return oldDelegate.crossAxisCount != crossAxisCount ||
    oldDelegate.mainAxisSpacing != mainAxisSpacing ||
    oldDelegate.crossAxisSpacing != crossAxisSpacing;
    }
    }
    }
    96 changes: 0 additions & 96 deletions flutter_spanablegrid_usage.dart
    Original file line number Diff line number Diff line change
    @@ -1,96 +0,0 @@
    import 'package:flutter/material.dart';
    import 'spanablelayout.dart';


    void main() => runApp(new MyApp());

    class MyApp extends StatelessWidget {
    // This widget is the root of your application.
    @override
    Widget build(BuildContext context) {
    return new MaterialApp(
    title: 'Flutter Demo',
    theme: new ThemeData(
    primarySwatch: Colors.blue,
    ),
    home: new MyHomePage(),
    );
    }
    }

    class MyHomePage extends StatefulWidget {
    MyHomePage({Key key}) : super(key: key);
    @override
    _MyHomePageState createState() => new _MyHomePageState();
    }

    class HomeChildDelegate extends SliverChildDelegate {

    @override
    Widget build(BuildContext context, int index) {

    if(index >= 20)
    return null;

    Color color = Colors.red;

    if(index == 0)
    color = Colors.blue;
    else if(index == 1 || index == 10)
    color = Colors.cyan;
    else if(index < 10)
    color = Colors.green;;

    return new Container(decoration: new BoxDecoration(color: color , shape: BoxShape.rectangle));
    }

    @override
    bool shouldRebuild(SliverChildDelegate oldDelegate) => true;

    @override
    int get estimatedChildCount => 20;
    }

    class HomeGridDelegate extends SpanableSliverGridDelegate {

    HomeGridDelegate() : super(3, mainAxisSpacing: 10.0, crossAxisSpacing: 10.0);

    @override
    int getCrossAxisSpan(int index) {
    if(index > 1 && index < 10)
    return 1;

    return 3;
    }

    @override
    double getMainAxisExtent(int index) {
    if(index == 0)
    return 220.0;

    if(index == 1 || index == 10)
    return 50.0;

    return 100.0;
    }
    }

    class _MyHomePageState extends State<MyHomePage> {

    _MyHomePageState() ;

    Widget _buildBody(BuildContext context) {
    return new GridView.custom(
    gridDelegate: new HomeGridDelegate(),
    childrenDelegate: new HomeChildDelegate(),
    padding: new EdgeInsets.all(12.0),
    );
    }

    @override
    Widget build(BuildContext context) {
    return new Scaffold(
    body: _buildBody(context),
    );
    }
    }
  2. @aloisdeniel aloisdeniel revised this gist Jan 6, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion flutter_spanablegrid.dart
    Original file line number Diff line number Diff line change
    @@ -191,7 +191,7 @@ abstract class SpanableSliverGridDelegate extends SliverGridDelegate {
    double getMainAxisExtent(int index);

    @override
    bool shouldRelayout(SliverGridDelegateWithFixedCrossAxisCount oldDelegate) {
    bool shouldRelayout(SpanableSliverGridDelegate oldDelegate) {
    return oldDelegate.crossAxisCount != crossAxisCount
    || oldDelegate.mainAxisSpacing != mainAxisSpacing
    || oldDelegate.crossAxisSpacing != crossAxisSpacing;
  3. @aloisdeniel aloisdeniel revised this gist Jan 6, 2018. 2 changed files with 0 additions and 0 deletions.
    File renamed without changes.
    File renamed without changes.
  4. @aloisdeniel aloisdeniel renamed this gist Jan 6, 2018. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  5. @aloisdeniel aloisdeniel revised this gist Jan 6, 2018. No changes.
  6. @aloisdeniel aloisdeniel created this gist Jan 6, 2018.
    96 changes: 96 additions & 0 deletions example.dart
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,96 @@
    import 'package:flutter/material.dart';
    import 'spanablelayout.dart';


    void main() => runApp(new MyApp());

    class MyApp extends StatelessWidget {
    // This widget is the root of your application.
    @override
    Widget build(BuildContext context) {
    return new MaterialApp(
    title: 'Flutter Demo',
    theme: new ThemeData(
    primarySwatch: Colors.blue,
    ),
    home: new MyHomePage(),
    );
    }
    }

    class MyHomePage extends StatefulWidget {
    MyHomePage({Key key}) : super(key: key);
    @override
    _MyHomePageState createState() => new _MyHomePageState();
    }

    class HomeChildDelegate extends SliverChildDelegate {

    @override
    Widget build(BuildContext context, int index) {

    if(index >= 20)
    return null;

    Color color = Colors.red;

    if(index == 0)
    color = Colors.blue;
    else if(index == 1 || index == 10)
    color = Colors.cyan;
    else if(index < 10)
    color = Colors.green;;

    return new Container(decoration: new BoxDecoration(color: color , shape: BoxShape.rectangle));
    }

    @override
    bool shouldRebuild(SliverChildDelegate oldDelegate) => true;

    @override
    int get estimatedChildCount => 20;
    }

    class HomeGridDelegate extends SpanableSliverGridDelegate {

    HomeGridDelegate() : super(3, mainAxisSpacing: 10.0, crossAxisSpacing: 10.0);

    @override
    int getCrossAxisSpan(int index) {
    if(index > 1 && index < 10)
    return 1;

    return 3;
    }

    @override
    double getMainAxisExtent(int index) {
    if(index == 0)
    return 220.0;

    if(index == 1 || index == 10)
    return 50.0;

    return 100.0;
    }
    }

    class _MyHomePageState extends State<MyHomePage> {

    _MyHomePageState() ;

    Widget _buildBody(BuildContext context) {
    return new GridView.custom(
    gridDelegate: new HomeGridDelegate(),
    childrenDelegate: new HomeChildDelegate(),
    padding: new EdgeInsets.all(12.0),
    );
    }

    @override
    Widget build(BuildContext context) {
    return new Scaffold(
    body: _buildBody(context),
    );
    }
    }
    199 changes: 199 additions & 0 deletions spanablegrid.dart
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,199 @@
    import 'dart:math' as math;

    import 'package:flutter/material.dart';
    import 'package:flutter/src/rendering/sliver.dart';
    import 'package:flutter/src/rendering/sliver_grid.dart';

    class _CoordinateOffset {
    final double main, cross;
    _CoordinateOffset(this.main, this.cross);
    }

    typedef int GetCrossAxisSpan(int index);

    typedef double GetMainAxisExtent(int index);

    class SpanableSliverGridLayout extends SliverGridLayout {

    /// Creates a layout that uses equally sized and spaced tiles.
    ///
    /// All of the arguments must not be null and must not be negative. The
    /// `crossAxisCount` argument must be greater than zero.
    const SpanableSliverGridLayout(
    this.crossAxisCount,
    this.childCrossAxisExtent,
    this.crossAxisStride,
    this.mainAxisSpacing,
    this.getCrossAxisSpan,
    this.getMainAxisExtend) :
    assert(crossAxisCount != null && crossAxisCount > 0),
    assert(mainAxisSpacing != null && mainAxisSpacing >= 0),
    assert(childCrossAxisExtent != null && childCrossAxisExtent >= 0),
    assert(crossAxisStride != null && crossAxisStride >= 0),
    assert(getCrossAxisSpan != null),
    assert(getMainAxisExtend != null);

    /// The number of children in the cross axis.
    final int crossAxisCount;

    /// The number of pixels from the leading edge of one tile to the trailing
    /// edge of the same tile in the main axis.
    final double mainAxisSpacing;

    /// The number of pixels from the leading edge of one tile to the leading edge
    /// of the next tile in the cross axis.
    final double crossAxisStride;

    /// The number of pixels from the leading edge of one tile to the trailing
    /// edge of the same tile in the cross axis.
    final double childCrossAxisExtent;

    final GetCrossAxisSpan getCrossAxisSpan;

    final GetMainAxisExtent getMainAxisExtend;

    _CoordinateOffset _findOffset(int index) {
    int cross= 0;
    double mainOffset = 0.0;
    double crossOffset = 0.0;
    double extend = 0.0;
    int span;

    for (int i = 0; i <= index; i++) {

    span = getCrossAxisSpan(i);
    span = math.min(this.crossAxisCount, math.max(0, span));

    if((cross + span) > this.crossAxisCount) {
    cross = 0;
    mainOffset += extend + this.mainAxisSpacing;
    crossOffset = 0.0;
    extend = 0.0;
    }

    crossOffset = cross * crossAxisStride;
    extend = math.max(extend, getMainAxisExtend(i));
    cross += span;
    }

    return new _CoordinateOffset(mainOffset, crossOffset);
    }

    int getMinOrMaxChildIndexForScrollOffset(double scrollOffset, bool min) {
    int cross = 0;
    double mainOffset = 0.0;
    double extend = 0.0;
    int i = 0;
    int span = 0;

    while (true) {
    span = getCrossAxisSpan(i);
    span = math.min(this.crossAxisCount, math.max(0, span));

    if ((cross + span) > this.crossAxisCount) {
    cross = 0;
    mainOffset += extend + this.mainAxisSpacing;
    extend = 0.0;
    }

    extend = math.max(extend, getMainAxisExtend(i));
    cross += span;

    if (min && scrollOffset <= mainOffset + extend) {
    return (i ~/ this.crossAxisCount) * this.crossAxisCount;
    }
    else if(!min && scrollOffset < mainOffset) {
    return i;
    }
    i++;
    }
    }

    @override
    int getMinChildIndexForScrollOffset(double scrollOffset) => getMinOrMaxChildIndexForScrollOffset(scrollOffset, true);

    @override
    int getMaxChildIndexForScrollOffset(double scrollOffset) => getMinOrMaxChildIndexForScrollOffset(scrollOffset, false);

    @override
    SliverGridGeometry getGeometryForChildIndex(int index) {
    var span = getCrossAxisSpan(index);
    var mainAxisExtent = getMainAxisExtend(index);
    var offset = _findOffset(index);

    return new SliverGridGeometry(
    scrollOffset: offset.main,
    crossAxisOffset: offset.cross,
    mainAxisExtent: mainAxisExtent,
    crossAxisExtent: this.childCrossAxisExtent + (span - 1) * this.crossAxisStride,
    );
    }
    @override
    double estimateMaxScrollOffset(int childCount)
    {
    if(childCount <= 0)
    return 0.0;

    var lastOffset = _findOffset(childCount-1);
    var extent = getMainAxisExtend(childCount-1);
    return lastOffset.main + extent;
    }
    }

    abstract class SpanableSliverGridDelegate extends SliverGridDelegate {
    /// Creates a delegate that makes grid layouts with a fixed number of tiles in
    /// the cross axis.
    ///
    /// All of the arguments must not be null. The `mainAxisSpacing` and
    /// `crossAxisSpacing` arguments must not be negative. The `crossAxisCount`
    /// and `childAspectRatio` arguments must be greater than zero.
    const SpanableSliverGridDelegate(
    this.crossAxisCount,
    {this.mainAxisSpacing: 0.0,
    this.crossAxisSpacing: 0.0,
    }) : assert(crossAxisCount != null && crossAxisCount > 0),
    assert(mainAxisSpacing != null && mainAxisSpacing >= 0),
    assert(crossAxisSpacing != null && crossAxisSpacing >= 0);

    /// The number of children in the cross axis.
    final int crossAxisCount;

    /// The number of logical pixels between each child along the main axis.
    final double mainAxisSpacing;

    /// The number of logical pixels between each child along the cross axis.
    final double crossAxisSpacing;

    bool _debugAssertIsValid() {
    assert(crossAxisCount > 0);
    assert(mainAxisSpacing >= 0.0);
    assert(crossAxisSpacing >= 0.0);
    return true;
    }

    @override
    SliverGridLayout getLayout(SliverConstraints constraints) {
    assert(_debugAssertIsValid());
    final double usableCrossAxisExtent = constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1);
    final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount;
    return new SpanableSliverGridLayout(
    crossAxisCount,
    childCrossAxisExtent,
    childCrossAxisExtent + crossAxisSpacing,
    mainAxisSpacing,
    getCrossAxisSpan,
    getMainAxisExtent,
    );
    }

    int getCrossAxisSpan(int index);

    double getMainAxisExtent(int index);

    @override
    bool shouldRelayout(SliverGridDelegateWithFixedCrossAxisCount oldDelegate) {
    return oldDelegate.crossAxisCount != crossAxisCount
    || oldDelegate.mainAxisSpacing != mainAxisSpacing
    || oldDelegate.crossAxisSpacing != crossAxisSpacing;
    }
    }