Last active
August 10, 2023 11:27
-
-
Save aloisdeniel/81d6a5241ca9e2f82f17cf3c001444c5 to your computer and use it in GitHub Desktop.
Custom GridView with various cell sizes in Flutter
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 '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(SpanableSliverGridDelegate oldDelegate) { | |
return oldDelegate.crossAxisCount != crossAxisCount | |
|| oldDelegate.mainAxisSpacing != mainAxisSpacing | |
|| oldDelegate.crossAxisSpacing != crossAxisSpacing; | |
} | |
} |
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 '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), | |
); | |
} | |
} |
I guess it's estimateMaxScrollOffset
must have been renamed
This can only span on cross axis though, not on the main axis as well?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
error: Missing concrete implementation of SliverGridLayout.computeMaxScrollOffset. (non_abstract_class_inherits_abstract_member_one at [example] lib/main_test.dart:16) with last stable version @aloisdeniel :)