Skip to content

Commit b56102c

Browse files
committed
fix: ci
1 parent e8173f7 commit b56102c

3 files changed

Lines changed: 173 additions & 165 deletions

File tree

config/cspell.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"dismissable",
1010
"elemento",
1111
"elementos",
12+
"extralong",
1213
"formz",
1314
"freerasp",
1415
"frontmatter",
@@ -28,6 +29,8 @@
2829
"pubspec",
2930
"serialization",
3031
"stdio",
32+
"subclassing",
33+
"vsync",
3134
"WCAG",
3235
"widgetbook",
3336
"Widgetbook",

skills/animations/SKILL.md

Lines changed: 29 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Apply these standards to ALL animation work:
2828

2929
Choose the simplest approach that meets the requirement:
3030

31-
```
31+
```text
3232
Does the widget rebuild when the value changes?
3333
|
3434
YES --> Does the framework provide an AnimatedFoo widget?
@@ -63,17 +63,17 @@ Use Flutter's built-in `Durations` and `Easing` classes. These align with the Ma
6363
| `Durations.short3` | 150ms | Fades, color changes |
6464
| `Durations.short4` | 200ms | Small element enter/exit |
6565
| `Durations.medium1` | 250ms | Component expand/collapse |
66-
| `Durations.medium2` | 300ms | Standard transitions |
67-
| `Durations.medium3` | 350ms | Medium-distance moves |
68-
| `Durations.medium4` | 400ms | Page transitions |
69-
| `Durations.long1` | 450ms | Complex layout changes |
66+
| `Durations.medium2` | 300ms | Standard transitions |
67+
| `Durations.medium3` | 350ms | Medium-distance moves |
68+
| `Durations.medium4` | 400ms | Page transitions |
69+
| `Durations.long1` | 450ms | Complex layout changes |
7070
| `Durations.long2` | 500ms | Large element enter/exit |
71-
| `Durations.long3` | 550ms | Full-screen transitions |
72-
| `Durations.long4` | 600ms | Extended motion sequences |
73-
| `Durations.extralong1` | 700ms | Staggered group animations |
74-
| `Durations.extralong2` | 800ms | Complex staggered sequences |
75-
| `Durations.extralong3` | 900ms | Elaborate choreographed motion |
76-
| `Durations.extralong4` | 1000ms | Maximum recommended animation length |
71+
| `Durations.long3` | 550ms | Full-screen transitions |
72+
| `Durations.long4` | 600ms | Extended motion sequences |
73+
| `Durations.extralong1` | 700ms | Staggered group animations |
74+
| `Durations.extralong2` | 800ms | Complex staggered sequences |
75+
| `Durations.extralong3` | 900ms | Elaborate choreographed motion |
76+
| `Durations.extralong4` | 1000ms | Maximum recommended animation length |
7777

7878
### Easing Tokens
7979

@@ -82,11 +82,11 @@ Use Flutter's built-in `Durations` and `Easing` classes. These align with the Ma
8282
| `Easing.standard` | Default for most animations |
8383
| `Easing.standardDecelerate` | Elements entering the screen |
8484
| `Easing.standardAccelerate` | Elements leaving the screen |
85-
| `Easing.emphasized` | Important transitions that need emphasis |
85+
| `Easing.emphasized` | Important transitions that need emphasis |
8686
| `Easing.emphasizedDecelerate` | Hero/shared element entering |
8787
| `Easing.emphasizedAccelerate` | Hero/shared element leaving |
8888
| `Easing.legacy` | Backward compatibility only — avoid in new code |
89-
| `Easing.linear` | Progress indicators, continuous rotation |
89+
| `Easing.linear` | Progress indicators, continuous rotation |
9090

9191
### Centralized Motion Constants
9292

