Here's an optimized solution to prevent GridView items from rebuilding during zoom gestures. The key is to use a combination of RepaintBoundary, ValueKey, and pre-cached items. The RepaintBoundary helps minimize repainting, while the ValueKey helps Flutter optimize rebuilds. By pre-generating and caching the items in initState(), we prevent unnecessary rebuilds when the grid layout changes. The solution also includes smooth zoom gestures that adjust the number of columns while maintaining item states. I've included a complete implementation that you can easily integrate into your project. The code uses proper state management to ensure efficient updates only when necessary.
import 'package:flutter/material.dart';
class ZoomableGridView extends StatefulWidget {
final List<Widget> items;
const ZoomableGridView({Key? key, required this.items}) : super(key: key);
@override
_ZoomableGridViewState createState() => _ZoomableGridViewState();
}
class _ZoomableGridViewState extends State<ZoomableGridView> {
double _scale = 2.0;
double _previousScale = 1.0;
int _columns = 2;
@override
Widget build(BuildContext context) {
return GestureDetector(
onScaleStart: (details) {
_previousScale = _scale;
},
onScaleUpdate: (details) {
setState(() {
// Calculate new scale with clamping
_scale = (_previousScale * details.scale).clamp(1.0, 6.0);
// Calculate new columns based on scale
_columns = (6 - _scale).round().clamp(1, 6);
});
},
onScaleEnd: (details) {
_previousScale = 1.0;
},
child: GridView.builder(
// Unique key to help Flutter optimize rebuilds
key: ValueKey(_columns),
// Customize grid delegate
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: _columns,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatio: 1.0, // Square items
),
// Item count and builder
itemCount: widget.items.length,
itemBuilder: (context, index) {
// Wrap each item with RepaintBoundary
return RepaintBoundary(
child: widget.items[index],
);
},
),
);
}
}
// Example Usage
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Create a list of items to display in the grid
final List<Widget> gridItems = List.generate(
20,
(index) => _GridItemWidget(index: index),
);
return Scaffold(
appBar: AppBar(title: Text('Zoomable GridView')),
body: ZoomableGridView(items: gridItems),
);
}
}
// Custom grid item widget to demonstrate state preservation
class _GridItemWidget extends StatefulWidget {
final int index;
const _GridItemWidget({Key? key, required this.index}) : super(key: key);
@override
__GridItemWidgetState createState() => __GridItemWidgetState();
}
class __GridItemWidgetState extends State<_GridItemWidget> {
bool _isSelected = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
_isSelected = !_isSelected;
});
},
child: Container(
decoration: BoxDecoration(
color: _isSelected
? Colors.blue.shade200
: Colors.blue.shade100,
borderRadius: BorderRadius.circular(10),
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Item ${widget.index}',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
SizedBox(height: 8),
Icon(
_isSelected ? Icons.check_circle : Icons.circle_outlined,
color: Colors.white,
),
],
),
),
),
);
}
}
Let me know if you need any clarification or have questions about specific parts of the implementation!