Skip to content

Commit 92e9e0a

Browse files
committed
Animation added!
1 parent 94e4d21 commit 92e9e0a

File tree

1 file changed

+152
-28
lines changed

1 file changed

+152
-28
lines changed

lib/circles_selector/CirclesHomeWidget.dart

+152-28
Original file line numberDiff line numberDiff line change
@@ -20,32 +20,59 @@ class PannableCircleGrid extends StatefulWidget {
2020
_PannableCircleGridState createState() => _PannableCircleGridState();
2121
}
2222

23-
class _PannableCircleGridState extends State<PannableCircleGrid> {
23+
class _PannableCircleGridState extends State<PannableCircleGrid>
24+
with TickerProviderStateMixin {
2425
static const double _circleSize = 80;
2526
static const double _selectedCircleMultiplier = 2;
2627
static const double _spacing = 10;
2728
static const int _columns = 1000;
2829

2930
Offset _offset = Offset.zero;
31+
Map<int, AnimationController> _animationControllers = {};
3032
int? _selectedIndex;
3133

34+
@override
35+
void dispose() {
36+
for (var controller in _animationControllers.values) {
37+
controller.dispose();
38+
}
39+
super.dispose();
40+
}
41+
42+
// void _handleAnimationStatus(AnimationStatus status) {
43+
// if (status == AnimationStatus.completed ||
44+
// status == AnimationStatus.dismissed) {
45+
// setState(() {
46+
// if (_animationController.status == AnimationStatus.dismissed) {
47+
// _deselectedIndex = null;
48+
// }
49+
// }); // Trigger a rebuild to ensure final state is painted
50+
// }
51+
// }
52+
3253
@override
3354
Widget build(BuildContext context) {
3455
return GestureDetector(
3556
onPanUpdate: _handlePan,
3657
child: ClipRect(
37-
child: CustomPaint(
38-
painter: CircleGridPainter(
39-
offset: _offset,
40-
circleSize: _circleSize,
41-
selectedCircleMultiplier: _selectedCircleMultiplier,
42-
spacing: _spacing,
43-
selectedIndex: _selectedIndex,
44-
columns: _columns,
45-
),
46-
child: GestureDetector(
47-
onTapUp: _handleTap,
48-
),
58+
child: AnimatedBuilder(
59+
animation: Listenable.merge(_animationControllers.values.toList()),
60+
builder: (context, child) {
61+
return CustomPaint(
62+
painter: CircleGridPainter(
63+
offset: _offset,
64+
circleSize: _circleSize,
65+
selectedCircleMultiplier: _selectedCircleMultiplier,
66+
spacing: _spacing,
67+
selectedIndex: _selectedIndex,
68+
columns: _columns,
69+
animationControllers: _animationControllers,
70+
),
71+
child: GestureDetector(
72+
onTapUp: _handleTap,
73+
),
74+
);
75+
},
4976
),
5077
),
5178
);
@@ -63,10 +90,73 @@ class _PannableCircleGridState extends State<PannableCircleGrid> {
6390
((tapPosition.dx - _offset.dx) / (_circleSize + _spacing)).floor();
6491
int row =
6592
((tapPosition.dy - _offset.dy) / (_circleSize + _spacing)).floor();
66-
int index = row * _columns + col;
93+
int tappedIndex = row * _columns + col;
94+
6795
setState(() {
68-
_selectedIndex = (_selectedIndex == index) ? null : index;
96+
if (_selectedIndex == tappedIndex) {
97+
// Case 1: Tapping the same circle
98+
_animationControllers[tappedIndex]?.reverse().then((_) {
99+
if (mounted) {
100+
setState(() {
101+
_animationControllers.remove(tappedIndex);
102+
_selectedIndex = null;
103+
});
104+
}
105+
});
106+
} else {
107+
// Case 2: Tapping a different circle
108+
if (_selectedIndex != null) {
109+
int? __selectedIndex = _selectedIndex;
110+
// Collapse the previously selected circle
111+
_animationControllers[_selectedIndex]?.reverse().then((_) {
112+
if (mounted) {
113+
setState(() {
114+
_animationControllers.remove(__selectedIndex);
115+
});
116+
}
117+
});
118+
}
119+
120+
// Expand the newly tapped circle
121+
_animationControllers[tappedIndex] = AnimationController(
122+
duration: const Duration(milliseconds: 300),
123+
vsync: this,
124+
)..forward();
125+
126+
_selectedIndex = tappedIndex;
127+
}
69128
});
129+
130+
// if (_animationControllers.containsKey(tappedIndex)) {
131+
// // Collapse the tapped circle
132+
// _animationControllers[tappedIndex]!.reverse().then((_) {
133+
// setState(() {
134+
// _animationControllers.remove(tappedIndex);
135+
// if (_selectedIndex == tappedIndex) {
136+
// _selectedIndex = null;
137+
// }
138+
// });
139+
// });
140+
// } else {
141+
// // Expand the tapped circle
142+
// _animationControllers[tappedIndex] = AnimationController(
143+
// duration: const Duration(milliseconds: 300),
144+
// vsync: this,
145+
// )..forward();
146+
147+
// // Collapse the previously selected circle, if any
148+
// if (_selectedIndex != null && _selectedIndex != tappedIndex) {
149+
// _animationControllers[_selectedIndex!]?.reverse().then((_) {
150+
// setState(() {
151+
// _animationControllers.remove(_selectedIndex);
152+
// });
153+
// });
154+
// }
155+
156+
// setState(() {
157+
// _selectedIndex = tappedIndex;
158+
// });
159+
// }
70160
}
71161
}
72162

@@ -77,6 +167,7 @@ class CircleGridPainter extends CustomPainter {
77167
final double spacing;
78168
final int? selectedIndex;
79169
final int columns;
170+
final Map<int, AnimationController> animationControllers;
80171

81172
static const double maxDisplacementDistance =
82173
6.0; // Maximum distance for displacement effect
@@ -92,6 +183,7 @@ class CircleGridPainter extends CustomPainter {
92183
required this.spacing,
93184
required this.columns,
94185
this.selectedIndex,
186+
required this.animationControllers,
95187
});
96188

97189
@override
@@ -126,10 +218,14 @@ class CircleGridPainter extends CustomPainter {
126218

127219
Map<Point<int>, Offset> _calculateDisplacements(_VisibleArea visibleArea) {
128220
Map<Point<int>, Offset> displacements = {};
129-
if (selectedIndex != null) {
130-
int selectedCol = selectedIndex! % columns;
131-
int selectedRow = selectedIndex! ~/ columns;
132-
double expansionAmount = circleSize * (selectedCircleMultiplier - 1) / 2;
221+
for (var entry in animationControllers.entries) {
222+
int selectedIndex = entry.key;
223+
double animationValue = entry.value.value;
224+
225+
int selectedCol = selectedIndex % columns;
226+
int selectedRow = selectedIndex ~/ columns;
227+
double expansionAmount =
228+
circleSize * (selectedCircleMultiplier - 1) / 2 * animationValue;
133229
double defaultSpacing = circleSize + spacing;
134230

135231
for (int row = visibleArea.startRow - 2;
@@ -176,10 +272,17 @@ class CircleGridPainter extends CustomPainter {
176272
}
177273

178274
if (displacement > 0) {
179-
displacements[Point(col, row)] = Offset(
275+
Point<int> point = Point(col, row);
276+
Offset newDisplacement = Offset(
180277
cos(angle) * displacement,
181278
sin(angle) * displacement,
182279
);
280+
281+
if (displacements.containsKey(point)) {
282+
displacements[point] = displacements[point]! + newDisplacement;
283+
} else {
284+
displacements[point] = newDisplacement;
285+
}
183286
}
184287
}
185288

@@ -214,24 +317,44 @@ class CircleGridPainter extends CustomPainter {
214317
for (int row = visibleArea.startRow; row <= visibleArea.endRow; row++) {
215318
for (int col = visibleArea.startCol; col <= visibleArea.endCol; col++) {
216319
int index = row * columns + col;
217-
bool isSelected = selectedIndex == index;
320+
bool isSelected = index == selectedIndex;
218321

219322
Offset circleOffset = Offset(
220323
col * (circleSize + spacing) + offset.dx,
221324
row * (circleSize + spacing) + offset.dy,
222325
);
223326

224-
if (!isSelected && displacements.containsKey(Point(col, row))) {
327+
// if (!isSelected && displacements.containsKey(Point(col, row))) {
328+
// circleOffset += displacements[Point(col, row)]! * animationValue;
329+
// }
330+
if (displacements.containsKey(Point(col, row))) {
225331
circleOffset += displacements[Point(col, row)]!;
226332
}
227333

228-
double currentCircleSize =
229-
isSelected ? circleSize * selectedCircleMultiplier : circleSize;
334+
double currentCircleSize = circleSize;
335+
Paint currentPaint = paint;
336+
337+
if (animationControllers.containsKey(index)) {
338+
double animationValue = animationControllers[index]?.value ?? 0.0;
339+
currentCircleSize +=
340+
(circleSize * (selectedCircleMultiplier - 1) * animationValue);
341+
currentPaint = Paint()
342+
..color =
343+
Color.lerp(paint.color, selectedPaint.color, animationValue)!
344+
..style = PaintingStyle.fill;
345+
}
346+
347+
// double currentCircleSize = isSelected
348+
// ? circleSize +
349+
// (circleSize *
350+
// (selectedCircleMultiplier - 1) *
351+
// animationControllers[index]!.value)
352+
// : circleSize;
230353

231354
canvas.drawCircle(
232355
circleOffset,
233356
currentCircleSize / 2,
234-
isSelected ? selectedPaint : paint,
357+
currentPaint,
235358
);
236359

237360
_drawText(
@@ -255,9 +378,10 @@ class CircleGridPainter extends CustomPainter {
255378
}
256379

257380
@override
258-
bool shouldRepaint(covariant CircleGridPainter oldDelegate) =>
259-
offset != oldDelegate.offset ||
260-
selectedIndex != oldDelegate.selectedIndex;
381+
bool shouldRepaint(covariant CircleGridPainter oldDelegate) => true;
382+
// =>
383+
// offset != oldDelegate.offset ||
384+
// animationControllers != oldDelegate.animationControllers;
261385
}
262386

263387
class _VisibleArea {

0 commit comments

Comments
 (0)