@@ -157,20 +157,20 @@ AnimatedSlide(
157157

158158
### Built-in Implicit Widgets Reference
159159

160-
| Widget | Animated Property |
161-
| ------------------------ | ------------------------------ |
162-
| `AnimatedContainer` | Multiple (size, color, border) |
163-
| `AnimatedOpacity` | `opacity` |
164-
| `AnimatedSlide` | `offset` (as fraction of size) |
165-
| `AnimatedAlign` | `alignment` |
166-
| `AnimatedPadding` | `padding` |
167-
| `AnimatedPositioned` | `left`, `top`, `right`, etc. |
168-
| `AnimatedScale` | `scale` |
169-
| `AnimatedRotation` | `turns` |
170-
| `AnimatedSwitcher` | Cross-fade between children |
171-
| `AnimatedCrossFade` | Cross-fade between two widgets |
172-
| `AnimatedSize` | Animates size changes of child |
173-
| `AnimatedDefaultTextStyle`| `style` |
160+
| Widget | Animated Property |
161+
| -------------------------- | ------------------------------ |
162+
| `AnimatedContainer` | Multiple (size, color, border) |
163+
| `AnimatedOpacity` | `opacity` |
164+
| `AnimatedSlide` | `offset` (as fraction of size) |
165+
| `AnimatedAlign` | `alignment` |
166+
| `AnimatedPadding` | `padding` |
167+
| `AnimatedPositioned` | `left`, `top`, `right`, etc. |
168+
| `AnimatedScale` | `scale` |
169+
| `AnimatedRotation` | `turns` |
170+
| `AnimatedSwitcher` | Cross-fade between children |
171+
| `AnimatedCrossFade` | Cross-fade between two widgets |
172+
| `AnimatedSize` | Animates size changes of child |
173+
| `AnimatedDefaultTextStyle` | `style` |
174174

175175
See [references/animated-switcher.md](references/animated-switcher.md) for `AnimatedSwitcher` patterns including custom transitions and size-change handling.
176176

@@ -249,143 +249,7 @@ class _MyWidgetState extends State<MyWidget>
249249
}
250250
```
251251

252-
### Responding to Widget Updates
253-
254-
Use `didUpdateWidget` to start, stop, or reverse an animation when a property changes:
255-
256-
```dart
257-
class _AnimatedGlowState extends State<AnimatedGlow>
258-
with SingleTickerProviderStateMixin {
259-
late final AnimationController _controller;
260-
261-
@override
262-
void initState() {
263-
super.initState();
264-
_controller = AnimationController(
265-
duration: Durations.long2,
266-
vsync: this,
267-
);
268-
if (widget.isGlowing) {
269-
_controller.repeat(reverse: true);
270-
}
271-
}
272-
273-
@override
274-
void didUpdateWidget(AnimatedGlow oldWidget) {
275-
super.didUpdateWidget(oldWidget);
276-
if (widget.isGlowing != oldWidget.isGlowing) {
277-
if (widget.isGlowing) {
278-
_controller.repeat(reverse: true);
279-
} else {
280-
_controller.stop();
281-
_controller.reset();
282-
}
283-
}
284-
}
285-
286-
@override
287-
void dispose() {
288-
_controller.dispose();
289-
super.dispose();
290-
}
291-
292-
// ...
293-
}
294-
```
295-
296-
Do not start animations in `build()`. Use `initState` for initial playback and `didUpdateWidget` for subsequent state changes.
297-
298-
### Constructor Injection for Testable Controllers
299-
300-
Expose an optional controller parameter to allow tests to drive the animation directly:
301-
302-
```dart
303-
class PulsingDot extends StatefulWidget {
304-
const PulsingDot({
305-
required this.isActive,
306-
super.key,
307-
@visibleForTesting this.controller,
308-
});
309-
310-
final bool isActive;
311-
312-
@visibleForTesting
313-
final AnimationController? controller;
314-
315-
@override
316-
State<PulsingDot> createState() => _PulsingDotState();
317-
}
318-
319-
class _PulsingDotState extends State<PulsingDot>
320-
with SingleTickerProviderStateMixin {
321-
late final AnimationController _controller;
322-
bool _ownsController = false;
323-
324-
@override
325-
void initState() {
326-
super.initState();
327-
if (widget.controller != null) {
328-
_controller = widget.controller!;
329-
} else {
330-
_ownsController = true;
331-
_controller = AnimationController(
332-
duration: Durations.long2,
333-
vsync: this,
334-
);
335-
}
336-
}
337-
338-
@override
339-
void dispose() {
340-
if (_ownsController) {
341-
_controller.dispose();
342-
}
343-
super.dispose();
344-
}
345-
346-
// ...
347-
}
348-
```
349-
350-
Only dispose the controller if the widget created it. Tests that inject a controller are responsible for its lifecycle.
351-
352-
### Transition Widgets vs AnimatedBuilder
353-
354-
**Single property** — use the built-in transition widget directly. Less code, same performance:
355-
356-
```dart
357-
// Good — single property, use the transition widget
358-
FadeTransition(
359-
opacity: _fadeAnimation,
360-
child: child,
361-
)
362-
```
363-
364-
**Multiple properties combined** — use `AnimatedBuilder` to compose them in one builder:
365-
366-
```dart
367-
// Good — multiple properties, use AnimatedBuilder
368-
AnimatedBuilder(
369-
animation: _controller,
370-
builder: (context, child) {
371-
return Opacity(
372-
opacity: _fadeAnimation.value,
373-
child: SlideTransition(
374-
position: _slideAnimation,
375-
child: child,
376-
),
377-
);
378-
},
379-
child: child,
380-
)
381-
```
382-
383-
| Scenario | Use |
384-
| --- | --- |
385-
| Animate one property (opacity, position, scale, rotation) | `FadeTransition`, `SlideTransition`, `ScaleTransition`, `RotationTransition` |
386-
| Animate multiple properties together | `AnimatedBuilder` with manual composition |
387-
388-
**Avoid subclassing `AnimatedWidget`** — couples the animation to a specific widget class, making reuse harder.
252+
See [references/explicit-animations.md](references/explicit-animations.md) for `didUpdateWidget` patterns, constructor injection for testable controllers, and transition widget vs `AnimatedBuilder` guidance.
389253

390254
### Chained Animations with Intervals
391255

@@ -583,7 +447,7 @@ AnimatedOpacity(
583447
## Additional Resources
584448

585449
- [references/animated-switcher.md](references/animated-switcher.md)`AnimatedSwitcher` patterns (cross-fade, custom transitions, size changes)
450+
- [references/explicit-animations.md](references/explicit-animations.md)`didUpdateWidget`, testable controllers, transition widgets vs `AnimatedBuilder`
586451
- [references/staggered-animations.md](references/staggered-animations.md) — staggered entry animations and staggered list items
587452
- [references/page-transitions.md](references/page-transitions.md) — reusable `AppPageTransitions` helper and GoRouter integration
588453
- [references/looping-animations.md](references/looping-animations.md) — repeating, pulsing, and continuous rotation patterns
589-
- [Animation Testing](../testing/references/animation-testing.md) — testing patterns for implicit, explicit, and page transition animations
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Explicit Animation Patterns
2+
3+
Detailed patterns for `AnimationController`-based animations. See the main skill file for core setup and standards.
4+
5+
## Responding to Widget Updates
6+
7+
Use `didUpdateWidget` to start, stop, or reverse an animation when a property changes:
8+
9+
```dart
10+
class _AnimatedGlowState extends State<AnimatedGlow>
11+
with SingleTickerProviderStateMixin {
12+
late final AnimationController _controller;
13+
14+
@override
15+
void initState() {
16+
super.initState();
17+
_controller = AnimationController(
18+
duration: Durations.long2,
19+
vsync: this,
20+
);
21+
if (widget.isGlowing) {
22+
_controller.repeat(reverse: true);
23+
}
24+
}
25+
26+
@override
27+
void didUpdateWidget(AnimatedGlow oldWidget) {
28+
super.didUpdateWidget(oldWidget);
29+
if (widget.isGlowing != oldWidget.isGlowing) {
30+
if (widget.isGlowing) {
31+
_controller.repeat(reverse: true);
32+
} else {
33+
_controller.stop();
34+
_controller.reset();
35+
}
36+
}
37+
}
38+
39+
@override
40+
void dispose() {
41+
_controller.dispose();
42+
super.dispose();
43+
}
44+
45+
// ...
46+
}
47+
```
48+
49+
Do not start animations in `build()`. Use `initState` for initial playback and `didUpdateWidget` for subsequent state changes.
50+
51+
## Constructor Injection for Testable Controllers
52+
53+
Expose an optional controller parameter to allow tests to drive the animation directly:
54+
55+
```dart
56+
class PulsingDot extends StatefulWidget {
57+
const PulsingDot({
58+
required this.isActive,
59+
super.key,
60+
@visibleForTesting this.controller,
61+
});
62+
63+
final bool isActive;
64+
65+
@visibleForTesting
66+
final AnimationController? controller;
67+
68+
@override
69+
State<PulsingDot> createState() => _PulsingDotState();
70+
}
71+
72+
class _PulsingDotState extends State<PulsingDot>
73+
with SingleTickerProviderStateMixin {
74+
late final AnimationController _controller;
75+
bool _ownsController = false;
76+
77+
@override
78+
void initState() {
79+
super.initState();
80+
if (widget.controller != null) {
81+
_controller = widget.controller!;
82+
} else {
83+
_ownsController = true;
84+
_controller = AnimationController(
85+
duration: Durations.long2,
86+
vsync: this,
87+
);
88+
}
89+
}
90+
91+
@override
92+
void dispose() {
93+
if (_ownsController) {
94+
_controller.dispose();
95+
}
96+
super.dispose();
97+
}
98+
99+
// ...
100+
}
101+
```
102+
103+
Only dispose the controller if the widget created it. Tests that inject a controller are responsible for its lifecycle.
104+
105+
## Transition Widgets vs AnimatedBuilder
106+
107+
**Single property** — use the built-in transition widget directly. Less code, same performance:
108+
109+
```dart
110+
// Good — single property, use the transition widget
111+
FadeTransition(
112+
opacity: _fadeAnimation,
113+
child: child,
114+
)
115+
```
116+
117+
**Multiple properties combined** — use `AnimatedBuilder` to compose them in one builder:
118+
119+
```dart
120+
// Good — multiple properties, use AnimatedBuilder
121+
AnimatedBuilder(
122+
animation: _controller,
123+
builder: (context, child) {
124+
return Opacity(
125+
opacity: _fadeAnimation.value,
126+
child: SlideTransition(
127+
position: _slideAnimation,
128+
child: child,
129+
),
130+
);
131+
},
132+
child: child,
133+
)
134+
```
135+
136+
| Scenario | Use |
137+
| --------------------------------------------------------- | ---------------------------------------------------------------------------- |
138+
| Animate one property (opacity, position, scale, rotation) | `FadeTransition`, `SlideTransition`, `ScaleTransition`, `RotationTransition` |
139+
| Animate multiple properties together | `AnimatedBuilder` with manual composition |
140+
141+
**Avoid subclassing `AnimatedWidget`** — couples the animation to a specific widget class, making reuse harder.

0 commit comments

Comments
 (0)