Skip to content

Instantly share code, notes, and snippets.

@stevenosse
Last active June 1, 2024 16:53
Show Gist options
  • Save stevenosse/94b7fb7fc2115c126354244d8a47aa62 to your computer and use it in GitHub Desktop.
Save stevenosse/94b7fb7fc2115c126354244d8a47aa62 to your computer and use it in GitHub Desktop.
POC: Zoomable GridView widget
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: ZoomableGridView(
itemCount: 300,
itemBuilder: (context, index) => Container(
color: Colors.blue,
margin: const EdgeInsets.all(4.0),
alignment: Alignment.center,
child: Text(index.toString()),
),
),
);
}
}
typedef ItemBuilder = Widget Function(BuildContext, int);
class ZoomableGridView extends StatefulWidget {
/// The builder for each item in the grid.
final ItemBuilder itemBuilder;
/// The duration of the animation when the grid is zoomed.
final Duration duration;
/// The minimum number of items per row.
final int minItemsPerRow;
/// The maximum number of items per row.
final int maxItemsPerRow;
/// The number of items in the grid.
final int itemCount;
/// The scale factor of the grid.
/// A smaller value will make the grid smaller.
final double scaleFactor;
const ZoomableGridView({
super.key,
required this.itemBuilder,
required this.itemCount,
this.scaleFactor = 1.0,
this.minItemsPerRow = 2,
this.maxItemsPerRow = 10,
this.duration = const Duration(milliseconds: 300),
});
@override
State<ZoomableGridView> createState() => _ZoomableGridViewState();
}
class _ZoomableGridViewState extends State<ZoomableGridView> {
late double _scaleFactor = widget.scaleFactor;
double _baseScaleFactor = 1.0;
@override
Widget build(BuildContext context) {
return InteractiveViewer(
panEnabled: false,
scaleEnabled: false,
minScale: 0.5,
maxScale: 3.0,
onInteractionUpdate: (details) =>
setState(() => _scaleFactor = _baseScaleFactor * details.scale),
onInteractionEnd: (_) => setState(() => _baseScaleFactor = _scaleFactor),
child: LayoutBuilder(
builder: (context, constraints) {
final int itemsPerRow = (4 / _scaleFactor).clamp(widget.minItemsPerRow, widget.maxItemsPerRow).toInt();
final double itemWidth = constraints.maxWidth / itemsPerRow;
return AnimatedSwitcher(
duration: widget.duration,
child: GridView.builder(
key: ValueKey<int>(itemsPerRow),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: itemsPerRow,
childAspectRatio: 1.0,
),
itemCount: widget.itemCount,
itemBuilder: (context, index) {
return AnimatedContainer(
duration: widget.duration,
width: itemWidth,
height: itemWidth,
child: widget.itemBuilder(context, index),
);
},
),
);
},
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment