Initial commit
This commit is contained in:
+246
@@ -0,0 +1,246 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:math';
|
||||
|
||||
void main() => runApp(const MyApp());
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const MaterialApp(
|
||||
home: MultiDragGrid(widgetCount: 6),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MultiDragGrid extends StatefulWidget {
|
||||
final int widgetCount;
|
||||
const MultiDragGrid({super.key, required this.widgetCount});
|
||||
|
||||
@override
|
||||
State<MultiDragGrid> createState() => _MultiDragGridState();
|
||||
}
|
||||
|
||||
class _MultiDragGridState extends State<MultiDragGrid> {
|
||||
final double gridSize = 40;
|
||||
final int widgetGridSize = 9;
|
||||
late List<Offset> positions;
|
||||
Map<Offset, int> cellCount = {};
|
||||
Set<Offset> overlappingCells = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
positions = List.generate(
|
||||
widget.widgetCount,
|
||||
(i) => Offset(0.0 + i * 40.0, 0.0),
|
||||
);
|
||||
_recalculateCellCounts();
|
||||
}
|
||||
|
||||
Offset _snapToGrid(Offset pos) {
|
||||
final double x = (pos.dx / gridSize).round() * gridSize;
|
||||
final double y = (pos.dy / gridSize).round() * gridSize;
|
||||
return Offset(x, y);
|
||||
}
|
||||
|
||||
List<Offset> _getGridCells(Offset snappedPos) {
|
||||
return [
|
||||
for (int dx = 0; dx < widgetGridSize; dx++)
|
||||
for (int dy = 0; dy < widgetGridSize; dy++)
|
||||
Offset(snappedPos.dx + dx * gridSize, snappedPos.dy + dy * gridSize),
|
||||
];
|
||||
}
|
||||
|
||||
void _recalculateCellCounts() {
|
||||
final Map<Offset, int> count = {};
|
||||
for (var pos in positions) {
|
||||
final snapped = _snapToGrid(pos);
|
||||
for (var cell in _getGridCells(snapped)) {
|
||||
count[cell] = (count[cell] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
overlappingCells = count.entries.where((e) => e.value > 1).map((e) => e.key).toSet();
|
||||
cellCount = count;
|
||||
}
|
||||
|
||||
void _updatePosition(int index, Offset newPos) {
|
||||
setState(() {
|
||||
positions[index] = _snapToGrid(newPos);
|
||||
_recalculateCellCounts();
|
||||
});
|
||||
}
|
||||
|
||||
void _updateDraggingPosition(int index, Offset newPos) {
|
||||
setState(() {
|
||||
positions[index] = newPos;
|
||||
_recalculateCellCounts();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Drag ${widget.widgetCount} Widgets + Overlap Detection')),
|
||||
body: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: SizedBox(
|
||||
width: 5000,
|
||||
height: 5000,
|
||||
child: InteractiveViewer(
|
||||
boundaryMargin: const EdgeInsets.all(double.infinity),
|
||||
minScale: 0.2,
|
||||
maxScale: 5.0,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
...positions.asMap().entries.expand((entry) {
|
||||
final snapped = _snapToGrid(entry.value);
|
||||
return _getGridCells(snapped).map((cellOffset) {
|
||||
final highlight = overlappingCells.contains(cellOffset);
|
||||
return Positioned(
|
||||
left: cellOffset.dx,
|
||||
top: cellOffset.dy,
|
||||
child: Container(
|
||||
width: gridSize,
|
||||
height: gridSize,
|
||||
decoration: BoxDecoration(
|
||||
color: highlight ? Colors.yellow.withOpacity(0.6) : Colors.transparent,
|
||||
border: Border.all(color: Colors.black12),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}),
|
||||
...positions.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final position = entry.value;
|
||||
return DraggableWidget(
|
||||
index: index,
|
||||
initialPosition: position,
|
||||
gridSize: gridSize,
|
||||
widgetGridSize: widgetGridSize,
|
||||
onDragEnd: (newPos) => _updatePosition(index, newPos),
|
||||
onDragUpdate: (newPos) => _updateDraggingPosition(index, newPos),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DraggableWidget extends StatefulWidget {
|
||||
final int index;
|
||||
final Offset initialPosition;
|
||||
final double gridSize;
|
||||
final int widgetGridSize;
|
||||
final void Function(Offset) onDragEnd;
|
||||
final void Function(Offset) onDragUpdate;
|
||||
|
||||
const DraggableWidget({
|
||||
required this.index,
|
||||
required this.initialPosition,
|
||||
required this.gridSize,
|
||||
required this.widgetGridSize,
|
||||
required this.onDragEnd,
|
||||
required this.onDragUpdate,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<DraggableWidget> createState() => _DraggableWidgetState();
|
||||
}
|
||||
|
||||
class _DraggableWidgetState extends State<DraggableWidget> {
|
||||
late Offset position;
|
||||
bool isDragging = false;
|
||||
|
||||
Offset _snapToGrid(Offset pos) {
|
||||
final double x = (pos.dx / widget.gridSize).round() * widget.gridSize;
|
||||
final double y = (pos.dy / widget.gridSize).round() * widget.gridSize;
|
||||
return Offset(x, y);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
position = _snapToGrid(widget.initialPosition);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Positioned(
|
||||
left: position.dx,
|
||||
top: position.dy,
|
||||
child: GestureDetector(
|
||||
onPanStart: (_) {
|
||||
setState(() => isDragging = true);
|
||||
},
|
||||
onPanUpdate: (details) {
|
||||
setState(() {
|
||||
position += details.delta;
|
||||
widget.onDragUpdate(position);
|
||||
});
|
||||
},
|
||||
onPanEnd: (details) {
|
||||
final snapped = _snapToGrid(position);
|
||||
widget.onDragEnd(snapped);
|
||||
setState(() {
|
||||
position = snapped;
|
||||
isDragging = false;
|
||||
});
|
||||
},
|
||||
child: Opacity(
|
||||
opacity: isDragging ? 0.7 : 1.0,
|
||||
child: CustomPaint(
|
||||
size: Size(widget.gridSize * widget.widgetGridSize, widget.gridSize * widget.widgetGridSize),
|
||||
painter: GridPainter(gridSize: widget.gridSize),
|
||||
child: Container(
|
||||
width: widget.gridSize * widget.widgetGridSize,
|
||||
height: widget.gridSize * widget.widgetGridSize,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
border: Border.all(color: Colors.blueAccent, width: 4),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GridPainter extends CustomPainter {
|
||||
final double gridSize;
|
||||
GridPainter({required this.gridSize});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final Paint finePaint = Paint()
|
||||
..color = Colors.grey[400]!
|
||||
..strokeWidth = 1;
|
||||
|
||||
final Paint boldPaint = Paint()
|
||||
..color = Colors.grey[700]!
|
||||
..strokeWidth = 2;
|
||||
|
||||
for (double x = 0; x <= size.width; x += gridSize) {
|
||||
final paint = ((x / gridSize) % 3 == 0) ? boldPaint : finePaint;
|
||||
canvas.drawLine(Offset(x, 0), Offset(x, size.height), paint);
|
||||
}
|
||||
for (double y = 0; y <= size.height; y += gridSize) {
|
||||
final paint = ((y / gridSize) % 3 == 0) ? boldPaint : finePaint;
|
||||
canvas.drawLine(Offset(0, y), Offset(size.width, y), paint);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(CustomPainter oldDelegate) => false;
|
||||
}
|
||||
Reference in New Issue
Block a user