246 lines
7.2 KiB
Dart
246 lines
7.2 KiB
Dart
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;
|
|
} |