1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | import 'package:flutter/foundation.dart'; |
6 | |
7 | import 'animation.dart'; |
8 | |
9 | export 'dart:ui' show VoidCallback; |
10 | |
11 | export 'animation.dart' show AnimationStatus, AnimationStatusListener; |
12 | |
13 | /// A mixin that helps listen to another object only when this object has registered listeners. |
14 | /// |
15 | /// This mixin provides implementations of [didRegisterListener] and [didUnregisterListener], |
16 | /// and therefore can be used in conjunction with mixins that require these methods, |
17 | /// [AnimationLocalListenersMixin] and [AnimationLocalStatusListenersMixin]. |
18 | mixin AnimationLazyListenerMixin { |
19 | int _listenerCounter = 0; |
20 | |
21 | /// Calls [didStartListening] every time a registration of a listener causes |
22 | /// an empty list of listeners to become non-empty. |
23 | /// |
24 | /// See also: |
25 | /// |
26 | /// * [didUnregisterListener], which may cause the listener list to |
27 | /// become empty again, and in turn cause this method to call |
28 | /// [didStartListening] again. |
29 | @protected |
30 | void didRegisterListener() { |
31 | assert(_listenerCounter >= 0); |
32 | if (_listenerCounter == 0) { |
33 | didStartListening(); |
34 | } |
35 | _listenerCounter += 1; |
36 | } |
37 | |
38 | /// Calls [didStopListening] when an only remaining listener is unregistered, |
39 | /// thus making the list empty. |
40 | /// |
41 | /// See also: |
42 | /// |
43 | /// * [didRegisterListener], which causes the listener list to become non-empty. |
44 | @protected |
45 | void didUnregisterListener() { |
46 | assert(_listenerCounter >= 1); |
47 | _listenerCounter -= 1; |
48 | if (_listenerCounter == 0) { |
49 | didStopListening(); |
50 | } |
51 | } |
52 | |
53 | /// Called when the number of listeners changes from zero to one. |
54 | @protected |
55 | void didStartListening(); |
56 | |
57 | /// Called when the number of listeners changes from one to zero. |
58 | @protected |
59 | void didStopListening(); |
60 | |
61 | /// Whether there are any listeners. |
62 | bool get isListening => _listenerCounter > 0; |
63 | } |
64 | |
65 | /// A mixin that replaces the [didRegisterListener]/[didUnregisterListener] contract |
66 | /// with a dispose contract. |
67 | /// |
68 | /// This mixin provides implementations of [didRegisterListener] and [didUnregisterListener], |
69 | /// and therefore can be used in conjunction with mixins that require these methods, |
70 | /// [AnimationLocalListenersMixin] and [AnimationLocalStatusListenersMixin]. |
71 | mixin AnimationEagerListenerMixin { |
72 | /// This implementation ignores listener registrations. |
73 | @protected |
74 | void didRegisterListener() {} |
75 | |
76 | /// This implementation ignores listener registrations. |
77 | @protected |
78 | void didUnregisterListener() {} |
79 | |
80 | /// Release the resources used by this object. The object is no longer usable |
81 | /// after this method is called. |
82 | @mustCallSuper |
83 | void dispose() {} |
84 | } |
85 | |
86 | /// A mixin that implements the [addListener]/[removeListener] protocol and notifies |
87 | /// all the registered listeners when [notifyListeners] is called. |
88 | /// |
89 | /// This mixin requires that the mixing class provide methods [didRegisterListener] |
90 | /// and [didUnregisterListener]. Implementations of these methods can be obtained |
91 | /// by mixing in another mixin from this library, such as [AnimationLazyListenerMixin]. |
92 | mixin AnimationLocalListenersMixin { |
93 | final HashedObserverList<VoidCallback> _listeners = HashedObserverList<VoidCallback>(); |
94 | |
95 | /// Called immediately before a listener is added via [addListener]. |
96 | /// |
97 | /// At the time this method is called the registered listener is not yet |
98 | /// notified by [notifyListeners]. |
99 | @protected |
100 | void didRegisterListener(); |
101 | |
102 | /// Called immediately after a listener is removed via [removeListener]. |
103 | /// |
104 | /// At the time this method is called the removed listener is no longer |
105 | /// notified by [notifyListeners]. |
106 | @protected |
107 | void didUnregisterListener(); |
108 | |
109 | /// Calls the listener every time the value of the animation changes. |
110 | /// |
111 | /// Listeners can be removed with [removeListener]. |
112 | void addListener(VoidCallback listener) { |
113 | didRegisterListener(); |
114 | _listeners.add(listener); |
115 | } |
116 | |
117 | /// Stop calling the listener every time the value of the animation changes. |
118 | /// |
119 | /// Listeners can be added with [addListener]. |
120 | void removeListener(VoidCallback listener) { |
121 | final bool removed = _listeners.remove(listener); |
122 | if (removed) { |
123 | didUnregisterListener(); |
124 | } |
125 | } |
126 | |
127 | /// Removes all listeners added with [addListener]. |
128 | /// |
129 | /// This method is typically called from the `dispose` method of the class |
130 | /// using this mixin if the class also uses the [AnimationEagerListenerMixin]. |
131 | /// |
132 | /// Calling this method will not trigger [didUnregisterListener]. |
133 | @protected |
134 | void clearListeners() { |
135 | _listeners.clear(); |
136 | } |
137 | |
138 | /// Calls all the listeners. |
139 | /// |
140 | /// If listeners are added or removed during this function, the modifications |
141 | /// will not change which listeners are called during this iteration. |
142 | @protected |
143 | @pragma('vm:notify-debugger-on-exception' ) |
144 | void notifyListeners() { |
145 | final List<VoidCallback> localListeners = _listeners.toList(growable: false); |
146 | for (final VoidCallback listener in localListeners) { |
147 | InformationCollector? collector; |
148 | assert(() { |
149 | collector = |
150 | () => <DiagnosticsNode>[ |
151 | DiagnosticsProperty<AnimationLocalListenersMixin>( |
152 | 'The $runtimeType notifying listeners was' , |
153 | this, |
154 | style: DiagnosticsTreeStyle.errorProperty, |
155 | ), |
156 | ]; |
157 | return true; |
158 | }()); |
159 | try { |
160 | if (_listeners.contains(listener)) { |
161 | listener(); |
162 | } |
163 | } catch (exception, stack) { |
164 | FlutterError.reportError( |
165 | FlutterErrorDetails( |
166 | exception: exception, |
167 | stack: stack, |
168 | library: 'animation library' , |
169 | context: ErrorDescription('while notifying listeners for $runtimeType' ), |
170 | informationCollector: collector, |
171 | ), |
172 | ); |
173 | } |
174 | } |
175 | } |
176 | } |
177 | |
178 | /// A mixin that implements the addStatusListener/removeStatusListener protocol |
179 | /// and notifies all the registered listeners when notifyStatusListeners is |
180 | /// called. |
181 | /// |
182 | /// This mixin requires that the mixing class provide methods [didRegisterListener] |
183 | /// and [didUnregisterListener]. Implementations of these methods can be obtained |
184 | /// by mixing in another mixin from this library, such as [AnimationLazyListenerMixin]. |
185 | mixin AnimationLocalStatusListenersMixin { |
186 | final ObserverList<AnimationStatusListener> _statusListeners = |
187 | ObserverList<AnimationStatusListener>(); |
188 | |
189 | /// Called immediately before a status listener is added via [addStatusListener]. |
190 | /// |
191 | /// At the time this method is called the registered listener is not yet |
192 | /// notified by [notifyStatusListeners]. |
193 | @protected |
194 | void didRegisterListener(); |
195 | |
196 | /// Called immediately after a status listener is removed via [removeStatusListener]. |
197 | /// |
198 | /// At the time this method is called the removed listener is no longer |
199 | /// notified by [notifyStatusListeners]. |
200 | @protected |
201 | void didUnregisterListener(); |
202 | |
203 | /// Calls listener every time the status of the animation changes. |
204 | /// |
205 | /// Listeners can be removed with [removeStatusListener]. |
206 | void addStatusListener(AnimationStatusListener listener) { |
207 | didRegisterListener(); |
208 | _statusListeners.add(listener); |
209 | } |
210 | |
211 | /// Stops calling the listener every time the status of the animation changes. |
212 | /// |
213 | /// Listeners can be added with [addStatusListener]. |
214 | void removeStatusListener(AnimationStatusListener listener) { |
215 | final bool removed = _statusListeners.remove(listener); |
216 | if (removed) { |
217 | didUnregisterListener(); |
218 | } |
219 | } |
220 | |
221 | /// Removes all listeners added with [addStatusListener]. |
222 | /// |
223 | /// This method is typically called from the `dispose` method of the class |
224 | /// using this mixin if the class also uses the [AnimationEagerListenerMixin]. |
225 | /// |
226 | /// Calling this method will not trigger [didUnregisterListener]. |
227 | @protected |
228 | void clearStatusListeners() { |
229 | _statusListeners.clear(); |
230 | } |
231 | |
232 | /// Calls all the status listeners. |
233 | /// |
234 | /// If listeners are added or removed during this function, the modifications |
235 | /// will not change which listeners are called during this iteration. |
236 | @protected |
237 | @pragma('vm:notify-debugger-on-exception' ) |
238 | void notifyStatusListeners(AnimationStatus status) { |
239 | final List<AnimationStatusListener> localListeners = _statusListeners.toList(growable: false); |
240 | for (final AnimationStatusListener listener in localListeners) { |
241 | try { |
242 | if (_statusListeners.contains(listener)) { |
243 | listener(status); |
244 | } |
245 | } catch (exception, stack) { |
246 | InformationCollector? collector; |
247 | assert(() { |
248 | collector = |
249 | () => <DiagnosticsNode>[ |
250 | DiagnosticsProperty<AnimationLocalStatusListenersMixin>( |
251 | 'The $runtimeType notifying status listeners was' , |
252 | this, |
253 | style: DiagnosticsTreeStyle.errorProperty, |
254 | ), |
255 | ]; |
256 | return true; |
257 | }()); |
258 | FlutterError.reportError( |
259 | FlutterErrorDetails( |
260 | exception: exception, |
261 | stack: stack, |
262 | library: 'animation library' , |
263 | context: ErrorDescription('while notifying status listeners for $runtimeType' ), |
264 | informationCollector: collector, |
265 | ), |
266 | ); |
267 | } |
268 | } |
269 | } |
270 | } |
271 | |