1 |
|
2 | import { AnimationBase, Properties, CubicBezierAnimationCurve } from './animation-common';
|
3 | import { Color } from '../../color';
|
4 | import { Trace } from '../../trace';
|
5 | import { opacityProperty, backgroundColorProperty, rotateProperty, rotateXProperty, rotateYProperty, translateXProperty, translateYProperty, scaleXProperty, scaleYProperty, heightProperty, widthProperty, PercentLength } from '../styling/style-properties';
|
6 | import { layout } from '../../utils';
|
7 | import { SDK_VERSION } from '../../utils/constants';
|
8 | import { Screen } from '../../platform';
|
9 | import lazy from '../../utils/lazy';
|
10 | export * from './animation-common';
|
11 | export { KeyframeAnimation, KeyframeAnimationInfo, KeyframeDeclaration, KeyframeInfo } from './keyframe-animation';
|
12 | let argbEvaluator;
|
13 | function ensureArgbEvaluator() {
|
14 | if (!argbEvaluator) {
|
15 | argbEvaluator = new android.animation.ArgbEvaluator();
|
16 | }
|
17 | }
|
18 | const easeIn = lazy(() => new android.view.animation.AccelerateInterpolator(1));
|
19 | const easeOut = lazy(() => new android.view.animation.DecelerateInterpolator(1));
|
20 | const easeInOut = lazy(() => new android.view.animation.AccelerateDecelerateInterpolator());
|
21 | const linear = lazy(() => new android.view.animation.LinearInterpolator());
|
22 | const bounce = lazy(() => new android.view.animation.BounceInterpolator());
|
23 | export function _resolveAnimationCurve(curve) {
|
24 | switch (curve) {
|
25 | case 'easeIn':
|
26 | if (Trace.isEnabled()) {
|
27 | Trace.write('Animation curve resolved to android.view.animation.AccelerateInterpolator(1).', Trace.categories.Animation);
|
28 | }
|
29 | return easeIn();
|
30 | case 'easeOut':
|
31 | if (Trace.isEnabled()) {
|
32 | Trace.write('Animation curve resolved to android.view.animation.DecelerateInterpolator(1).', Trace.categories.Animation);
|
33 | }
|
34 | return easeOut();
|
35 | case 'easeInOut':
|
36 | if (Trace.isEnabled()) {
|
37 | Trace.write('Animation curve resolved to android.view.animation.AccelerateDecelerateInterpolator().', Trace.categories.Animation);
|
38 | }
|
39 | return easeInOut();
|
40 | case 'linear':
|
41 | if (Trace.isEnabled()) {
|
42 | Trace.write('Animation curve resolved to android.view.animation.LinearInterpolator().', Trace.categories.Animation);
|
43 | }
|
44 | return linear();
|
45 | case 'spring':
|
46 | if (Trace.isEnabled()) {
|
47 | Trace.write('Animation curve resolved to android.view.animation.BounceInterpolator().', Trace.categories.Animation);
|
48 | }
|
49 | return bounce();
|
50 | case 'ease':
|
51 | return androidx.core.view.animation.PathInterpolatorCompat.create(0.25, 0.1, 0.25, 1.0);
|
52 | default:
|
53 | if (Trace.isEnabled()) {
|
54 | Trace.write('Animation curve resolved to original: ' + curve, Trace.categories.Animation);
|
55 | }
|
56 | if (curve instanceof CubicBezierAnimationCurve) {
|
57 | return androidx.core.view.animation.PathInterpolatorCompat.create(curve.x1, curve.y1, curve.x2, curve.y2);
|
58 | }
|
59 | else if (curve && curve.getInterpolation) {
|
60 | return curve;
|
61 | }
|
62 | else if (curve instanceof android.view.animation.LinearInterpolator) {
|
63 | return curve;
|
64 | }
|
65 | else {
|
66 | throw new Error(`Invalid animation curve: ${curve}`);
|
67 | }
|
68 | }
|
69 | }
|
70 | function getAndroidRepeatCount(iterations) {
|
71 | return iterations === Number.POSITIVE_INFINITY ? android.view.animation.Animation.INFINITE : iterations - 1;
|
72 | }
|
73 | function createObjectAnimator(nativeView, propertyName, value) {
|
74 | const arr = Array.create('float', 1);
|
75 | arr[0] = value;
|
76 | return android.animation.ObjectAnimator.ofFloat(nativeView, propertyName, arr);
|
77 | }
|
78 | function createAnimationSet(animators, iterations) {
|
79 | iterations = getAndroidRepeatCount(iterations);
|
80 | const animatorSet = new android.animation.AnimatorSet();
|
81 | const animatorsArray = Array.create(android.animation.Animator, animators.length);
|
82 | animators.forEach((animator, index) => {
|
83 | animatorsArray[index] = animator;
|
84 |
|
85 | animatorsArray[index].setRepeatCount(iterations);
|
86 | });
|
87 | animatorSet.playTogether(animatorsArray);
|
88 | animatorSet.setupStartValues();
|
89 | return animatorSet;
|
90 | }
|
91 | export class Animation extends AnimationBase {
|
92 | constructor(animationDefinitions, playSequentially) {
|
93 | super(animationDefinitions, playSequentially);
|
94 | this._resetOnFinish = true;
|
95 | this._valueSource = 'animation';
|
96 | if (animationDefinitions.length > 0 && animationDefinitions[0].valueSource !== undefined) {
|
97 | this._valueSource = animationDefinitions[0].valueSource;
|
98 | }
|
99 | const that = new WeakRef(this);
|
100 | this._animatorListener = new android.animation.Animator.AnimatorListener({
|
101 | onAnimationStart: function (animator) {
|
102 | if (Trace.isEnabled()) {
|
103 | Trace.write('MainAnimatorListener.onAndroidAnimationStart(' + animator + ')', Trace.categories.Animation);
|
104 | }
|
105 | },
|
106 | onAnimationRepeat: function (animator) {
|
107 | if (Trace.isEnabled()) {
|
108 | Trace.write('MainAnimatorListener.onAnimationRepeat(' + animator + ')', Trace.categories.Animation);
|
109 | }
|
110 | },
|
111 | onAnimationEnd: function (animator) {
|
112 | if (Trace.isEnabled()) {
|
113 | Trace.write('MainAnimatorListener.onAnimationEnd(' + animator + ')', Trace.categories.Animation);
|
114 | }
|
115 | const thisRef = that?.get();
|
116 | if (thisRef) {
|
117 | thisRef._onAndroidAnimationEnd();
|
118 | }
|
119 | },
|
120 | onAnimationCancel: function (animator) {
|
121 | if (Trace.isEnabled()) {
|
122 | Trace.write('MainAnimatorListener.onAnimationCancel(' + animator + ')', Trace.categories.Animation);
|
123 | }
|
124 | const thisRef = that?.get();
|
125 | if (thisRef) {
|
126 | thisRef._onAndroidAnimationCancel();
|
127 | }
|
128 | },
|
129 | });
|
130 | }
|
131 | play(resetOnFinish) {
|
132 | if (resetOnFinish !== undefined) {
|
133 | this._resetOnFinish = resetOnFinish;
|
134 | }
|
135 | if (this.isPlaying) {
|
136 | return this._rejectAlreadyPlaying();
|
137 | }
|
138 | const animationFinishedPromise = super.play();
|
139 | if (!this._animatorSet) {
|
140 | this._animators = new Array();
|
141 | this._propertyUpdateCallbacks = new Array();
|
142 | this._propertyResetCallbacks = new Array();
|
143 | for (let i = 0, length = this._propertyAnimations.length; i < length; i++) {
|
144 | this._createAnimators(this._propertyAnimations[i]);
|
145 | }
|
146 | this._nativeAnimatorsArray = Array.create(android.animation.Animator, this._animators.length);
|
147 | for (let i = 0, length = this._animators.length; i < length; i++) {
|
148 | this._nativeAnimatorsArray[i] = this._animators[i];
|
149 | }
|
150 | this._animatorSet = new android.animation.AnimatorSet();
|
151 | this._animatorSet.addListener(this._animatorListener);
|
152 | }
|
153 | this._play();
|
154 | return animationFinishedPromise;
|
155 | }
|
156 | cancel() {
|
157 | if (!this.isPlaying) {
|
158 | Trace.write('Animation is not currently playing.', Trace.categories.Animation, Trace.messageType.warn);
|
159 | return;
|
160 | }
|
161 | Trace.write('Cancelling AnimatorSet.', Trace.categories.Animation);
|
162 | this._animatorSet.cancel();
|
163 | }
|
164 | _resolveAnimationCurve(curve) {
|
165 | return _resolveAnimationCurve(curve);
|
166 | }
|
167 | _play() {
|
168 | if (SDK_VERSION <= 23) {
|
169 | this._animatorSet = new android.animation.AnimatorSet();
|
170 | this._animatorSet.addListener(this._animatorListener);
|
171 | }
|
172 | if (this._animators.length > 0) {
|
173 | if (this._playSequentially) {
|
174 | this._animatorSet.playSequentially(this._nativeAnimatorsArray);
|
175 | }
|
176 | else {
|
177 | this._animatorSet.playTogether(this._nativeAnimatorsArray);
|
178 | }
|
179 | }
|
180 | if (Trace.isEnabled()) {
|
181 | Trace.write('Starting ' + this._nativeAnimatorsArray.length + ' animations ' + (this._playSequentially ? 'sequentially.' : 'together.'), Trace.categories.Animation);
|
182 | }
|
183 | this._animatorSet.setupStartValues();
|
184 | this._animatorSet.start();
|
185 | }
|
186 | _onAndroidAnimationEnd() {
|
187 |
|
188 | if (!this.isPlaying) {
|
189 |
|
190 | return;
|
191 | }
|
192 | this._propertyUpdateCallbacks.forEach((v) => v());
|
193 | this._resolveAnimationFinishedPromise();
|
194 | if (this._resetOnFinish && this._target) {
|
195 | this._target._removeAnimation(this);
|
196 | }
|
197 | }
|
198 | _onAndroidAnimationCancel() {
|
199 |
|
200 | this._propertyResetCallbacks.forEach((v) => v());
|
201 | this._resolveAnimationFinishedPromise();
|
202 | if (this._target) {
|
203 | this._target._removeAnimation(this);
|
204 | }
|
205 | }
|
206 | _createAnimators(propertyAnimation) {
|
207 | if (!propertyAnimation.target.nativeViewProtected) {
|
208 | return;
|
209 | }
|
210 | if (Trace.isEnabled()) {
|
211 | Trace.write('Creating ObjectAnimator(s) for animation: ' + Animation._getAnimationInfo(propertyAnimation) + '...', Trace.categories.Animation);
|
212 | }
|
213 | if (propertyAnimation.target === null || propertyAnimation.target === undefined) {
|
214 | throw new Error(`Animation target cannot be null or undefined; property: ${propertyAnimation.property}; value: ${propertyAnimation.value};`);
|
215 | }
|
216 | if (propertyAnimation.property === null || propertyAnimation.property === undefined) {
|
217 | throw new Error(`Animation property cannot be null or undefined; target: ${propertyAnimation.target}; value: ${propertyAnimation.value};`);
|
218 | }
|
219 | if (propertyAnimation.value === null || propertyAnimation.value === undefined) {
|
220 | throw new Error(`Animation value cannot be null or undefined; target: ${propertyAnimation.target}; property: ${propertyAnimation.property};`);
|
221 | }
|
222 | this._target = propertyAnimation.target;
|
223 | const nativeView = propertyAnimation.target.nativeViewProtected;
|
224 | const animators = new Array();
|
225 | const propertyUpdateCallbacks = new Array();
|
226 | const propertyResetCallbacks = new Array();
|
227 | let originalValue1;
|
228 | let originalValue2;
|
229 | let originalValue3;
|
230 | const density = layout.getDisplayDensity();
|
231 | const setLocal = this._valueSource === 'animation';
|
232 | const style = propertyAnimation.target.style;
|
233 | switch (propertyAnimation.property) {
|
234 | case Properties.opacity:
|
235 | opacityProperty._initDefaultNativeValue(style);
|
236 | originalValue1 = nativeView.getAlpha();
|
237 | propertyUpdateCallbacks.push(() => {
|
238 | propertyAnimation.target.style[setLocal ? opacityProperty.name : opacityProperty.keyframe] = propertyAnimation.value;
|
239 | });
|
240 | propertyResetCallbacks.push(() => {
|
241 | if (setLocal) {
|
242 | propertyAnimation.target.style[opacityProperty.name] = originalValue1;
|
243 | }
|
244 | else {
|
245 | propertyAnimation.target.style[opacityProperty.keyframe] = originalValue1;
|
246 | }
|
247 | if (propertyAnimation.target.nativeViewProtected) {
|
248 | propertyAnimation.target[opacityProperty.setNative](propertyAnimation.target.style.opacity);
|
249 | }
|
250 | });
|
251 | animators.push(createObjectAnimator(nativeView, 'alpha', propertyAnimation.value));
|
252 | break;
|
253 | case Properties.backgroundColor: {
|
254 | backgroundColorProperty._initDefaultNativeValue(style);
|
255 | ensureArgbEvaluator();
|
256 | originalValue1 = propertyAnimation.target.backgroundColor;
|
257 | const nativeArray = Array.create(java.lang.Object, 2);
|
258 | nativeArray[0] = propertyAnimation.target.backgroundColor ? java.lang.Integer.valueOf(propertyAnimation.target.backgroundColor.argb) : java.lang.Integer.valueOf(-1);
|
259 | nativeArray[1] = java.lang.Integer.valueOf(propertyAnimation.value.argb);
|
260 | const animator = android.animation.ValueAnimator.ofObject(argbEvaluator, nativeArray);
|
261 | animator.addUpdateListener(new android.animation.ValueAnimator.AnimatorUpdateListener({
|
262 | onAnimationUpdate(animator) {
|
263 | const argb = animator.getAnimatedValue().intValue();
|
264 | propertyAnimation.target.style[setLocal ? backgroundColorProperty.name : backgroundColorProperty.keyframe] = new Color(argb);
|
265 | },
|
266 | }));
|
267 | propertyUpdateCallbacks.push(() => {
|
268 | propertyAnimation.target.style[setLocal ? backgroundColorProperty.name : backgroundColorProperty.keyframe] = propertyAnimation.value;
|
269 | });
|
270 | propertyResetCallbacks.push(() => {
|
271 | if (setLocal) {
|
272 | propertyAnimation.target.style[backgroundColorProperty.name] = originalValue1;
|
273 | }
|
274 | else {
|
275 | propertyAnimation.target.style[backgroundColorProperty.keyframe] = originalValue1;
|
276 | }
|
277 | if (propertyAnimation.target.nativeViewProtected && propertyAnimation.target[backgroundColorProperty.setNative]) {
|
278 | propertyAnimation.target[backgroundColorProperty.setNative](propertyAnimation.target.style.backgroundColor);
|
279 | }
|
280 | });
|
281 | animators.push(animator);
|
282 | break;
|
283 | }
|
284 | case Properties.translate:
|
285 | translateXProperty._initDefaultNativeValue(style);
|
286 | translateYProperty._initDefaultNativeValue(style);
|
287 | originalValue1 = nativeView.getTranslationX() / density;
|
288 | originalValue2 = nativeView.getTranslationY() / density;
|
289 | propertyUpdateCallbacks.push(() => {
|
290 | propertyAnimation.target.style[setLocal ? translateXProperty.name : translateXProperty.keyframe] = propertyAnimation.value.x;
|
291 | propertyAnimation.target.style[setLocal ? translateYProperty.name : translateYProperty.keyframe] = propertyAnimation.value.y;
|
292 | });
|
293 | propertyResetCallbacks.push(() => {
|
294 | if (setLocal) {
|
295 | propertyAnimation.target.style[translateXProperty.name] = originalValue1;
|
296 | propertyAnimation.target.style[translateYProperty.name] = originalValue2;
|
297 | }
|
298 | else {
|
299 | propertyAnimation.target.style[translateXProperty.keyframe] = originalValue1;
|
300 | propertyAnimation.target.style[translateYProperty.keyframe] = originalValue2;
|
301 | }
|
302 | if (propertyAnimation.target.nativeViewProtected) {
|
303 | propertyAnimation.target[translateXProperty.setNative](propertyAnimation.target.style.translateX);
|
304 | propertyAnimation.target[translateYProperty.setNative](propertyAnimation.target.style.translateY);
|
305 | }
|
306 | });
|
307 | animators.push(createAnimationSet([createObjectAnimator(nativeView, 'translationX', propertyAnimation.value.x * density), createObjectAnimator(nativeView, 'translationY', propertyAnimation.value.y * density)], propertyAnimation.iterations));
|
308 | break;
|
309 | case Properties.scale:
|
310 | scaleXProperty._initDefaultNativeValue(style);
|
311 | scaleYProperty._initDefaultNativeValue(style);
|
312 | originalValue1 = nativeView.getScaleX();
|
313 | originalValue2 = nativeView.getScaleY();
|
314 | propertyUpdateCallbacks.push(() => {
|
315 | propertyAnimation.target.style[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = propertyAnimation.value.x;
|
316 | propertyAnimation.target.style[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = propertyAnimation.value.y;
|
317 | });
|
318 | propertyResetCallbacks.push(() => {
|
319 | if (setLocal) {
|
320 | propertyAnimation.target.style[scaleXProperty.name] = originalValue1;
|
321 | propertyAnimation.target.style[scaleYProperty.name] = originalValue2;
|
322 | }
|
323 | else {
|
324 | propertyAnimation.target.style[scaleXProperty.keyframe] = originalValue1;
|
325 | propertyAnimation.target.style[scaleYProperty.keyframe] = originalValue2;
|
326 | }
|
327 | if (propertyAnimation.target.nativeViewProtected) {
|
328 | propertyAnimation.target[scaleXProperty.setNative](propertyAnimation.target.style.scaleX);
|
329 | propertyAnimation.target[scaleYProperty.setNative](propertyAnimation.target.style.scaleY);
|
330 | }
|
331 | });
|
332 | animators.push(createAnimationSet([createObjectAnimator(nativeView, 'scaleX', propertyAnimation.value.x), createObjectAnimator(nativeView, 'scaleY', propertyAnimation.value.y)], propertyAnimation.iterations));
|
333 | break;
|
334 | case Properties.rotate:
|
335 | rotateProperty._initDefaultNativeValue(style);
|
336 | rotateXProperty._initDefaultNativeValue(style);
|
337 | rotateYProperty._initDefaultNativeValue(style);
|
338 | originalValue1 = nativeView.getRotationX();
|
339 | originalValue2 = nativeView.getRotationY();
|
340 | originalValue3 = nativeView.getRotation();
|
341 | propertyUpdateCallbacks.push(() => {
|
342 | propertyAnimation.target.style[setLocal ? rotateXProperty.name : rotateXProperty.keyframe] = propertyAnimation.value.x;
|
343 | propertyAnimation.target.style[setLocal ? rotateYProperty.name : rotateYProperty.keyframe] = propertyAnimation.value.y;
|
344 | propertyAnimation.target.style[setLocal ? rotateProperty.name : rotateProperty.keyframe] = propertyAnimation.value.z;
|
345 | });
|
346 | propertyResetCallbacks.push(() => {
|
347 | if (setLocal) {
|
348 | propertyAnimation.target.style[rotateXProperty.name] = originalValue1;
|
349 | propertyAnimation.target.style[rotateYProperty.name] = originalValue2;
|
350 | propertyAnimation.target.style[rotateProperty.name] = originalValue3;
|
351 | }
|
352 | else {
|
353 | propertyAnimation.target.style[rotateXProperty.keyframe] = originalValue1;
|
354 | propertyAnimation.target.style[rotateYProperty.keyframe] = originalValue2;
|
355 | propertyAnimation.target.style[rotateProperty.keyframe] = originalValue3;
|
356 | }
|
357 | if (propertyAnimation.target.nativeViewProtected) {
|
358 | propertyAnimation.target[rotateProperty.setNative](propertyAnimation.target.style.rotate);
|
359 | propertyAnimation.target[rotateXProperty.setNative](propertyAnimation.target.style.rotateX);
|
360 | propertyAnimation.target[rotateYProperty.setNative](propertyAnimation.target.style.rotateY);
|
361 | }
|
362 | });
|
363 | animators.push(createAnimationSet([createObjectAnimator(nativeView, 'rotationX', propertyAnimation.value.x), createObjectAnimator(nativeView, 'rotationY', propertyAnimation.value.y), createObjectAnimator(nativeView, 'rotation', propertyAnimation.value.z)], propertyAnimation.iterations));
|
364 | break;
|
365 | case Properties.width:
|
366 | case Properties.height: {
|
367 | const isVertical = propertyAnimation.property === 'height';
|
368 | const extentProperty = isVertical ? heightProperty : widthProperty;
|
369 | extentProperty._initDefaultNativeValue(style);
|
370 | const nativeArray = Array.create('float', 2);
|
371 | let toValue = propertyAnimation.value;
|
372 | const parent = propertyAnimation.target.parent;
|
373 | if (!parent) {
|
374 | throw new Error(`cannot animate ${propertyAnimation.property} on root view`);
|
375 | }
|
376 | const parentExtent = isVertical ? parent.getMeasuredHeight() : parent.getMeasuredWidth();
|
377 | toValue = PercentLength.toDevicePixels(toValue, parentExtent, parentExtent) / Screen.mainScreen.scale;
|
378 | const nativeHeight = isVertical ? nativeView.getHeight() : nativeView.getWidth();
|
379 | const targetStyle = setLocal ? extentProperty.name : extentProperty.keyframe;
|
380 | originalValue1 = nativeHeight / Screen.mainScreen.scale;
|
381 | nativeArray[0] = originalValue1;
|
382 | nativeArray[1] = toValue;
|
383 | const extentAnimator = android.animation.ValueAnimator.ofFloat(nativeArray);
|
384 | extentAnimator.addUpdateListener(new android.animation.ValueAnimator.AnimatorUpdateListener({
|
385 | onAnimationUpdate(animator) {
|
386 | const argb = animator.getAnimatedValue().floatValue();
|
387 | propertyAnimation.target.style[setLocal ? extentProperty.name : extentProperty.keyframe] = argb;
|
388 | },
|
389 | }));
|
390 | propertyUpdateCallbacks.push(() => {
|
391 | propertyAnimation.target.style[targetStyle] = propertyAnimation.value;
|
392 | });
|
393 | propertyResetCallbacks.push(() => {
|
394 | propertyAnimation.target.style[targetStyle] = originalValue1;
|
395 | if (propertyAnimation.target.nativeViewProtected) {
|
396 | const setter = propertyAnimation.target[extentProperty.setNative];
|
397 | setter(propertyAnimation.target.style[propertyAnimation.property]);
|
398 | }
|
399 | });
|
400 | animators.push(extentAnimator);
|
401 | break;
|
402 | }
|
403 | default:
|
404 | throw new Error(`Animating property '${propertyAnimation.property}' is unsupported`);
|
405 | }
|
406 | for (let i = 0, length = animators.length; i < length; i++) {
|
407 |
|
408 | if (propertyAnimation.duration !== undefined) {
|
409 | animators[i].setDuration(propertyAnimation.duration);
|
410 | }
|
411 |
|
412 | if (propertyAnimation.delay !== undefined) {
|
413 | animators[i].setStartDelay(propertyAnimation.delay);
|
414 | }
|
415 |
|
416 | if (propertyAnimation.iterations !== undefined && animators[i] instanceof android.animation.ValueAnimator) {
|
417 | animators[i].setRepeatCount(getAndroidRepeatCount(propertyAnimation.iterations));
|
418 | }
|
419 |
|
420 | if (propertyAnimation.curve !== undefined) {
|
421 | animators[i].setInterpolator(propertyAnimation.curve);
|
422 | }
|
423 | if (Trace.isEnabled()) {
|
424 | Trace.write('Animator created: ' + animators[i], Trace.categories.Animation);
|
425 | }
|
426 | }
|
427 | this._animators = this._animators.concat(animators);
|
428 | this._propertyUpdateCallbacks = this._propertyUpdateCallbacks.concat(propertyUpdateCallbacks);
|
429 | this._propertyResetCallbacks = this._propertyResetCallbacks.concat(propertyResetCallbacks);
|
430 | }
|
431 | }
|
432 |
|
\ | No newline at end of file |