Skip to content

Commit bc95f56

Browse files
fix(core): animation iteration correct for android css animations and iOS rotation (#9628)
closes #7712 Co-authored-by: Nathan Walker <walkerrunpdx@gmail.com>
1 parent c95c79a commit bc95f56

5 files changed

Lines changed: 128 additions & 171 deletions

File tree

‎apps/automated/src/ui/animation/css-animation-tests.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export function test_ReadIterations() {
7373
let animation = createAnimationFromCSS('.test { animation-iteration-count: 5; }', 'test');
7474
TKUnit.assertEqual(animation.iterations, 5);
7575
animation = createAnimationFromCSS('.test { animation-iteration-count: infinite; }', 'test');
76-
TKUnit.assertEqual(animation.iterations, Number.MAX_VALUE);
76+
TKUnit.assertEqual(animation.iterations, Number.POSITIVE_INFINITY);
7777
}
7878

7979
export function test_ReadFillMode() {

‎packages/core/ui/animation/index.android.ts‎

Lines changed: 115 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,6 @@ const easeInOut = lazy(() => new android.view.animation.AccelerateDecelerateInte
2727
const linear = lazy(() => new android.view.animation.LinearInterpolator());
2828
const bounce = lazy(() => new android.view.animation.BounceInterpolator());
2929

30-
const keyPrefix = 'ui.animation.';
31-
const propertyKeys = {};
32-
propertyKeys[Properties.backgroundColor] = Symbol(keyPrefix + Properties.backgroundColor);
33-
propertyKeys[Properties.opacity] = Symbol(keyPrefix + Properties.opacity);
34-
propertyKeys[Properties.rotate] = Symbol(keyPrefix + Properties.rotate);
35-
propertyKeys[Properties.scale] = Symbol(keyPrefix + Properties.scale);
36-
propertyKeys[Properties.translate] = Symbol(keyPrefix + Properties.translate);
37-
propertyKeys[Properties.height] = Symbol(keyPrefix + Properties.height);
38-
propertyKeys[Properties.width] = Symbol(keyPrefix + Properties.width);
39-
4030
export function _resolveAnimationCurve(curve: string | CubicBezierAnimationCurve | android.view.animation.Interpolator | android.view.animation.LinearInterpolator): android.view.animation.Interpolator {
4131
switch (curve) {
4232
case 'easeIn':
@@ -178,27 +168,27 @@ export class Animation extends AnimationBase {
178168
return this._rejectAlreadyPlaying();
179169
}
180170

181-
if (this._animatorSet) {
182-
return this._play();
183-
}
184-
185-
this._animators = new Array<android.animation.Animator>();
186-
this._propertyUpdateCallbacks = new Array<Function>();
187-
this._propertyResetCallbacks = new Array<Function>();
171+
const animationFinishedPromise = super.play();
188172

189-
for (let i = 0, length = this._propertyAnimations.length; i < length; i++) {
190-
this._createAnimators(this._propertyAnimations[i]);
191-
}
173+
if (!this._animatorSet) {
174+
this._animators = new Array<android.animation.Animator>();
175+
this._propertyUpdateCallbacks = new Array<Function>();
176+
this._propertyResetCallbacks = new Array<Function>();
192177

193-
this._nativeAnimatorsArray = Array.create(android.animation.Animator, this._animators.length);
194-
for (let i = 0, length = this._animators.length; i < length; i++) {
195-
this._nativeAnimatorsArray[i] = this._animators[i];
196-
}
178+
for (let i = 0, length = this._propertyAnimations.length; i < length; i++) {
179+
this._createAnimators(this._propertyAnimations[i]);
180+
}
197181

198-
this._animatorSet = new android.animation.AnimatorSet();
199-
this._animatorSet.addListener(this._animatorListener);
182+
this._nativeAnimatorsArray = Array.create(android.animation.Animator, this._animators.length);
183+
for (let i = 0, length = this._animators.length; i < length; i++) {
184+
this._nativeAnimatorsArray[i] = this._animators[i];
185+
}
200186

201-
return this._play();
187+
this._animatorSet = new android.animation.AnimatorSet();
188+
this._animatorSet.addListener(this._animatorListener);
189+
}
190+
this._play();
191+
return animationFinishedPromise;
202192
}
203193

204194
public cancel(): void {
@@ -217,9 +207,7 @@ export class Animation extends AnimationBase {
217207
return _resolveAnimationCurve(curve);
218208
}
219209

220-
private _play(): AnimationPromise {
221-
const animationFinishedPromise = super.play();
222-
210+
private _play(): void {
223211
if (Device.sdkVersion <= '23') {
224212
this._animatorSet = new android.animation.AnimatorSet();
225213
this._animatorSet.addListener(this._animatorListener);
@@ -239,8 +227,6 @@ export class Animation extends AnimationBase {
239227

240228
this._animatorSet.setupStartValues();
241229
this._animatorSet.start();
242-
243-
return animationFinishedPromise;
244230
}
245231

246232
private _onAndroidAnimationEnd() {
@@ -300,43 +286,26 @@ export class Animation extends AnimationBase {
300286
let originalValue3;
301287
const density = layout.getDisplayDensity();
302288

303-
const key = propertyKeys[propertyAnimation.property];
304-
if (key) {
305-
propertyAnimation.target[key] = propertyAnimation;
306-
}
307-
function checkAnimation(cb) {
308-
return () => {
309-
if (propertyAnimation.target[key] === propertyAnimation) {
310-
delete propertyAnimation.target[key];
311-
cb();
312-
}
313-
};
314-
}
315-
316289
const setLocal = this._valueSource === 'animation';
317290
const style = propertyAnimation.target.style;
318291
switch (propertyAnimation.property) {
319292
case Properties.opacity:
320293
opacityProperty._initDefaultNativeValue(style);
321294

322295
originalValue1 = nativeView.getAlpha();
323-
propertyUpdateCallbacks.push(
324-
checkAnimation(() => {
325-
propertyAnimation.target.style[setLocal ? opacityProperty.name : opacityProperty.keyframe] = propertyAnimation.value;
326-
})
327-
);
328-
propertyResetCallbacks.push(
329-
checkAnimation(() => {
330-
if (setLocal) {
331-
propertyAnimation.target.style[opacityProperty.name] = originalValue1;
332-
} else {
333-
propertyAnimation.target.style[opacityProperty.keyframe] = originalValue1;
334-
}
335-
if (propertyAnimation.target.nativeViewProtected) {
336-
propertyAnimation.target[opacityProperty.setNative](propertyAnimation.target.style.opacity);
337-
}
338-
})
339-
);
296+
propertyUpdateCallbacks.push(() => {
297+
propertyAnimation.target.style[setLocal ? opacityProperty.name : opacityProperty.keyframe] = propertyAnimation.value;
298+
});
299+
propertyResetCallbacks.push(() => {
300+
if (setLocal) {
301+
propertyAnimation.target.style[opacityProperty.name] = originalValue1;
302+
} else {
303+
propertyAnimation.target.style[opacityProperty.keyframe] = originalValue1;
304+
}
305+
if (propertyAnimation.target.nativeViewProtected) {
306+
propertyAnimation.target[opacityProperty.setNative](propertyAnimation.target.style.opacity);
307+
}
308+
});
340309
animators.push(createObjectAnimator(nativeView, 'alpha', propertyAnimation.value));
341310
break;
342311

@@ -358,24 +327,20 @@ export class Animation extends AnimationBase {
358327
})
359328
);
360329

361-
propertyUpdateCallbacks.push(
362-
checkAnimation(() => {
363-
propertyAnimation.target.style[setLocal ? backgroundColorProperty.name : backgroundColorProperty.keyframe] = propertyAnimation.value;
364-
})
365-
);
366-
propertyResetCallbacks.push(
367-
checkAnimation(() => {
368-
if (setLocal) {
369-
propertyAnimation.target.style[backgroundColorProperty.name] = originalValue1;
370-
} else {
371-
propertyAnimation.target.style[backgroundColorProperty.keyframe] = originalValue1;
372-
}
373-
374-
if (propertyAnimation.target.nativeViewProtected && propertyAnimation.target[backgroundColorProperty.setNative]) {
375-
propertyAnimation.target[backgroundColorProperty.setNative](propertyAnimation.target.style.backgroundColor);
376-
}
377-
})
378-
);
330+
propertyUpdateCallbacks.push(() => {
331+
propertyAnimation.target.style[setLocal ? backgroundColorProperty.name : backgroundColorProperty.keyframe] = propertyAnimation.value;
332+
});
333+
propertyResetCallbacks.push(() => {
334+
if (setLocal) {
335+
propertyAnimation.target.style[backgroundColorProperty.name] = originalValue1;
336+
} else {
337+
propertyAnimation.target.style[backgroundColorProperty.keyframe] = originalValue1;
338+
}
339+
340+
if (propertyAnimation.target.nativeViewProtected && propertyAnimation.target[backgroundColorProperty.setNative]) {
341+
propertyAnimation.target[backgroundColorProperty.setNative](propertyAnimation.target.style.backgroundColor);
342+
}
343+
});
379344
animators.push(animator);
380345
break;
381346
}
@@ -386,29 +351,25 @@ export class Animation extends AnimationBase {
386351
originalValue1 = nativeView.getTranslationX() / density;
387352
originalValue2 = nativeView.getTranslationY() / density;
388353

389-
propertyUpdateCallbacks.push(
390-
checkAnimation(() => {
391-
propertyAnimation.target.style[setLocal ? translateXProperty.name : translateXProperty.keyframe] = propertyAnimation.value.x;
392-
propertyAnimation.target.style[setLocal ? translateYProperty.name : translateYProperty.keyframe] = propertyAnimation.value.y;
393-
})
394-
);
395-
396-
propertyResetCallbacks.push(
397-
checkAnimation(() => {
398-
if (setLocal) {
399-
propertyAnimation.target.style[translateXProperty.name] = originalValue1;
400-
propertyAnimation.target.style[translateYProperty.name] = originalValue2;
401-
} else {
402-
propertyAnimation.target.style[translateXProperty.keyframe] = originalValue1;
403-
propertyAnimation.target.style[translateYProperty.keyframe] = originalValue2;
404-
}
405-
406-
if (propertyAnimation.target.nativeViewProtected) {
407-
propertyAnimation.target[translateXProperty.setNative](propertyAnimation.target.style.translateX);
408-
propertyAnimation.target[translateYProperty.setNative](propertyAnimation.target.style.translateY);
409-
}
410-
})
411-
);
354+
propertyUpdateCallbacks.push(() => {
355+
propertyAnimation.target.style[setLocal ? translateXProperty.name : translateXProperty.keyframe] = propertyAnimation.value.x;
356+
propertyAnimation.target.style[setLocal ? translateYProperty.name : translateYProperty.keyframe] = propertyAnimation.value.y;
357+
});
358+
359+
propertyResetCallbacks.push(() => {
360+
if (setLocal) {
361+
propertyAnimation.target.style[translateXProperty.name] = originalValue1;
362+
propertyAnimation.target.style[translateYProperty.name] = originalValue2;
363+
} else {
364+
propertyAnimation.target.style[translateXProperty.keyframe] = originalValue1;
365+
propertyAnimation.target.style[translateYProperty.keyframe] = originalValue2;
366+
}
367+
368+
if (propertyAnimation.target.nativeViewProtected) {
369+
propertyAnimation.target[translateXProperty.setNative](propertyAnimation.target.style.translateX);
370+
propertyAnimation.target[translateYProperty.setNative](propertyAnimation.target.style.translateY);
371+
}
372+
});
412373

413374
animators.push(createAnimationSet([createObjectAnimator(nativeView, 'translationX', propertyAnimation.value.x * density), createObjectAnimator(nativeView, 'translationY', propertyAnimation.value.y * density)], propertyAnimation.iterations));
414375
break;
@@ -420,29 +381,25 @@ export class Animation extends AnimationBase {
420381
originalValue1 = nativeView.getScaleX();
421382
originalValue2 = nativeView.getScaleY();
422383

423-
propertyUpdateCallbacks.push(
424-
checkAnimation(() => {
425-
propertyAnimation.target.style[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = propertyAnimation.value.x;
426-
propertyAnimation.target.style[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = propertyAnimation.value.y;
427-
})
428-
);
429-
430-
propertyResetCallbacks.push(
431-
checkAnimation(() => {
432-
if (setLocal) {
433-
propertyAnimation.target.style[scaleXProperty.name] = originalValue1;
434-
propertyAnimation.target.style[scaleYProperty.name] = originalValue2;
435-
} else {
436-
propertyAnimation.target.style[scaleXProperty.keyframe] = originalValue1;
437-
propertyAnimation.target.style[scaleYProperty.keyframe] = originalValue2;
438-
}
439-
440-
if (propertyAnimation.target.nativeViewProtected) {
441-
propertyAnimation.target[scaleXProperty.setNative](propertyAnimation.target.style.scaleX);
442-
propertyAnimation.target[scaleYProperty.setNative](propertyAnimation.target.style.scaleY);
443-
}
444-
})
445-
);
384+
propertyUpdateCallbacks.push(() => {
385+
propertyAnimation.target.style[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = propertyAnimation.value.x;
386+
propertyAnimation.target.style[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = propertyAnimation.value.y;
387+
});
388+
389+
propertyResetCallbacks.push(() => {
390+
if (setLocal) {
391+
propertyAnimation.target.style[scaleXProperty.name] = originalValue1;
392+
propertyAnimation.target.style[scaleYProperty.name] = originalValue2;
393+
} else {
394+
propertyAnimation.target.style[scaleXProperty.keyframe] = originalValue1;
395+
propertyAnimation.target.style[scaleYProperty.keyframe] = originalValue2;
396+
}
397+
398+
if (propertyAnimation.target.nativeViewProtected) {
399+
propertyAnimation.target[scaleXProperty.setNative](propertyAnimation.target.style.scaleX);
400+
propertyAnimation.target[scaleYProperty.setNative](propertyAnimation.target.style.scaleY);
401+
}
402+
});
446403

447404
animators.push(createAnimationSet([createObjectAnimator(nativeView, 'scaleX', propertyAnimation.value.x), createObjectAnimator(nativeView, 'scaleY', propertyAnimation.value.y)], propertyAnimation.iterations));
448405
break;
@@ -456,32 +413,28 @@ export class Animation extends AnimationBase {
456413
originalValue2 = nativeView.getRotationY();
457414
originalValue3 = nativeView.getRotation();
458415

459-
propertyUpdateCallbacks.push(
460-
checkAnimation(() => {
461-
propertyAnimation.target.style[setLocal ? rotateXProperty.name : rotateXProperty.keyframe] = propertyAnimation.value.x;
462-
propertyAnimation.target.style[setLocal ? rotateYProperty.name : rotateYProperty.keyframe] = propertyAnimation.value.y;
463-
propertyAnimation.target.style[setLocal ? rotateProperty.name : rotateProperty.keyframe] = propertyAnimation.value.z;
464-
})
465-
);
466-
propertyResetCallbacks.push(
467-
checkAnimation(() => {
468-
if (setLocal) {
469-
propertyAnimation.target.style[rotateXProperty.name] = originalValue1;
470-
propertyAnimation.target.style[rotateYProperty.name] = originalValue2;
471-
propertyAnimation.target.style[rotateProperty.name] = originalValue3;
472-
} else {
473-
propertyAnimation.target.style[rotateXProperty.keyframe] = originalValue1;
474-
propertyAnimation.target.style[rotateYProperty.keyframe] = originalValue2;
475-
propertyAnimation.target.style[rotateProperty.keyframe] = originalValue3;
476-
}
477-
478-
if (propertyAnimation.target.nativeViewProtected) {
479-
propertyAnimation.target[rotateProperty.setNative](propertyAnimation.target.style.rotate);
480-
propertyAnimation.target[rotateXProperty.setNative](propertyAnimation.target.style.rotateX);
481-
propertyAnimation.target[rotateYProperty.setNative](propertyAnimation.target.style.rotateY);
482-
}
483-
})
484-
);
416+
propertyUpdateCallbacks.push(() => {
417+
propertyAnimation.target.style[setLocal ? rotateXProperty.name : rotateXProperty.keyframe] = propertyAnimation.value.x;
418+
propertyAnimation.target.style[setLocal ? rotateYProperty.name : rotateYProperty.keyframe] = propertyAnimation.value.y;
419+
propertyAnimation.target.style[setLocal ? rotateProperty.name : rotateProperty.keyframe] = propertyAnimation.value.z;
420+
});
421+
propertyResetCallbacks.push(() => {
422+
if (setLocal) {
423+
propertyAnimation.target.style[rotateXProperty.name] = originalValue1;
424+
propertyAnimation.target.style[rotateYProperty.name] = originalValue2;
425+
propertyAnimation.target.style[rotateProperty.name] = originalValue3;
426+
} else {
427+
propertyAnimation.target.style[rotateXProperty.keyframe] = originalValue1;
428+
propertyAnimation.target.style[rotateYProperty.keyframe] = originalValue2;
429+
propertyAnimation.target.style[rotateProperty.keyframe] = originalValue3;
430+
}
431+
432+
if (propertyAnimation.target.nativeViewProtected) {
433+
propertyAnimation.target[rotateProperty.setNative](propertyAnimation.target.style.rotate);
434+
propertyAnimation.target[rotateXProperty.setNative](propertyAnimation.target.style.rotateX);
435+
propertyAnimation.target[rotateYProperty.setNative](propertyAnimation.target.style.rotateY);
436+
}
437+
});
485438

486439
animators.push(createAnimationSet([createObjectAnimator(nativeView, 'rotationX', propertyAnimation.value.x), createObjectAnimator(nativeView, 'rotationY', propertyAnimation.value.y), createObjectAnimator(nativeView, 'rotation', propertyAnimation.value.z)], propertyAnimation.iterations));
487440
break;
@@ -514,20 +467,16 @@ export class Animation extends AnimationBase {
514467
},
515468
})
516469
);
517-
propertyUpdateCallbacks.push(
518-
checkAnimation(() => {
519-
propertyAnimation.target.style[targetStyle] = propertyAnimation.value;
520-
})
521-
);
522-
propertyResetCallbacks.push(
523-
checkAnimation(() => {
524-
propertyAnimation.target.style[targetStyle] = originalValue1;
525-
if (propertyAnimation.target.nativeViewProtected) {
526-
const setter = propertyAnimation.target[extentProperty.setNative];
527-
setter(propertyAnimation.target.style[propertyAnimation.property]);
528-
}
529-
})
530-
);
470+
propertyUpdateCallbacks.push(() => {
471+
propertyAnimation.target.style[targetStyle] = propertyAnimation.value;
472+
});
473+
propertyResetCallbacks.push(() => {
474+
propertyAnimation.target.style[targetStyle] = originalValue1;
475+
if (propertyAnimation.target.nativeViewProtected) {
476+
const setter = propertyAnimation.target[extentProperty.setNative];
477+
setter(propertyAnimation.target.style[propertyAnimation.property]);
478+
}
479+
});
531480
animators.push(extentAnimator);
532481
break;
533482
}

‎packages/core/ui/animation/index.ios.ts‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,10 +464,19 @@ export class Animation extends AnimationBase {
464464
private static _createGroupAnimation(args: AnimationInfo, animation: PropertyAnimation) {
465465
const groupAnimation = CAAnimationGroup.new();
466466
groupAnimation.duration = args.duration;
467+
if (args.repeatCount !== undefined) {
468+
groupAnimation.repeatCount = args.repeatCount;
469+
}
470+
if (args.delay !== undefined) {
471+
groupAnimation.beginTime = CACurrentMediaTime() + args.delay;
472+
}
473+
if (animation.curve !== undefined) {
474+
groupAnimation.timingFunction = animation.curve;
475+
}
467476
const animations = NSMutableArray.alloc<CAAnimation>().initWithCapacity(3);
468477

469478
args.subPropertiesToAnimate.forEach((property) => {
470-
const basicAnimationArgs = { ...args };
479+
const basicAnimationArgs = { ...args, duration: undefined, repeatCount: undefined, delay: undefined, curve: undefined };
471480
basicAnimationArgs.propertyNameToAnimate = `${args.propertyNameToAnimate}.${property}`;
472481
basicAnimationArgs.fromValue = args.fromValue[property];
473482
basicAnimationArgs.toValue = args.toValue[property];

0 commit comments

Comments
 (0)