-
-
Save orestesgaolin/2348974b0c114c0bf08def9a88921546 to your computer and use it in GitHub Desktop.
staggered grid
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:math' as math; | |
import 'dart:math'; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter/rendering.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 { | |
final int itemCount; | |
HomeChildDelegate(this.itemCount); | |
@override | |
Widget build(BuildContext context, int index) { | |
if (index > itemCount) 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(HomeChildDelegate oldDelegate) => | |
itemCount != oldDelegate.itemCount; | |
@override | |
int get estimatedChildCount => itemCount; | |
} | |
class WidgetSpan { | |
final int widthSpan; | |
final int heightSpan; | |
WidgetSpan(this.widthSpan, this.heightSpan); | |
} | |
class HomeGridDelegate extends SpannableSliverGridDelegate { | |
final List<WidgetSpan> config; | |
HomeGridDelegate(int crossAxisCount, {this.config}) | |
: super(crossAxisCount, mainAxisSpacing: 10.0, crossAxisSpacing: 10.0); | |
@override | |
int getCrossAxisSpan(int index) { | |
if (config.length <= index) { | |
return 1; | |
} | |
return config[index].widthSpan; | |
} | |
@override | |
double getMainAxisExtent(int index) { | |
if (config.length <= index) { | |
return .0; | |
} | |
return config[index].heightSpan * 200.0; | |
} | |
} | |
class _MyHomePageState extends State<MyHomePage> { | |
static const widgetWidth = 300; | |
static final List<WidgetSpan> config = [ | |
WidgetSpan(1, 1), | |
WidgetSpan(2, 1), | |
WidgetSpan(1, 2), | |
WidgetSpan(1, 1), | |
WidgetSpan(1, 1), | |
]; | |
_MyHomePageState(); | |
Widget _buildBody(BuildContext context) { | |
return LayoutBuilder( | |
builder: (context, constraint) { | |
return GridView.custom( | |
gridDelegate: HomeGridDelegate( | |
(constraint.maxWidth / widgetWidth).floor(), | |
config: config), | |
childrenDelegate: HomeChildDelegate(config.length - 1), | |
padding: 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); | |
} | |
typedef int GetCrossAxisSpan(int index); | |
typedef double GetMainAxisExtent(int index); | |
class SpannableSliverGridLayout 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 SpannableSliverGridLayout( | |
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 computeMaxScrollOffset(int childCount) { | |
if (childCount <= 0) return 0.0; | |
var lastOffset = _findOffset(childCount); | |
var maxExtent = .0; | |
for (var i = 0; i <= childCount; i++) { | |
maxExtent = max(maxExtent, getMainAxisExtend(childCount - i)); | |
} | |
return lastOffset.main + maxExtent; | |
} | |
} | |
abstract class SpannableSliverGridDelegate 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 SpannableSliverGridDelegate( | |
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 SpannableSliverGridLayout( | |
crossAxisCount, | |
childCrossAxisExtent, | |
childCrossAxisExtent + crossAxisSpacing, | |
mainAxisSpacing, | |
getCrossAxisSpan, | |
getMainAxisExtent, | |
); | |
} | |
int getCrossAxisSpan(int index); | |
double getMainAxisExtent(int index); | |
@override | |
bool shouldRelayout(SpannableSliverGridDelegate oldDelegate) { | |
return oldDelegate.crossAxisCount != crossAxisCount || | |
oldDelegate.mainAxisSpacing != mainAxisSpacing || | |
oldDelegate.crossAxisSpacing != crossAxisSpacing; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment