Showing
7 changed files
with
1124 additions
and
0 deletions
@@ -3,6 +3,7 @@ | @@ -3,6 +3,7 @@ | ||
3 | /// injection, and route management in a quick and practical way. | 3 | /// injection, and route management in a quick and practical way. |
4 | library get; | 4 | library get; |
5 | 5 | ||
6 | +export 'get_animations/index.dart'; | ||
6 | export 'get_common/get_reset.dart'; | 7 | export 'get_common/get_reset.dart'; |
7 | export 'get_connect/connect.dart'; | 8 | export 'get_connect/connect.dart'; |
8 | export 'get_core/get_core.dart'; | 9 | export 'get_core/get_core.dart'; |
lib/get_animations/animations.dart
0 → 100644
1 | +import 'dart:math'; | ||
2 | + | ||
3 | +import 'package:flutter/widgets.dart'; | ||
4 | + | ||
5 | +import 'get_animated_builder.dart'; | ||
6 | + | ||
7 | +class FadeInAnimation extends OpacityAnimation { | ||
8 | + FadeInAnimation({ | ||
9 | + super.key, | ||
10 | + required super.duration, | ||
11 | + required super.delay, | ||
12 | + required super.child, | ||
13 | + super.onComplete, | ||
14 | + super.begin = 0, | ||
15 | + super.end = 1, | ||
16 | + super.idleValue = 0, | ||
17 | + }); | ||
18 | +} | ||
19 | + | ||
20 | +class FadeOutAnimation extends OpacityAnimation { | ||
21 | + FadeOutAnimation({ | ||
22 | + super.key, | ||
23 | + required super.duration, | ||
24 | + required super.delay, | ||
25 | + required super.child, | ||
26 | + super.onComplete, | ||
27 | + super.begin = 1, | ||
28 | + super.end = 0, | ||
29 | + super.idleValue = 1, | ||
30 | + }); | ||
31 | +} | ||
32 | + | ||
33 | +class OpacityAnimation extends GetAnimatedBuilder<double> { | ||
34 | + OpacityAnimation({ | ||
35 | + super.key, | ||
36 | + required super.duration, | ||
37 | + required super.delay, | ||
38 | + required super.child, | ||
39 | + required super.onComplete, | ||
40 | + required double begin, | ||
41 | + required double end, | ||
42 | + required super.idleValue, | ||
43 | + }) : super( | ||
44 | + tween: Tween<double>(begin: begin, end: end), | ||
45 | + builder: (context, value, child) { | ||
46 | + return Opacity( | ||
47 | + opacity: value, | ||
48 | + child: child!, | ||
49 | + ); | ||
50 | + }, | ||
51 | + ); | ||
52 | +} | ||
53 | + | ||
54 | +class RotateAnimation extends GetAnimatedBuilder<double> { | ||
55 | + RotateAnimation({ | ||
56 | + super.key, | ||
57 | + required super.duration, | ||
58 | + required super.delay, | ||
59 | + required super.child, | ||
60 | + super.onComplete, | ||
61 | + required double begin, | ||
62 | + required double end, | ||
63 | + super.idleValue = 0, | ||
64 | + }) : super( | ||
65 | + builder: (context, value, child) => Transform.rotate( | ||
66 | + angle: value, | ||
67 | + child: child, | ||
68 | + ), | ||
69 | + tween: Tween<double>(begin: begin, end: end), | ||
70 | + ); | ||
71 | +} | ||
72 | + | ||
73 | +class ScaleAnimation extends GetAnimatedBuilder<double> { | ||
74 | + ScaleAnimation({ | ||
75 | + super.key, | ||
76 | + required super.duration, | ||
77 | + required super.delay, | ||
78 | + required super.child, | ||
79 | + super.onComplete, | ||
80 | + required double begin, | ||
81 | + required double end, | ||
82 | + super.idleValue = 0, | ||
83 | + }) : super( | ||
84 | + builder: (context, value, child) => Transform.scale( | ||
85 | + scale: value, | ||
86 | + child: child, | ||
87 | + ), | ||
88 | + tween: Tween<double>(begin: begin, end: end), | ||
89 | + ); | ||
90 | +} | ||
91 | + | ||
92 | +class SlideAnimation extends GetAnimatedBuilder<Offset> { | ||
93 | + SlideAnimation({ | ||
94 | + super.key, | ||
95 | + required super.duration, | ||
96 | + required super.delay, | ||
97 | + required super.child, | ||
98 | + super.onComplete, | ||
99 | + required Offset begin, | ||
100 | + required Offset end, | ||
101 | + super.idleValue = const Offset(0, 0), | ||
102 | + }) : super( | ||
103 | + builder: (context, value, child) => Transform.translate( | ||
104 | + offset: value, | ||
105 | + child: child, | ||
106 | + ), | ||
107 | + tween: Tween(begin: begin, end: end), | ||
108 | + ); | ||
109 | +} | ||
110 | + | ||
111 | +class BounceAnimation extends GetAnimatedBuilder<double> { | ||
112 | + BounceAnimation({ | ||
113 | + super.key, | ||
114 | + required super.duration, | ||
115 | + required super.delay, | ||
116 | + required super.child, | ||
117 | + super.onComplete, | ||
118 | + super.curve = Curves.bounceOut, | ||
119 | + required double begin, | ||
120 | + required double end, | ||
121 | + super.idleValue = 0, | ||
122 | + }) : super( | ||
123 | + builder: (context, value, child) => Transform.scale( | ||
124 | + scale: 1 + value.abs(), | ||
125 | + child: child, | ||
126 | + ), | ||
127 | + tween: Tween<double>(begin: begin, end: end), | ||
128 | + ); | ||
129 | +} | ||
130 | + | ||
131 | +class ShakeAnimation extends GetAnimatedBuilder<double> { | ||
132 | + ShakeAnimation({ | ||
133 | + super.key, | ||
134 | + required super.duration, | ||
135 | + required super.delay, | ||
136 | + required super.child, | ||
137 | + super.onComplete, | ||
138 | + required double begin, | ||
139 | + required double end, | ||
140 | + super.idleValue = 0, | ||
141 | + }) : super( | ||
142 | + builder: (context, value, child) => Transform.rotate( | ||
143 | + angle: value * pi / 180.0, | ||
144 | + child: child, | ||
145 | + ), | ||
146 | + tween: Tween<double>(begin: begin, end: end), | ||
147 | + ); | ||
148 | +} | ||
149 | + | ||
150 | +class SpinAnimation extends GetAnimatedBuilder<double> { | ||
151 | + SpinAnimation({ | ||
152 | + super.key, | ||
153 | + required super.duration, | ||
154 | + required super.delay, | ||
155 | + required super.child, | ||
156 | + super.onComplete, | ||
157 | + super.idleValue = 0, | ||
158 | + }) : super( | ||
159 | + builder: (context, value, child) => Transform.rotate( | ||
160 | + angle: value * pi / 180.0, | ||
161 | + child: child, | ||
162 | + ), | ||
163 | + tween: Tween<double>(begin: 0, end: 360), | ||
164 | + ); | ||
165 | +} | ||
166 | + | ||
167 | +class ColorAnimation extends GetAnimatedBuilder<Color?> { | ||
168 | + ColorAnimation({ | ||
169 | + super.key, | ||
170 | + required super.duration, | ||
171 | + required super.delay, | ||
172 | + required super.child, | ||
173 | + super.onComplete, | ||
174 | + required Color begin, | ||
175 | + required Color end, | ||
176 | + Color? idleColor, | ||
177 | + }) : super( | ||
178 | + builder: (context, value, child) => ColorFiltered( | ||
179 | + colorFilter: ColorFilter.mode( | ||
180 | + Color.lerp(begin, end, value!.value.toDouble())!, | ||
181 | + BlendMode.srcIn, | ||
182 | + ), | ||
183 | + child: child, | ||
184 | + ), | ||
185 | + idleValue: idleColor ?? begin, | ||
186 | + tween: ColorTween(begin: begin, end: end), | ||
187 | + ); | ||
188 | +} | ||
189 | + | ||
190 | +class SizeAnimation extends GetAnimatedBuilder<double> { | ||
191 | + SizeAnimation({ | ||
192 | + super.key, | ||
193 | + required super.duration, | ||
194 | + required super.delay, | ||
195 | + required super.child, | ||
196 | + super.onComplete, | ||
197 | + super.idleValue = 0, | ||
198 | + required double begin, | ||
199 | + required double end, | ||
200 | + }) : super( | ||
201 | + builder: (context, value, child) => Transform.scale( | ||
202 | + scale: value, | ||
203 | + child: child, | ||
204 | + ), | ||
205 | + tween: Tween<double>(begin: begin, end: end), | ||
206 | + ); | ||
207 | +} |
lib/get_animations/extensions.dart
0 → 100644
1 | +import 'package:flutter/material.dart'; | ||
2 | + | ||
3 | +import 'animations.dart'; | ||
4 | +import 'get_animated_builder.dart'; | ||
5 | + | ||
6 | +const _defaultDuration = Duration(seconds: 2); | ||
7 | +const _defaultDelay = Duration.zero; | ||
8 | + | ||
9 | +extension AnimationExtension on Widget { | ||
10 | + GetAnimatedBuilder? get _currentAnimation => | ||
11 | + (this is GetAnimatedBuilder) ? this as GetAnimatedBuilder : null; | ||
12 | + | ||
13 | + GetAnimatedBuilder fadeIn({ | ||
14 | + Duration duration = _defaultDuration, | ||
15 | + Duration delay = _defaultDelay, | ||
16 | + ValueSetter<AnimationController>? onComplete, | ||
17 | + bool isSequential = false, | ||
18 | + }) { | ||
19 | + assert(isSequential || this is! FadeOutAnimation, | ||
20 | + 'Can not use fadeOut + fadeIn when isSequential is false'); | ||
21 | + | ||
22 | + return FadeInAnimation( | ||
23 | + duration: duration, | ||
24 | + delay: _getDelay(isSequential, delay), | ||
25 | + onComplete: onComplete, | ||
26 | + child: this, | ||
27 | + ); | ||
28 | + } | ||
29 | + | ||
30 | + GetAnimatedBuilder fadeOut({ | ||
31 | + Duration duration = _defaultDuration, | ||
32 | + Duration delay = _defaultDelay, | ||
33 | + ValueSetter<AnimationController>? onComplete, | ||
34 | + bool isSequential = false, | ||
35 | + }) { | ||
36 | + assert(isSequential || this is! FadeInAnimation, | ||
37 | + 'Can not use fadeOut() + fadeIn when isSequential is false'); | ||
38 | + | ||
39 | + return FadeOutAnimation( | ||
40 | + duration: duration, | ||
41 | + delay: _getDelay(isSequential, delay), | ||
42 | + onComplete: onComplete, | ||
43 | + child: this, | ||
44 | + ); | ||
45 | + } | ||
46 | + | ||
47 | + GetAnimatedBuilder rotate({ | ||
48 | + required double begin, | ||
49 | + required double end, | ||
50 | + Duration duration = _defaultDuration, | ||
51 | + Duration delay = _defaultDelay, | ||
52 | + ValueSetter<AnimationController>? onComplete, | ||
53 | + bool isSequential = false, | ||
54 | + }) { | ||
55 | + return RotateAnimation( | ||
56 | + duration: duration, | ||
57 | + delay: _getDelay(isSequential, delay), | ||
58 | + begin: begin, | ||
59 | + end: end, | ||
60 | + onComplete: onComplete, | ||
61 | + child: this, | ||
62 | + ); | ||
63 | + } | ||
64 | + | ||
65 | + GetAnimatedBuilder scale({ | ||
66 | + required double begin, | ||
67 | + required double end, | ||
68 | + Duration duration = _defaultDuration, | ||
69 | + Duration delay = _defaultDelay, | ||
70 | + ValueSetter<AnimationController>? onComplete, | ||
71 | + bool isSequential = false, | ||
72 | + }) { | ||
73 | + return ScaleAnimation( | ||
74 | + duration: duration, | ||
75 | + delay: _getDelay(isSequential, delay), | ||
76 | + begin: begin, | ||
77 | + end: end, | ||
78 | + onComplete: onComplete, | ||
79 | + child: this, | ||
80 | + ); | ||
81 | + } | ||
82 | + | ||
83 | + GetAnimatedBuilder slide({ | ||
84 | + required Offset begin, | ||
85 | + required Offset end, | ||
86 | + Duration duration = _defaultDuration, | ||
87 | + Duration delay = _defaultDelay, | ||
88 | + ValueSetter<AnimationController>? onComplete, | ||
89 | + bool isSequential = false, | ||
90 | + }) { | ||
91 | + return SlideAnimation( | ||
92 | + duration: duration, | ||
93 | + delay: _getDelay(isSequential, delay), | ||
94 | + begin: begin, | ||
95 | + end: end, | ||
96 | + onComplete: onComplete, | ||
97 | + child: this, | ||
98 | + ); | ||
99 | + } | ||
100 | + | ||
101 | + GetAnimatedBuilder bounce({ | ||
102 | + required double begin, | ||
103 | + required double end, | ||
104 | + Duration duration = _defaultDuration, | ||
105 | + Duration delay = _defaultDelay, | ||
106 | + ValueSetter<AnimationController>? onComplete, | ||
107 | + bool isSequential = false, | ||
108 | + }) { | ||
109 | + return BounceAnimation( | ||
110 | + duration: duration, | ||
111 | + delay: _getDelay(isSequential, delay), | ||
112 | + begin: begin, | ||
113 | + end: end, | ||
114 | + onComplete: onComplete, | ||
115 | + child: this, | ||
116 | + ); | ||
117 | + } | ||
118 | + | ||
119 | + GetAnimatedBuilder shake({ | ||
120 | + required double begin, | ||
121 | + required double end, | ||
122 | + Duration duration = _defaultDuration, | ||
123 | + Duration delay = _defaultDelay, | ||
124 | + ValueSetter<AnimationController>? onComplete, | ||
125 | + bool isSequential = false, | ||
126 | + }) { | ||
127 | + return ShakeAnimation( | ||
128 | + duration: duration, | ||
129 | + delay: _getDelay(isSequential, delay), | ||
130 | + begin: begin, | ||
131 | + end: end, | ||
132 | + onComplete: onComplete, | ||
133 | + child: this, | ||
134 | + ); | ||
135 | + } | ||
136 | + | ||
137 | + GetAnimatedBuilder spin({ | ||
138 | + Duration duration = _defaultDuration, | ||
139 | + Duration delay = _defaultDelay, | ||
140 | + ValueSetter<AnimationController>? onComplete, | ||
141 | + bool isSequential = false, | ||
142 | + }) { | ||
143 | + return SpinAnimation( | ||
144 | + duration: duration, | ||
145 | + delay: _getDelay(isSequential, delay), | ||
146 | + onComplete: onComplete, | ||
147 | + child: this, | ||
148 | + ); | ||
149 | + } | ||
150 | + | ||
151 | + GetAnimatedBuilder size({ | ||
152 | + required double begin, | ||
153 | + required double end, | ||
154 | + Duration duration = _defaultDuration, | ||
155 | + Duration delay = _defaultDelay, | ||
156 | + ValueSetter<AnimationController>? onComplete, | ||
157 | + bool isSequential = false, | ||
158 | + }) { | ||
159 | + return SizeAnimation( | ||
160 | + duration: duration, | ||
161 | + delay: _getDelay(isSequential, delay), | ||
162 | + begin: begin, | ||
163 | + end: end, | ||
164 | + onComplete: onComplete, | ||
165 | + child: this, | ||
166 | + ); | ||
167 | + } | ||
168 | + | ||
169 | + Duration _getDelay(bool isSequential, Duration delay) { | ||
170 | + assert(!(isSequential && delay != Duration.zero), | ||
171 | + "Error: When isSequential is true, delay must be non-zero. Context: isSequential: $isSequential delay: $delay"); | ||
172 | + | ||
173 | + return isSequential | ||
174 | + ? (_currentAnimation?.totalDuration ?? Duration.zero) | ||
175 | + : delay; | ||
176 | + } | ||
177 | +} |
lib/get_animations/get_animated_builder.dart
0 → 100644
1 | +import 'package:flutter/material.dart'; | ||
2 | + | ||
3 | +import 'animations.dart'; | ||
4 | + | ||
5 | +class GetAnimatedBuilder<T> extends StatefulWidget { | ||
6 | + final Duration duration; | ||
7 | + final Duration delay; | ||
8 | + final Widget child; | ||
9 | + final ValueSetter<AnimationController>? onComplete; | ||
10 | + final ValueSetter<AnimationController>? onStart; | ||
11 | + final Tween<T> tween; | ||
12 | + final T idleValue; | ||
13 | + final ValueWidgetBuilder<T> builder; | ||
14 | + final Curve curve; | ||
15 | + | ||
16 | + Duration get totalDuration => duration + delay; | ||
17 | + | ||
18 | + const GetAnimatedBuilder({ | ||
19 | + super.key, | ||
20 | + this.curve = Curves.linear, | ||
21 | + this.onComplete, | ||
22 | + this.onStart, | ||
23 | + required this.duration, | ||
24 | + required this.tween, | ||
25 | + required this.idleValue, | ||
26 | + required this.builder, | ||
27 | + required this.child, | ||
28 | + required this.delay, | ||
29 | + }); | ||
30 | + @override | ||
31 | + GetAnimatedBuilderState<T> createState() => GetAnimatedBuilderState<T>(); | ||
32 | +} | ||
33 | + | ||
34 | +class GetAnimatedBuilderState<T> extends State<GetAnimatedBuilder<T>> | ||
35 | + with SingleTickerProviderStateMixin { | ||
36 | + late final AnimationController _controller; | ||
37 | + late final Animation<T> _animation; | ||
38 | + | ||
39 | + // AnimationController get controller => _controller; | ||
40 | + // Animation<T> get animation => _animation; | ||
41 | + | ||
42 | + bool _wasStarted = false; | ||
43 | + // bool get wasStarted => _wasStarted; | ||
44 | + | ||
45 | + late T _idleValue; | ||
46 | + | ||
47 | + bool _willResetOnDispose = false; | ||
48 | + | ||
49 | + bool get willResetOnDispose => _willResetOnDispose; | ||
50 | + | ||
51 | + void _listener(AnimationStatus status) { | ||
52 | + switch (status) { | ||
53 | + case AnimationStatus.completed: | ||
54 | + widget.onComplete?.call(_controller); | ||
55 | + if (_willResetOnDispose) { | ||
56 | + _controller.reset(); | ||
57 | + } | ||
58 | + break; | ||
59 | + // case AnimationStatus.dismissed: | ||
60 | + case AnimationStatus.forward: | ||
61 | + widget.onStart?.call(_controller); | ||
62 | + break; | ||
63 | + // case AnimationStatus.reverse: | ||
64 | + default: | ||
65 | + break; | ||
66 | + } | ||
67 | + } | ||
68 | + | ||
69 | + @override | ||
70 | + void initState() { | ||
71 | + super.initState(); | ||
72 | + | ||
73 | + if (widget is OpacityAnimation) { | ||
74 | + final current = | ||
75 | + context.findRootAncestorStateOfType<GetAnimatedBuilderState>(); | ||
76 | + final isLast = current == null; | ||
77 | + | ||
78 | + if (widget is FadeInAnimation) { | ||
79 | + _idleValue = 1.0 as dynamic; | ||
80 | + } else { | ||
81 | + if (isLast) { | ||
82 | + _willResetOnDispose = false; | ||
83 | + } else { | ||
84 | + _willResetOnDispose = true; | ||
85 | + } | ||
86 | + _idleValue = widget.idleValue; | ||
87 | + } | ||
88 | + } else { | ||
89 | + _idleValue = widget.idleValue; | ||
90 | + } | ||
91 | + | ||
92 | + _controller = AnimationController( | ||
93 | + vsync: this, | ||
94 | + duration: widget.duration, | ||
95 | + ); | ||
96 | + | ||
97 | + _controller.addStatusListener(_listener); | ||
98 | + | ||
99 | + _animation = widget.tween.animate( | ||
100 | + CurvedAnimation( | ||
101 | + parent: _controller, | ||
102 | + curve: widget.curve, | ||
103 | + ), | ||
104 | + ); | ||
105 | + | ||
106 | + Future.delayed(widget.delay, () { | ||
107 | + if (mounted) { | ||
108 | + setState(() { | ||
109 | + _wasStarted = true; | ||
110 | + _controller.forward(); | ||
111 | + }); | ||
112 | + } | ||
113 | + }); | ||
114 | + } | ||
115 | + | ||
116 | + @override | ||
117 | + void dispose() { | ||
118 | + _controller.removeStatusListener(_listener); | ||
119 | + _controller.dispose(); | ||
120 | + super.dispose(); | ||
121 | + } | ||
122 | + | ||
123 | + @override | ||
124 | + Widget build(BuildContext context) { | ||
125 | + return AnimatedBuilder( | ||
126 | + animation: _animation, | ||
127 | + builder: (context, child) { | ||
128 | + final value = _wasStarted ? _animation.value : _idleValue; | ||
129 | + return widget.builder(context, value, child); | ||
130 | + }, | ||
131 | + child: widget.child, | ||
132 | + ); | ||
133 | + } | ||
134 | +} |
lib/get_animations/index.dart
0 → 100644
test/animations/extensions_test.dart
0 → 100644
1 | +import 'package:flutter/material.dart'; | ||
2 | +import 'package:flutter_test/flutter_test.dart'; | ||
3 | +import 'package:get/get.dart'; | ||
4 | + | ||
5 | +void main() { | ||
6 | + group('Animation Extension', () { | ||
7 | + Widget buildWidget() { | ||
8 | + return Container( | ||
9 | + width: 100, | ||
10 | + height: 100, | ||
11 | + color: Colors.red, | ||
12 | + ); | ||
13 | + } | ||
14 | + | ||
15 | + testWidgets('fadeIn() and fadeOut() can not be used sequentially', | ||
16 | + (WidgetTester tester) async { | ||
17 | + final widget = buildWidget(); | ||
18 | + | ||
19 | + expect(() => widget.fadeIn().fadeOut(), throwsAssertionError); | ||
20 | + expect(() => widget.fadeOut().fadeIn(), throwsAssertionError); | ||
21 | + | ||
22 | + expect(() => widget.fadeIn(isSequential: true).fadeOut(), | ||
23 | + throwsAssertionError); | ||
24 | + expect(() => widget.fadeOut(isSequential: true).fadeIn(), | ||
25 | + throwsAssertionError); | ||
26 | + }); | ||
27 | + | ||
28 | + testWidgets('can not use delay when isSequential is true', | ||
29 | + (WidgetTester tester) async { | ||
30 | + final widget = buildWidget(); | ||
31 | + | ||
32 | + expect( | ||
33 | + () => widget.fadeIn( | ||
34 | + isSequential: true, delay: const Duration(seconds: 1)), | ||
35 | + throwsAssertionError); | ||
36 | + }); | ||
37 | + | ||
38 | + testWidgets( | ||
39 | + 'fadeIn() and fadeOut() can be used together when isSequential is true', | ||
40 | + (WidgetTester tester) async { | ||
41 | + final widget = buildWidget(); | ||
42 | + | ||
43 | + expect( | ||
44 | + () => widget.fadeIn(isSequential: true).fadeOut(isSequential: true), | ||
45 | + isNot(throwsException)); | ||
46 | + | ||
47 | + expect(() => widget.fadeIn().fadeOut(isSequential: true), | ||
48 | + isNot(throwsException)); | ||
49 | + }); | ||
50 | + | ||
51 | + testWidgets('fadeIn() returns a FadeInAnimation', | ||
52 | + (WidgetTester tester) async { | ||
53 | + final widget = buildWidget(); | ||
54 | + const begin = 0.0; | ||
55 | + const end = 1.0; | ||
56 | + final animation = widget.fadeIn(); | ||
57 | + | ||
58 | + expect(animation, isA<FadeInAnimation>()); | ||
59 | + | ||
60 | + _testDefaultValues( | ||
61 | + animation: animation, widget: widget, begin: begin, end: end); | ||
62 | + }); | ||
63 | + | ||
64 | + testWidgets('fadeOut() returns a animation', (WidgetTester tester) async { | ||
65 | + final widget = buildWidget(); | ||
66 | + const begin = 1.0; | ||
67 | + const end = 0.0; | ||
68 | + final animation = widget.fadeOut(); | ||
69 | + | ||
70 | + expect(animation, isA<FadeOutAnimation>()); | ||
71 | + | ||
72 | + _testDefaultValues( | ||
73 | + animation: animation, widget: widget, begin: begin, end: end); | ||
74 | + }); | ||
75 | + | ||
76 | + testWidgets('rotate() returns a RotateAnimation', | ||
77 | + (WidgetTester tester) async { | ||
78 | + const begin = 0.9; | ||
79 | + const end = 1.1; | ||
80 | + final widget = buildWidget(); | ||
81 | + final animation = widget.rotate(begin: begin, end: end); | ||
82 | + | ||
83 | + expect(animation, isA<RotateAnimation>()); | ||
84 | + | ||
85 | + _testDefaultValues( | ||
86 | + animation: animation, widget: widget, begin: begin, end: end); | ||
87 | + }); | ||
88 | + | ||
89 | + testWidgets('scale() returns a ScaleAnimation', | ||
90 | + (WidgetTester tester) async { | ||
91 | + const begin = 0.9; | ||
92 | + const end = 1.1; | ||
93 | + final widget = buildWidget(); | ||
94 | + final animation = widget.scale(begin: begin, end: end); | ||
95 | + | ||
96 | + expect(animation, isA<ScaleAnimation>()); | ||
97 | + | ||
98 | + _testDefaultValues( | ||
99 | + animation: animation, widget: widget, begin: begin, end: end); | ||
100 | + }); | ||
101 | + | ||
102 | + testWidgets('slide() returns a SlideAnimation', | ||
103 | + (WidgetTester tester) async { | ||
104 | + const begin = Offset.zero; | ||
105 | + const end = Offset.zero; | ||
106 | + final widget = buildWidget(); | ||
107 | + final animation = widget.slide(begin: begin, end: end); | ||
108 | + | ||
109 | + expect(animation, isA<SlideAnimation>()); | ||
110 | + | ||
111 | + _testDefaultValues<Offset>( | ||
112 | + animation: animation, widget: widget, begin: begin, end: end); | ||
113 | + }); | ||
114 | + | ||
115 | + testWidgets('bounce() returns a BounceAnimation', | ||
116 | + (WidgetTester tester) async { | ||
117 | + const begin = 0.9; | ||
118 | + const end = 1.1; | ||
119 | + final widget = buildWidget(); | ||
120 | + final animation = widget.bounce(begin: begin, end: end); | ||
121 | + | ||
122 | + expect(animation, isA<BounceAnimation>()); | ||
123 | + | ||
124 | + _testDefaultValues( | ||
125 | + animation: animation, | ||
126 | + widget: widget, | ||
127 | + begin: begin, | ||
128 | + end: end, | ||
129 | + curve: Curves.bounceOut, | ||
130 | + ); | ||
131 | + }); | ||
132 | + | ||
133 | + testWidgets('shake() returns a ShakeAnimation', | ||
134 | + (WidgetTester tester) async { | ||
135 | + const begin = 0.9; | ||
136 | + const end = 1.1; | ||
137 | + final widget = buildWidget(); | ||
138 | + final animation = widget.shake(begin: begin, end: end); | ||
139 | + | ||
140 | + expect(animation, isA<ShakeAnimation>()); | ||
141 | + | ||
142 | + _testDefaultValues( | ||
143 | + animation: animation, widget: widget, begin: begin, end: end); | ||
144 | + }); | ||
145 | + | ||
146 | + testWidgets('spin() returns a SpinAnimation', (WidgetTester tester) async { | ||
147 | + final widget = buildWidget(); | ||
148 | + const begin = 0.0; | ||
149 | + const end = 360; | ||
150 | + final animation = widget.spin(); | ||
151 | + | ||
152 | + expect(animation, isA<SpinAnimation>()); | ||
153 | + | ||
154 | + _testDefaultValues( | ||
155 | + animation: animation, widget: widget, begin: begin, end: end); | ||
156 | + }); | ||
157 | + | ||
158 | + testWidgets('size() returns a SizeAnimation', (WidgetTester tester) async { | ||
159 | + final widget = buildWidget(); | ||
160 | + | ||
161 | + const begin = 0.9; | ||
162 | + const end = 1.1; | ||
163 | + final animation = widget.size(begin: begin, end: end); | ||
164 | + | ||
165 | + expect(animation, isA<SizeAnimation>()); | ||
166 | + | ||
167 | + _testDefaultValues( | ||
168 | + animation: animation, widget: widget, begin: begin, end: end); | ||
169 | + }); | ||
170 | + }); | ||
171 | +} | ||
172 | + | ||
173 | +void _testDefaultValues<T>({ | ||
174 | + required GetAnimatedBuilder animation, | ||
175 | + required Widget widget, | ||
176 | + required T begin, | ||
177 | + required T end, | ||
178 | + Curve curve = Curves.linear, | ||
179 | +}) { | ||
180 | + expect(animation.tween.begin, begin); | ||
181 | + expect(animation.tween.end, end); | ||
182 | + if (animation.idleValue is Offset) { | ||
183 | + expect(animation.idleValue, Offset.zero); | ||
184 | + } else if (animation is FadeOutAnimation) { | ||
185 | + expect(animation.idleValue, 1); | ||
186 | + } else { | ||
187 | + expect(animation.idleValue, 0); | ||
188 | + } | ||
189 | + | ||
190 | + expect(animation.delay, Duration.zero); | ||
191 | + expect(animation.child, widget); | ||
192 | + expect(animation.curve, curve); | ||
193 | +} |
1 | +import 'package:flutter/material.dart'; | ||
2 | +import 'package:flutter_test/flutter_test.dart'; | ||
3 | +import 'package:get/get.dart'; | ||
4 | + | ||
5 | +class _Wrapper extends StatelessWidget { | ||
6 | + const _Wrapper({required this.child}); | ||
7 | + final Widget child; | ||
8 | + | ||
9 | + @override | ||
10 | + Widget build(BuildContext context) { | ||
11 | + return MaterialApp( | ||
12 | + home: Scaffold(body: child), | ||
13 | + ); | ||
14 | + } | ||
15 | +} | ||
16 | + | ||
17 | +void main() { | ||
18 | + testWidgets('GetAnimatedBuilder defaults', (WidgetTester tester) async { | ||
19 | + await tester.pumpWidget( | ||
20 | + _Wrapper( | ||
21 | + child: GetAnimatedBuilder<int>( | ||
22 | + duration: const Duration(milliseconds: 500), | ||
23 | + tween: Tween(begin: 0, end: 10), | ||
24 | + idleValue: 0, | ||
25 | + builder: (_, value, __) => Text(value.toString()), | ||
26 | + delay: Duration.zero, | ||
27 | + child: Container(), | ||
28 | + ), | ||
29 | + ), | ||
30 | + ); | ||
31 | + | ||
32 | + // Verify that the widget starts with the idle value. | ||
33 | + expect(find.text('0'), findsOneWidget); | ||
34 | + | ||
35 | + // Wait for the animation to complete. | ||
36 | + await tester.pumpAndSettle(const Duration(milliseconds: 500)); | ||
37 | + | ||
38 | + // Verify that the widget ends with the final value. | ||
39 | + expect(find.text('10'), findsOneWidget); | ||
40 | + }); | ||
41 | + | ||
42 | + testWidgets('GetAnimatedBuilder changes value over time', (tester) async { | ||
43 | + await tester.pumpWidget( | ||
44 | + _Wrapper( | ||
45 | + child: GetAnimatedBuilder<double>( | ||
46 | + duration: const Duration(milliseconds: 500), | ||
47 | + tween: Tween<double>(begin: 0.0, end: 1.0), | ||
48 | + idleValue: 0.0, | ||
49 | + builder: (context, value, child) { | ||
50 | + return Opacity(opacity: value); | ||
51 | + }, | ||
52 | + delay: const Duration(milliseconds: 500), | ||
53 | + child: Container( | ||
54 | + width: 100, | ||
55 | + height: 100, | ||
56 | + color: Colors.red, | ||
57 | + ), | ||
58 | + ), | ||
59 | + ), | ||
60 | + ); | ||
61 | + | ||
62 | + // Initial state is idleValue | ||
63 | + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity, 0.0); | ||
64 | + | ||
65 | + // Wait for the delay to finish | ||
66 | + await tester.pump(const Duration(milliseconds: 500)); | ||
67 | + | ||
68 | + // Verify that the value changes over time | ||
69 | + await tester.pump(const Duration(milliseconds: 100)); | ||
70 | + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity, | ||
71 | + closeTo(0.2, 0.01)); | ||
72 | + | ||
73 | + await tester.pump(const Duration(milliseconds: 100)); | ||
74 | + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity, | ||
75 | + closeTo(0.4, 0.01)); | ||
76 | + | ||
77 | + await tester.pump(const Duration(milliseconds: 100)); | ||
78 | + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity, | ||
79 | + closeTo(0.6, 0.01)); | ||
80 | + | ||
81 | + await tester.pump(const Duration(milliseconds: 100)); | ||
82 | + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity, | ||
83 | + closeTo(0.8, 0.01)); | ||
84 | + | ||
85 | + await tester.pump(const Duration(milliseconds: 100)); | ||
86 | + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity, | ||
87 | + closeTo(1.0, 0.01)); | ||
88 | + }); | ||
89 | + | ||
90 | + testWidgets('onComplete callback is called when animation finishes', | ||
91 | + (WidgetTester tester) async { | ||
92 | + AnimationController? controller; | ||
93 | + var onCompleteCalled = false; | ||
94 | + | ||
95 | + await tester.pumpWidget( | ||
96 | + _Wrapper( | ||
97 | + child: GetAnimatedBuilder<int>( | ||
98 | + duration: const Duration(milliseconds: 500), | ||
99 | + tween: Tween(begin: 0, end: 10), | ||
100 | + idleValue: 0, | ||
101 | + builder: (_, value, __) => Text(value.toString()), | ||
102 | + delay: Duration.zero, | ||
103 | + onComplete: (c) { | ||
104 | + onCompleteCalled = true; | ||
105 | + controller = c; | ||
106 | + }, | ||
107 | + child: Container(), | ||
108 | + ), | ||
109 | + ), | ||
110 | + ); | ||
111 | + | ||
112 | + expect(onCompleteCalled, isFalse); | ||
113 | + | ||
114 | + // Wait for the animation to complete. | ||
115 | + await tester.pumpAndSettle(const Duration(milliseconds: 500)); | ||
116 | + | ||
117 | + // Verify that the onComplete callback was called. | ||
118 | + expect(controller, isNotNull); | ||
119 | + | ||
120 | + expect(onCompleteCalled, isTrue); | ||
121 | + }); | ||
122 | + | ||
123 | + testWidgets('onStart callback is called when animation starts', | ||
124 | + (WidgetTester tester) async { | ||
125 | + var onStartCalled = false; | ||
126 | + | ||
127 | + await tester.pumpWidget( | ||
128 | + _Wrapper( | ||
129 | + child: GetAnimatedBuilder( | ||
130 | + duration: const Duration(seconds: 1), | ||
131 | + delay: Duration.zero, | ||
132 | + tween: Tween<double>(begin: 0, end: 1), | ||
133 | + idleValue: 0, | ||
134 | + builder: (context, value, child) => Container(), | ||
135 | + child: Container(), | ||
136 | + onStart: (_) { | ||
137 | + onStartCalled = true; | ||
138 | + }, | ||
139 | + ), | ||
140 | + ), | ||
141 | + ); | ||
142 | + | ||
143 | + expect(onStartCalled, isFalse); | ||
144 | + | ||
145 | + await tester.pump(const Duration(milliseconds: 500)); | ||
146 | + expect(onStartCalled, isTrue); | ||
147 | + }); | ||
148 | + | ||
149 | + testWidgets('GetAnimatedBuilder delay', (WidgetTester tester) async { | ||
150 | + await tester.pumpWidget( | ||
151 | + _Wrapper( | ||
152 | + child: GetAnimatedBuilder<int>( | ||
153 | + duration: const Duration(milliseconds: 500), | ||
154 | + tween: Tween(begin: 0, end: 10), | ||
155 | + idleValue: 0, | ||
156 | + builder: (_, value, __) => Text(value.toString()), | ||
157 | + delay: const Duration(milliseconds: 500), | ||
158 | + child: Container(), | ||
159 | + ), | ||
160 | + ), | ||
161 | + ); | ||
162 | + | ||
163 | + // Verify that the widget starts with the idle value. | ||
164 | + expect(find.text('0'), findsOneWidget); | ||
165 | + | ||
166 | + // Wait for the delay to pass. | ||
167 | + await tester.pump(const Duration(milliseconds: 500)); | ||
168 | + | ||
169 | + // Verify that the animation has started. | ||
170 | + expect(find.text('0'), findsOneWidget); | ||
171 | + | ||
172 | + // Wait for the animation to complete. | ||
173 | + await tester.pumpAndSettle(const Duration(milliseconds: 500)); | ||
174 | + | ||
175 | + // Verify that the widget ends with the final value. | ||
176 | + expect(find.text('10'), findsOneWidget); | ||
177 | + }); | ||
178 | + | ||
179 | + testWidgets( | ||
180 | + 'FadeInAnimation in idle should be visible, but not visible when the animation starts', | ||
181 | + (WidgetTester tester) async { | ||
182 | + await tester.pumpWidget( | ||
183 | + _Wrapper( | ||
184 | + child: FadeInAnimation( | ||
185 | + delay: const Duration(milliseconds: 500), | ||
186 | + duration: const Duration(milliseconds: 500), | ||
187 | + idleValue: 0, | ||
188 | + child: const Text('Hello'), | ||
189 | + ), | ||
190 | + ), | ||
191 | + ); | ||
192 | + | ||
193 | + // in idle, the opacity should be 1.0 | ||
194 | + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity, 1.0); | ||
195 | + | ||
196 | + // Wait for the delay to finish | ||
197 | + await tester.pump(const Duration(milliseconds: 500)); | ||
198 | + | ||
199 | + // When the animation starts the opacity should not be visible | ||
200 | + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity, 0.0); | ||
201 | + | ||
202 | + // Verify that the value changes over time | ||
203 | + await tester.pump(const Duration(milliseconds: 100)); | ||
204 | + | ||
205 | + // The value should be updated | ||
206 | + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity, | ||
207 | + closeTo(0.2, 0.01)); | ||
208 | + | ||
209 | + await tester.pump(const Duration(milliseconds: 100)); | ||
210 | + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity, | ||
211 | + closeTo(0.4, 0.01)); | ||
212 | + | ||
213 | + await tester.pump(const Duration(milliseconds: 100)); | ||
214 | + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity, | ||
215 | + closeTo(0.6, 0.01)); | ||
216 | + | ||
217 | + await tester.pump(const Duration(milliseconds: 100)); | ||
218 | + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity, | ||
219 | + closeTo(0.8, 0.01)); | ||
220 | + | ||
221 | + await tester.pump(const Duration(milliseconds: 100)); | ||
222 | + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity, | ||
223 | + closeTo(1.0, 0.01)); | ||
224 | + }); | ||
225 | + | ||
226 | + testWidgets( | ||
227 | + 'willResetOnDispose should false when fadeOut is the last animation in a sequential animation', | ||
228 | + (WidgetTester tester) async { | ||
229 | + await tester.pumpWidget( | ||
230 | + _Wrapper( | ||
231 | + child: const Text('Hello') | ||
232 | + .fadeIn( | ||
233 | + isSequential: true, | ||
234 | + duration: const Duration(milliseconds: 500), | ||
235 | + ) | ||
236 | + .fadeOut( | ||
237 | + isSequential: true, | ||
238 | + duration: const Duration(milliseconds: 500), | ||
239 | + ), | ||
240 | + ), | ||
241 | + ); | ||
242 | + | ||
243 | + // The variable starts as false | ||
244 | + expect( | ||
245 | + tester | ||
246 | + .state<GetAnimatedBuilderState>(find.byType(FadeOutAnimation)) | ||
247 | + .willResetOnDispose, | ||
248 | + false); | ||
249 | + | ||
250 | + // Jump to middle of next animation | ||
251 | + await tester.pump(const Duration(milliseconds: 500)); | ||
252 | + | ||
253 | + // The value should be false | ||
254 | + expect( | ||
255 | + tester | ||
256 | + .state<GetAnimatedBuilderState>(find.byType(FadeOutAnimation)) | ||
257 | + .willResetOnDispose, | ||
258 | + false); | ||
259 | + | ||
260 | + await tester.pumpAndSettle(); | ||
261 | + }); | ||
262 | + | ||
263 | + testWidgets( | ||
264 | + 'willResetOnDispose should true when fadeOut is not last animation in a sequential animation', | ||
265 | + (WidgetTester tester) async { | ||
266 | + await tester.pumpWidget( | ||
267 | + _Wrapper( | ||
268 | + child: const Text('Hello') | ||
269 | + .fadeOut( | ||
270 | + isSequential: true, | ||
271 | + duration: const Duration(milliseconds: 500), | ||
272 | + ) | ||
273 | + .fadeIn( | ||
274 | + isSequential: true, | ||
275 | + duration: const Duration(milliseconds: 500), | ||
276 | + ), | ||
277 | + ), | ||
278 | + ); | ||
279 | + | ||
280 | + // The variable starts as true | ||
281 | + expect( | ||
282 | + tester | ||
283 | + .state<GetAnimatedBuilderState>(find.byType(FadeOutAnimation)) | ||
284 | + .willResetOnDispose, | ||
285 | + true); | ||
286 | + | ||
287 | + // Jump to middle of next animation | ||
288 | + await tester.pump(const Duration(milliseconds: 500)); | ||
289 | + | ||
290 | + // The value should be true | ||
291 | + expect( | ||
292 | + tester | ||
293 | + .state<GetAnimatedBuilderState>(find.byType(FadeOutAnimation)) | ||
294 | + .willResetOnDispose, | ||
295 | + true); | ||
296 | + | ||
297 | + await tester.pumpAndSettle(); | ||
298 | + }); | ||
299 | + | ||
300 | + testWidgets('RotateAnimation', (WidgetTester tester) async { | ||
301 | + await tester.pumpWidget( | ||
302 | + RotateAnimation( | ||
303 | + duration: const Duration(seconds: 1), | ||
304 | + delay: Duration.zero, | ||
305 | + begin: 0.0, | ||
306 | + end: 360.0, | ||
307 | + child: Container(), | ||
308 | + ), | ||
309 | + ); | ||
310 | + expect(find.byType(RotateAnimation), findsOneWidget); | ||
311 | + await tester.pumpAndSettle(); | ||
312 | + }); | ||
313 | + | ||
314 | + testWidgets('ScaleAnimation', (WidgetTester tester) async { | ||
315 | + await tester.pumpWidget( | ||
316 | + ScaleAnimation( | ||
317 | + duration: const Duration(seconds: 1), | ||
318 | + delay: Duration.zero, | ||
319 | + begin: 1.0, | ||
320 | + end: 2.0, | ||
321 | + child: Container(), | ||
322 | + ), | ||
323 | + ); | ||
324 | + expect(find.byType(ScaleAnimation), findsOneWidget); | ||
325 | + await tester.pumpAndSettle(); | ||
326 | + }); | ||
327 | + | ||
328 | + testWidgets('SlideAnimation', (WidgetTester tester) async { | ||
329 | + await tester.pumpWidget( | ||
330 | + SlideAnimation( | ||
331 | + duration: const Duration(seconds: 1), | ||
332 | + delay: Duration.zero, | ||
333 | + begin: Offset.zero, | ||
334 | + end: const Offset(1.0, 1.0), | ||
335 | + child: Container(), | ||
336 | + ), | ||
337 | + ); | ||
338 | + expect(find.byType(SlideAnimation), findsOneWidget); | ||
339 | + await tester.pumpAndSettle(); | ||
340 | + }); | ||
341 | + | ||
342 | + testWidgets('BounceAnimation', (WidgetTester tester) async { | ||
343 | + await tester.pumpWidget( | ||
344 | + BounceAnimation( | ||
345 | + duration: const Duration(seconds: 1), | ||
346 | + delay: Duration.zero, | ||
347 | + begin: 0.0, | ||
348 | + end: 1.0, | ||
349 | + child: Container(), | ||
350 | + ), | ||
351 | + ); | ||
352 | + expect(find.byType(BounceAnimation), findsOneWidget); | ||
353 | + await tester.pumpAndSettle(); | ||
354 | + }); | ||
355 | + | ||
356 | + testWidgets('ShakeAnimation', (WidgetTester tester) async { | ||
357 | + await tester.pumpWidget( | ||
358 | + ShakeAnimation( | ||
359 | + duration: const Duration(seconds: 1), | ||
360 | + delay: Duration.zero, | ||
361 | + begin: 0.0, | ||
362 | + end: 10.0, | ||
363 | + child: Container(), | ||
364 | + ), | ||
365 | + ); | ||
366 | + expect(find.byType(ShakeAnimation), findsOneWidget); | ||
367 | + await tester.pumpAndSettle(); | ||
368 | + }); | ||
369 | + | ||
370 | + testWidgets('SpinAnimation', (WidgetTester tester) async { | ||
371 | + await tester.pumpWidget( | ||
372 | + SpinAnimation( | ||
373 | + duration: const Duration(seconds: 1), | ||
374 | + delay: Duration.zero, | ||
375 | + child: Container(), | ||
376 | + ), | ||
377 | + ); | ||
378 | + expect(find.byType(SpinAnimation), findsOneWidget); | ||
379 | + await tester.pumpAndSettle(); | ||
380 | + }); | ||
381 | + | ||
382 | + testWidgets('ColorAnimation', (WidgetTester tester) async { | ||
383 | + await tester.pumpWidget( | ||
384 | + ColorAnimation( | ||
385 | + duration: const Duration(seconds: 1), | ||
386 | + delay: Duration.zero, | ||
387 | + begin: Colors.blue, | ||
388 | + end: Colors.red, | ||
389 | + child: Container(), | ||
390 | + ), | ||
391 | + ); | ||
392 | + expect(find.byType(ColorAnimation), findsOneWidget); | ||
393 | + await tester.pumpAndSettle(); | ||
394 | + }); | ||
395 | + | ||
396 | + testWidgets('SizeAnimation', (WidgetTester tester) async { | ||
397 | + await tester.pumpWidget( | ||
398 | + SizeAnimation( | ||
399 | + duration: const Duration(seconds: 1), | ||
400 | + delay: Duration.zero, | ||
401 | + begin: 1.0, | ||
402 | + end: 2.0, | ||
403 | + child: Container(), | ||
404 | + ), | ||
405 | + ); | ||
406 | + expect(find.byType(SizeAnimation), findsOneWidget); | ||
407 | + await tester.pumpAndSettle(); | ||
408 | + }); | ||
409 | +} |
-
Please register or login to post a comment